没有不值得去解决的问题,也没有不值得去学习的技术!

Build a GraphQL Server With Lighthouse: Create a GraphQL API for a Simple Blog From Scratch

After successfully executing the data seeding, access /graphql and try the following query. Retrieve a list of all posts in the database, along with all comments and the user's name. It works as expected

1. Getting started building a GraphQL server with Lighthouse. Reference URL: https://lighthouse-php.cn/tutorial/

2. Create a GraphQL API for a simple blog from scratch. Create a new Laravel project via Composer: lighthouse-tutorial

Plaintext
PS E:\wwwroot> composer create-project laravel/laravel lighthouse-tutorial
Creating a "laravel/laravel" project at "./lighthouse-tutorial"
Installing laravel/laravel (v8.6.10)
  - Installing laravel/laravel (v8.6.10): Extracting archive
Created project in E:\wwwroot\lighthouse-tutorial
> @php -r "file_exists('.env') || copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies
Lock file operations: 110 installs, 0 updates, 0 removals
  - Locking asm89/stack-cors (v2.0.5)
  - Locking brick/math (0.9.3)
  - Locking dflydev/dot-access-data (v3.0.1)
  - Locking doctrine/inflector (2.0.4)
  - Locking doctrine/instantiator (1.4.0)
  - Locking doctrine/lexer (1.2.1)
  - Locking dragonmantank/cron-expression (v3.2.3)
  - Locking egulias/email-validator (2.1.25)
  - Locking facade/flare-client-php (1.9.1)
  - Locking facade/ignition (2.17.4)
  - Locking facade/ignition-contracts (1.0.2)
  - Locking fakerphp/faker (v1.17.0)
  - Locking filp/whoops (2.14.5)
  - Locking fruitcake/laravel-cors (v2.0.5)
  - Locking graham-campbell/result-type (v1.0.4)
  - Locking guzzlehttp/guzzle (7.4.1)
  - Locking guzzlehttp/promises (1.5.1)
  - Locking guzzlehttp/psr7 (2.1.0)
  - Locking hamcrest/hamcrest-php (v2.0.1)
  - Locking laravel/framework (v8.78.1)
  - Locking laravel/sail (v1.12.12)
  - Locking laravel/sanctum (v2.13.0)
  - Locking laravel/serializable-closure (v1.0.5)
  - Locking laravel/tinker (v2.6.3)
  - Locking league/commonmark (2.1.1)
  - Locking league/config (v1.1.1)
  - Locking league/flysystem (1.1.9)
  - Locking league/mime-type-detection (1.9.0)
  - Locking mockery/mockery (1.4.4)
  - Locking monolog/monolog (2.3.5)
  - Locking myclabs/deep-copy (1.10.2)
  - Locking nesbot/carbon (2.55.2)
  - Locking nette/schema (v1.2.2)
  - Locking nette/utils (v3.2.6)
  - Locking nikic/php-parser (v4.13.2)
  - Locking nunomaduro/collision (v5.11.0)
  - Locking opis/closure (3.6.2)
  - Locking phar-io/manifest (2.0.3)
  - Locking phar-io/version (3.1.0)
  - Locking phpdocumentor/reflection-common (2.2.0)
  - Locking phpdocumentor/reflection-docblock (5.3.0)
  - Locking phpdocumentor/type-resolver (1.6.0)
  - Locking phpoption/phpoption (1.8.1)
  - Locking phpspec/prophecy (v1.15.0)
  - Locking phpunit/php-code-coverage (9.2.10)
  - Locking phpunit/php-file-iterator (3.0.6)
  - Locking phpunit/php-invoker (3.1.1)
  - Locking phpunit/php-text-template (2.0.4)
  - Locking phpunit/php-timer (5.0.3)
  - Locking phpunit/phpunit (9.5.11)
  - Locking psr/container (1.1.2)
  - Locking psr/event-dispatcher (1.0.0)
  - Locking psr/http-client (1.0.1)
  - Locking psr/http-factory (1.0.1)
  - Locking psr/http-message (1.0.1)
  - Locking psr/log (1.1.4)
  - Locking psr/simple-cache (1.0.1)
  - Locking psy/psysh (v0.10.12)
  - Locking ralouphie/getallheaders (3.0.3)
  - Locking ramsey/collection (1.2.2)
  - Locking ramsey/uuid (4.2.3)
  - Locking sebastian/cli-parser (1.0.1)
  - Locking sebastian/code-unit (1.0.8)
  - Locking sebastian/code-unit-reverse-lookup (2.0.3)
  - Locking sebastian/comparator (4.0.6)
  - Locking sebastian/complexity (2.0.2)
  - Locking sebastian/diff (4.0.4)
  - Locking sebastian/environment (5.1.3)
  - Locking sebastian/exporter (4.0.4)
  - Locking sebastian/global-state (5.0.3)
  - Locking sebastian/lines-of-code (1.0.3)
  - Locking sebastian/object-enumerator (4.0.4)
  - Locking sebastian/object-reflector (2.0.4)
  - Locking sebastian/recursion-context (4.0.4)
  - Locking sebastian/resource-operations (3.0.3)
  - Locking sebastian/type (2.3.4)
  - Locking sebastian/version (3.0.2)
  - Locking swiftmailer/swiftmailer (v6.3.0)
  - Locking symfony/console (v5.4.2)
  - Locking symfony/css-selector (v5.4.2)
  - Locking symfony/deprecation-contracts (v2.5.0)
  - Locking symfony/error-handler (v5.4.2)
  - Locking symfony/event-dispatcher (v5.4.0)
  - Locking symfony/event-dispatcher-contracts (v2.5.0)
  - Locking symfony/finder (v5.4.2)
  - Locking symfony/http-foundation (v5.4.2)
  - Locking symfony/http-kernel (v5.4.2)
  - Locking symfony/mime (v5.4.2)
  - Locking symfony/polyfill-ctype (v1.24.0)
  - Locking symfony/polyfill-iconv (v1.24.0)
  - Locking symfony/polyfill-intl-grapheme (v1.24.0)
  - Locking symfony/polyfill-intl-idn (v1.24.0)
  - Locking symfony/polyfill-intl-normalizer (v1.24.0)
  - Locking symfony/polyfill-mbstring (v1.24.0)
  - Locking symfony/polyfill-php72 (v1.24.0)
  - Locking symfony/polyfill-php73 (v1.24.0)
  - Locking symfony/polyfill-php80 (v1.24.0)
  - Locking symfony/polyfill-php81 (v1.24.0)
  - Locking symfony/process (v5.4.2)
  - Locking symfony/routing (v5.4.0)
  - Locking symfony/service-contracts (v2.5.0)
  - Locking symfony/string (v5.4.2)
  - Locking symfony/translation (v5.4.2)
  - Locking symfony/translation-contracts (v2.5.0)
  - Locking symfony/var-dumper (v5.4.2)
  - Locking theseer/tokenizer (1.2.1)
  - Locking tijsverkoyen/css-to-inline-styles (2.2.4)
  - Locking vlucas/phpdotenv (v5.4.1)
  - Locking voku/portable-ascii (1.5.6)
  - Locking webmozart/assert (1.10.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 110 installs, 0 updates, 0 removals
  - Downloading nunomaduro/collision (v5.11.0)
  - Installing doctrine/inflector (2.0.4): Extracting archive
  - Installing doctrine/lexer (1.2.1): Extracting archive
  - Installing symfony/polyfill-ctype (v1.24.0): Extracting archive
  - Installing webmozart/assert (1.10.0): Extracting archive
  - Installing dragonmantank/cron-expression (v3.2.3): Extracting archive
  - Installing symfony/polyfill-php80 (v1.24.0): Extracting archive
  - Installing symfony/polyfill-mbstring (v1.24.0): Extracting archive
  - Installing symfony/var-dumper (v5.4.2): Extracting archive
  - Installing symfony/polyfill-intl-normalizer (v1.24.0): Extracting archive
  - Installing symfony/polyfill-intl-grapheme (v1.24.0): Extracting archive
  - Installing symfony/string (v5.4.2): Extracting archive
  - Installing symfony/deprecation-contracts (v2.5.0): Extracting archive
  - Installing psr/container (1.1.2): Extracting archive
  - Installing symfony/service-contracts (v2.5.0): Extracting archive
  - Installing symfony/polyfill-php73 (v1.24.0): Extracting archive
  - Installing symfony/console (v5.4.2): Extracting archive
  - Installing psr/log (1.1.4): Extracting archive
  - Installing monolog/monolog (2.3.5): Extracting archive
  - Installing voku/portable-ascii (1.5.6): Extracting archive
  - Installing phpoption/phpoption (1.8.1): Extracting archive
  - Installing graham-campbell/result-type (v1.0.4): Extracting archive
  - Installing vlucas/phpdotenv (v5.4.1): Extracting archive
  - Installing symfony/css-selector (v5.4.2): Extracting archive
  - Installing tijsverkoyen/css-to-inline-styles (2.2.4): Extracting archive
  - Installing symfony/routing (v5.4.0): Extracting archive
  - Installing symfony/process (v5.4.2): Extracting archive
  - Installing symfony/polyfill-php72 (v1.24.0): Extracting archive
  - Installing symfony/polyfill-intl-idn (v1.24.0): Extracting archive
  - Installing symfony/mime (v5.4.2): Extracting archive
  - Installing symfony/http-foundation (v5.4.2): Extracting archive
  - Installing psr/event-dispatcher (1.0.0): Extracting archive
  - Installing symfony/event-dispatcher-contracts (v2.5.0): Extracting archive
  - Installing symfony/event-dispatcher (v5.4.0): Extracting archive
  - Installing symfony/error-handler (v5.4.2): Extracting archive
  - Installing symfony/http-kernel (v5.4.2): Extracting archive
  - Installing symfony/finder (v5.4.2): Extracting archive
  - Installing symfony/polyfill-iconv (v1.24.0): Extracting archive
  - Installing egulias/email-validator (2.1.25): Extracting archive
  - Installing swiftmailer/swiftmailer (v6.3.0): Extracting archive
  - Installing symfony/polyfill-php81 (v1.24.0): Extracting archive
  - Installing ramsey/collection (1.2.2): Extracting archive
  - Installing brick/math (0.9.3): Extracting archive
  - Installing ramsey/uuid (4.2.3): Extracting archive
  - Installing psr/simple-cache (1.0.1): Extracting archive
  - Installing opis/closure (3.6.2): Extracting archive
  - Installing symfony/translation-contracts (v2.5.0): Extracting archive
  - Installing symfony/translation (v5.4.2): Extracting archive
  - Installing nesbot/carbon (2.55.2): Extracting archive
  - Installing league/mime-type-detection (1.9.0): Extracting archive
  - Installing league/flysystem (1.1.9): Extracting archive
  - Installing nette/utils (v3.2.6): Extracting archive
  - Installing nette/schema (v1.2.2): Extracting archive
  - Installing dflydev/dot-access-data (v3.0.1): Extracting archive
  - Installing league/config (v1.1.1): Extracting archive
  - Installing league/commonmark (2.1.1): Extracting archive
  - Installing laravel/serializable-closure (v1.0.5): Extracting archive
  - Installing laravel/framework (v8.78.1): Extracting archive
  - Installing facade/ignition-contracts (1.0.2): Extracting archive
  - Installing facade/flare-client-php (1.9.1): Extracting archive
  - Installing facade/ignition (2.17.4): Extracting archive
  - Installing fakerphp/faker (v1.17.0): Extracting archive
  - Installing asm89/stack-cors (v2.0.5): Extracting archive
  - Installing fruitcake/laravel-cors (v2.0.5): Extracting archive
  - Installing psr/http-message (1.0.1): Extracting archive
  - Installing psr/http-client (1.0.1): Extracting archive
  - Installing ralouphie/getallheaders (3.0.3): Extracting archive
  - Installing psr/http-factory (1.0.1): Extracting archive
  - Installing guzzlehttp/psr7 (2.1.0): Extracting archive
  - Installing guzzlehttp/promises (1.5.1): Extracting archive
  - Installing guzzlehttp/guzzle (7.4.1): Extracting archive
  - Installing laravel/sail (v1.12.12): Extracting archive
  - Installing laravel/sanctum (v2.13.0): Extracting archive
  - Installing nikic/php-parser (v4.13.2): Extracting archive
  - Installing psy/psysh (v0.10.12): Extracting archive
  - Installing laravel/tinker (v2.6.3): Extracting archive
  - Installing hamcrest/hamcrest-php (v2.0.1): Extracting archive
  - Installing mockery/mockery (1.4.4): Extracting archive
  - Installing filp/whoops (2.14.5): Extracting archive
  - Installing nunomaduro/collision (v5.11.0): Extracting archive
  - Installing phpdocumentor/reflection-common (2.2.0): Extracting archive
  - Installing phpdocumentor/type-resolver (1.6.0): Extracting archive
  - Installing phpdocumentor/reflection-docblock (5.3.0): Extracting archive
  - Installing sebastian/version (3.0.2): Extracting archive
  - Installing sebastian/type (2.3.4): Extracting archive
  - Installing sebastian/resource-operations (3.0.3): Extracting archive
  - Installing sebastian/recursion-context (4.0.4): Extracting archive
  - Installing sebastian/object-reflector (2.0.4): Extracting archive
  - Installing sebastian/object-enumerator (4.0.4): Extracting archive
  - Installing sebastian/global-state (5.0.3): Extracting archive
  - Installing sebastian/exporter (4.0.4): Extracting archive
  - Installing sebastian/environment (5.1.3): Extracting archive
  - Installing sebastian/diff (4.0.4): Extracting archive
  - Installing sebastian/comparator (4.0.6): Extracting archive
  - Installing sebastian/code-unit (1.0.8): Extracting archive
  - Installing sebastian/cli-parser (1.0.1): Extracting archive
  - Installing phpunit/php-timer (5.0.3): Extracting archive
  - Installing phpunit/php-text-template (2.0.4): Extracting archive
  - Installing phpunit/php-invoker (3.1.1): Extracting archive
  - Installing phpunit/php-file-iterator (3.0.6): Extracting archive
  - Installing theseer/tokenizer (1.2.1): Extracting archive
  - Installing sebastian/lines-of-code (1.0.3): Extracting archive
  - Installing sebastian/code-unit-reverse-lookup (2.0.3): Extracting archive
  - Installing phpunit/php-code-coverage (9.2.10): Extracting archive
  - Installing doctrine/instantiator (1.4.0): Extracting archive
  - Installing phpspec/prophecy (v1.15.0): Extracting archive
  - Installing phar-io/version (3.1.0): Extracting archive
  - Installing phar-io/manifest (2.0.3): Extracting archive
  - Installing myclabs/deep-copy (1.10.2): Extracting archive
  - Installing phpunit/phpunit (9.5.11): Extracting archive
70 package suggestions were added by new dependencies, use `composer suggest` to see details.
Package swiftmailer/swiftmailer is abandoned, you should avoid using it. Use symfony/mailer instead.
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/sail
Discovered Package: laravel/sanctum
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Package manifest generated successfully.
77 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force
No publishable resources for tag [laravel-assets].
Publishing complete.
> @php artisan key:generate --ansi
Application key set successfully.

3. Create a database: lighthouse_tutorial, and configure the database connection in .env. Run the database migrations to create the users table

Plaintext
PS E:\wwwroot\lighthouse-tutorial> php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (42.37ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (36.39ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (40.11ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (53.85ms)

4. Seed some fake user data into the database. factory(‘App\User’, 10)->create(); is no longer available in Laravel 8. You need to use User::factory()->count(10)->create();. See Figure 1

Seed some fake user data into the database. factory('App\User', 10)->create(); is no longer available in Laravel 8. You need to use User::factory()->count(10)->create();
Figure 1
Plaintext
PS E:\wwwroot\lighthouse-tutorial> php artisan tinker
Psy Shell v0.10.12 (PHP 7.4.27 — cli) by Justin Hileman
>>> factory('App\User', 10)->create();
PHP Fatal error:  Call to undefined function factory() in Psy Shell code on line 1
>>> App\Factory('App\User', 10)->create();
PHP Fatal error:  Call to undefined function App\Factory() in Psy Shell code on line 1
>>> User::factory()->count(50)->create();
[!] Aliasing 'User' to 'App\Models\User' for this Tinker session.
=> Illuminate\Database\Eloquent\Collection {#3581
     all: [
       App\Models\User {#3585
         name: "Ashton Tremblay",
         email: "letha42@example.net",
         email_verified_at: "2022-01-12 05:51:41",
         #password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
         #remember_token: "k1o1mrmP7o",
         updated_at: "2022-01-12 05:51:41",
         created_at: "2022-01-12 05:51:41",
         id: 1,
       },
       App\Models\User {#3592
         name: "Rene Powlowski",
         email: "hkovacek@example.org",
         email_verified_at: "2022-01-12 05:51:41",
         #password: "$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi",
         #remember_token: "64Cl19QXs3",
         updated_at: "2022-01-12 05:51:41",
         created_at: "2022-01-12 05:51:41",
         id: 2,
       },
	   ...
     ],
   }
>>>                                                                                                                     

5. Install Lighthouse, which will be used as the GraphQL server.

Plaintext
PS E:\wwwroot\lighthouse-tutorial> composer require nuwave/lighthouse
Using version ^5.35 for nuwave/lighthouse
./composer.json has been updated
Running composer update nuwave/lighthouse
Loading composer repositories with package information
Updating dependencies
Lock file operations: 5 installs, 0 updates, 0 removals
  - Locking haydenpierce/class-finder (0.4.3)
  - Locking laragraph/utils (v1.1.1)
  - Locking nuwave/lighthouse (v5.35.0)
  - Locking thecodingmachine/safe (v1.3.3)
  - Locking webonyx/graphql-php (v14.11.3)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 5 installs, 0 updates, 0 removals
  - Downloading nuwave/lighthouse (v5.35.0)
  - Installing webonyx/graphql-php (v14.11.3): Extracting archive
  - Installing thecodingmachine/safe (v1.3.3): Extracting archive
  - Installing laragraph/utils (v1.1.1): Extracting archive
  - Installing haydenpierce/class-finder (0.4.3): Extracting archive
  - Installing nuwave/lighthouse (v5.35.0): Extracting archive
6 package suggestions were added by new dependencies, use `composer suggest` to see details.
Package swiftmailer/swiftmailer is abandoned, you should avoid using it. Use symfony/mailer instead.
Generating optimized autoload files
> Illuminate\Foundation\ComposerScripts::postAutoloadDump
> @php artisan package:discover --ansi
Discovered Package: facade/ignition
Discovered Package: fruitcake/laravel-cors
Discovered Package: laravel/sail
Discovered Package: laravel/sanctum
Discovered Package: laravel/tinker
Discovered Package: nesbot/carbon
Discovered Package: nunomaduro/collision
Discovered Package: nuwave/lighthouse
Package manifest generated successfully.
79 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force
No publishable resources for tag [laravel-assets].
Publishing complete.
PS E:\wwwroot\lighthouse-tutorial>

6. Use Altair GraphQL Client as the GraphQL query IDE (online debugging tool). It is like a Postman specifically for GraphQL, with many amazing features. Search for GraphQL in the Chrome Web Store, and it is the first result. See Figure 2

Use Altair GraphQL Client as the GraphQL query IDE (online debugging tool). It is like a Postman specifically for GraphQL, with many amazing features. Search for GraphQL in the Chrome Web Store, and it is the first result.
Figure 2

7. Publish the default schema to graphql/schema.graphql. Error: No publishable resources for tag [schema].

Plaintext
PS E:\wwwroot\lighthouse-tutorial> php artisan vendor:publish --provider="Nuwave\Lighthouse\LighthouseServiceProvider" --tag=schema
No publishable resources for tag [schema].
Publishing complete.
PS E:\wwwroot\lighthouse-tutorial>

8. It is recommended to refer to the latest English documentation: https://lighthouse-php.com/tutorial. Publish the default schema to graphql/schema.graphql. Publishing complete.

Plaintext
PS E:\wwwroot\lighthouse-tutorial> php artisan vendor:publish --tag=lighthouse-schema
Copied File [\vendor\nuwave\lighthouse\src\default-schema.graphql] To [\graphql\schema.graphql]
Publishing complete.
PS E:\wwwroot\lighthouse-tutorial>

9. Configure a virtual host in Nginx: lighthouse-tutorial.local. Open the URL http://lighthouse-tutorial.local/graplql in Altair GraphQL Client. See Figure 3

Configure a virtual host in Nginx: lighthouse-tutorial.local. Open the URL http://lighthouse-tutorial.local/graplql in Altair GraphQL Client
Figure 3
Nginx
server {
    listen 80;
    server_name lighthouse-tutorial.local;
    root E:/wwwroot/lighthouse-tutorial/public;

	access_log  logs/lighthouse-tutorial.local.access.log;
    error_log   logs/lighthouse-tutorial.local.error.log;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        # fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
		fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

Request:

GraphQL

query{
  user(id: 1){
    id
    name
    email
    created_at
    updated_at
  }
}

Response:

JSON
{
  "data": {
    "user": {
      "id": "1",
      "name": "Ashton Tremblay",
      "email": "letha42@example.net",
      "created_at": "2022-01-12 05:51:41",
      "updated_at": "2022-01-12 05:51:41"
    }
  }
}

10. Define models and migrations for our posts and comments. Run the migrations. Add the posts model to app/User.php.

11. The Schema: modify graphql/schema.graphql and define the schema for our posts based on the eloquent models we created. We added two queries for posts to the end of the Query type. @all returns a list of all Post models. @find and @eq are combined to retrieve a single Post by its ID. We can also explicitly define our data model by adding additional type definitions. See Figure 4

We can also explicitly define our data model by adding additional type definitions.
Figure 4
Plaintext
"A date string with format `Y-m-d`, e.g. `2011-05-23`."
scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date")

"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`."
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")

type Query {
    users: [User!]! @paginate(defaultCount: 10)
    user(id: ID @eq): User @find
    posts: [Post!]! @all
    post(id: Int! @eq): Post @find
}

type User {
    id: ID!
    name: String!
    email: String!
    created_at: DateTime!
    updated_at: DateTime!
    posts: [Post!]! @hasMany
}

type Post {
    id: ID!
    title: String!
    content: String!
    author: User! @belongsTo
    comments: [Comment!]! @hasMany
}

type Comment {
    id: ID!
    reply: String!
    post: Post! @belongsTo
}


12. Final test: insert some fake data into your database, which you can do using Laravel seeders. Edit the model factory files: database/factories/PostFactory.php, database/factories/CommentFactory.php.
database/factories/PostFactory.php

PHP
<?php
 
namespace Database\Factories;
 
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Post;
 
class PostFactory extends Factory
{
    protected $model = Post::class;
 
    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        $sentence = $this->faker->sentence();
 
        return [
            'author_id' => rand(1, 100),
            'title' => $sentence,
            'content' => $this->faker->text(),
        ];
    }
}

database/factories/CommentFactory.php

PHP
<?php
 
namespace Database\Factories;
 
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\Comment;
 
class CommentFactory extends Factory
{
    protected $model = Comment::class;
 
    /**
     * Define the model's default state.
     *
     * @return array
     */
    public function definition()
    {
        return [
            'post_id' => rand(1, 100),
            'reply' => $this->faker->sentence(),
        ];
    }
}

13. After customizing the data factories, we start writing the seeding part. Edit the data seeder files: database/seeders/UsersTableSeeder.php, database/seeders/PostsTableSeeder.php, database/seeders/CommentsTableSeeder.php.
database/seeders/UsersTableSeeder.php

PHP
<?php
 
namespace Database\Seeders;
 
use Illuminate\Database\Seeder;
use App\Models\User;
 
class UsersTableSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        User::factory()->count(100)->create();
    }
}

database/seeders/PostsTableSeeder.php

PHP
<?php
 
namespace Database\Seeders;
 
use Illuminate\Database\Seeder;
use App\Models\Post;
 
class PostsTableSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        Post::factory()->count(100)->create();
    }
}

database/seeders/CommentsTableSeeder.php

PHP
<?php
 
namespace Database\Seeders;
 
use Illuminate\Database\Seeder;
use App\Models\Comment;
 
class CommentsTableSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        Comment::factory()->count(100)->create();
    }
}

14. Register the data seeders. Edit database/seeders/DatabaseSeeder.php

PHP
<?php
 
namespace Database\Seeders;
 
use Illuminate\Database\Seeder;
 
class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     *
     * @return void
     */
    public function run()
    {
        $this->call(UsersTableSeeder::class);
        $this->call(PostsTableSeeder::class);
        $this->call(CommentsTableSeeder::class);
    }
}
 

15. Execute the data seeding command: php artisan migrate:refresh –seed. Error: Call to undefined method App\Models\Post::factory(). See Figure 5

Execute the data seeding command: php artisan migrate:refresh --seed. Error: Call to undefined method App\Models\Post::factory()
Figure 5
Plaintext
PS E:\wwwroot\lighthouse-tutorial> php artisan migrate:refresh --seed
Rolling back: 2022_01_12_062416_create_comments_table
Rolled back:  2022_01_12_062416_create_comments_table (16.76ms)
Rolling back: 2022_01_12_062104_create_posts_table
Rolled back:  2022_01_12_062104_create_posts_table (8.20ms)
Rolling back: 2019_12_14_000001_create_personal_access_tokens_table
Rolled back:  2019_12_14_000001_create_personal_access_tokens_table (9.17ms)
Rolling back: 2019_08_19_000000_create_failed_jobs_table
Rolled back:  2019_08_19_000000_create_failed_jobs_table (8.87ms)
Rolling back: 2014_10_12_100000_create_password_resets_table
Rolled back:  2014_10_12_100000_create_password_resets_table (9.72ms)
Rolling back: 2014_10_12_000000_create_users_table
Rolled back:  2014_10_12_000000_create_users_table (7.01ms)
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (28.61ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (31.30ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (31.69ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (39.67ms)
Migrating: 2022_01_12_062104_create_posts_table
Migrated:  2022_01_12_062104_create_posts_table (17.16ms)
Migrating: 2022_01_12_062416_create_comments_table
Migrated:  2022_01_12_062416_create_comments_table (20.25ms)
Seeding: Database\Seeders\UsersTableSeeder
Seeded:  Database\Seeders\UsersTableSeeder (1,839.80ms)
Seeding: Database\Seeders\PostsTableSeeder

   BadMethodCallException

  Call to undefined method App\Models\Post::factory()

  at E:\wwwroot\lighthouse-tutorial\vendor\laravel\framework\src\Illuminate\Support\Traits\ForwardsCalls.php:71
     67▕      * @throws \BadMethodCallException
     68▕      */
     69▕     protected static function throwBadMethodCallException($method)
     70▕     {
  ➜  71▕         throw new BadMethodCallException(sprintf(
     72▕             'Call to undefined method %s::%s()', static::class, $method
     73▕         ));
     74▕     }
     75▕ }

  • Bad Method Call: Did you mean App\Models\Post::author() ?

  1   E:\wwwroot\lighthouse-tutorial\vendor\laravel\framework\src\Illuminate\Support\Traits\ForwardsCalls.php:36
      Illuminate\Database\Eloquent\Model::throwBadMethodCallException("factory")

  2   E:\wwwroot\lighthouse-tutorial\vendor\laravel\framework\src\Illuminate\Database\Eloquent\Model.php:2132
      Illuminate\Database\Eloquent\Model::forwardCallTo(Object(Illuminate\Database\Eloquent\Builder), "factory", [])
PS E:\wwwroot\lighthouse-tutorial>

16. Edit the data model files, app/Models/Post.php and app/Models/Comment.php, to import HasFactory.
app/Models/Post.php

PHP
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Post extends Model
{
    use HasFactory;
 
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'author_id');
    }
 
    public function comments(): HasMany
    {
        return $this->hasMany(Comment::class);
    }
}

app/Models/Comment.php

PHP
<?php
 
namespace App\Models;
 
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
 
class Comment extends Model
{
    use HasFactory;
 
    public function post(): BelongsTo
    {
        return $this->belongsTo(Post::class);
    }
}

17. After successfully executing the data seeding, access /graphql and try the following query. Retrieve a list of all posts in the database, along with all comments and the user’s name. It works as expected. See Figure 6

After successfully executing the data seeding, access /graphql and try the following query. Retrieve a list of all posts in the database, along with all comments and the user's name. It works as expected
Figure 6

Request:

GraphQL
query {
  posts {
    id
    title
    author {
      name
    }
    comments {
      id
      reply
    }
  }
}

Response:

JSON
{
  "data": {
    "posts": [
      {
        "id": "1",
        "title": "Optio earum deserunt fuga qui quia hic.",
        "author": {
          "name": "Mrs. Lottie Kuhlman IV"
        },
        "comments": []
      },
      {
        "id": "2",
        "title": "Soluta eius quisquam consequatur accusantium distinctio minima.",
        "author": {
          "name": "Otilia Marvin"
        },
        "comments": []
      },
      {
        "id": "3",
        "title": "Unde harum commodi rerum nam dolores vel eligendi.",
        "author": {
          "name": "Alfreda Crooks"
        },
        "comments": [
          {
            "id": "14",
            "reply": "Laborum exercitationem odit voluptas cumque adipisci."
          }
        ]
      },
      {
        "id": "4",
        "title": "Praesentium iste nisi aut reprehenderit aliquam.",
        "author": {
          "name": "Hassan Goyette MD"
        },
        "comments": []
      },
      {
        "id": "5",
        "title": "Atque illo consequatur eveniet.",
        "author": {
          "name": "Estevan Mann"
        },
        "comments": []
      },
      {
        "id": "6",
        "title": "Praesentium et eaque soluta.",
        "author": {
          "name": "Prof. Rosalia Cartwright Sr."
        },
        "comments": [
          {
            "id": "5",
            "reply": "Omnis doloremque tempore ut laboriosam nihil."
          }
        ]
      },
      {
        "id": "7",
        "title": "Assumenda consequatur quo perspiciatis reiciendis.",
        "author": {
          "name": "Nestor Bauch"
        },
        "comments": [
          {
            "id": "70",
            "reply": "Error quia natus inventore aliquid praesentium et."
          }
        ]
      },
      {
        "id": "8",
        "title": "Nesciunt eos enim minus ipsa non sed minus.",
        "author": {
          "name": "Reggie Batz"
        },
        "comments": [
          {
            "id": "22",
            "reply": "Magnam et cum reiciendis et."
          }
        ]
      },
      {
        "id": "9",
        "title": "In et quasi dolorem aliquam nemo sequi.",
        "author": {
          "name": "Carroll Cartwright"
        },
        "comments": [
          {
            "id": "73",
            "reply": "Cumque harum id officiis eum cumque."
          }
        ]
      },
      {
        "id": "10",
        "title": "Consequatur modi enim fugit ut eos.",
        "author": {
          "name": "Kiley Connelly"
        },
        "comments": [
          {
            "id": "71",
            "reply": "Aut harum fuga quos veniam quia perspiciatis."
          },
          {
            "id": "90",
            "reply": "Harum unde debitis sunt sint tenetur temporibus voluptates."
          }
        ]
      },
	  ...
    ]
  }
}

18. Randomly select the data where the post’s id equals 10 for verification; the response matches the database. See Figure 7

Randomly select the data where the post's id equals 10 for verification; the response matches the database.
Figure 7

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系