【问题标题】:Dependency injection inside model factory模型工厂内部的依赖注入
【发布时间】:2016-12-06 04:08:11
【问题描述】:

这是我的第一个问题,所以我也很感激有关如何正确提问的提示。

所以,在我的 Laravel 应用程序中,我有一个包含用户的数据库表。首先,我想为它建立一个模型工厂。于是我从laravel doc page取了一个标准码:

$factory->define(App\User::class, function (Faker\Generator $faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => bcrypt(str_random(10)),
        'remember_token' => str_random(10),
    ];
});

我把它改成了:

$factory->define(App\User::class,
                    function(Faker\Generator $faker) {

    return [
        'name' => $faker->name(),
        'email' => $faker->safeEmail(),
        'password' => bcrypt(str_random(10)),
        'phone_number' => $faker->phoneNumber(),
        'remember_token' => str_random(10),
        'account_type' => 0,
    ];

});

到目前为止,一切正常。但我希望它更复杂,我决定使用更具体的 Faker 类来生成意大利数据。我把它改成:

$factory->define(App\User::class,
                    function(Faker\Generator $faker,
                             Faker\Provider\it_IT\PhoneNumber $fakerITPN,
                             Faker\Provider\it_IT\Person $fakerITPER,
                             Faker\Provider\it_IT\Internet $fakerITInt) {

    return [
        'name' => $fakerITPER->name(),
        'email' => $fakerITInt->safeEmail(),
        'password' => bcrypt(str_random(10)),
        'phone_number' => $fakerITPN->phoneNumber(),
        'remember_token' => str_random(10),
        'account_type' => 0,
    ];

});

在播种机课上我写道:

factory(App\User::class)->create();

然后,在我使用 Artisan 之后,命令:

artisan migrate:refresh --seed -vvv

我得到以下错误(只是头部,为了清除):

[ErrorException]                                                                                                                             
  Argument 2 passed to Illuminate\Database\Eloquent\Factory::{closure}() must be an instance of Faker\Provider\it_IT\PhoneNumber, array given  

Exception trace:
 () at /home/vagrant/php/housing/database/factories/ModelFactory.php:19
 Illuminate\Foundation\Bootstrap\HandleExceptions->handleError() at /home/vagrant/php/housing/database/factories/ModelFactory.php:19
 Illuminate\Database\Eloquent\Factory::{closure}() at n/a:n/a
 call_user_func() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:130
 Illuminate\Database\Eloquent\FactoryBuilder->Illuminate\Database\Eloquent\{closure}() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php:2308
 Illuminate\Database\Eloquent\Model::unguarded() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:133
 Illuminate\Database\Eloquent\FactoryBuilder->makeInstance() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:105
 Illuminate\Database\Eloquent\FactoryBuilder->make() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Eloquent/FactoryBuilder.php:83
 Illuminate\Database\Eloquent\FactoryBuilder->create() at /home/vagrant/php/housing/database/seeds/UsersTableSeeder.php:24
 UsersTableSeeder->run() at /home/vagrant/php/housing/vendor/laravel/framework/src/Illuminate/Database/Seeder.php:42

显然,依赖注入有问题,但我不知道是什么。我知道,在这种情况下,我可以手动创建我需要的类的实例,但我想知道如何正确地做到这一点。有人可以帮忙吗?

【问题讨论】:

    标签: dependency-injection laravel-5


    【解决方案1】:

    如果您查看 faker@https://github.com/fzaninotto/Faker#localization 的文档,您会发现您可以简单地将正确的本地化指定为创建参数。

    在你的情况下,只需使用:

    Faker\Factory::create('it_IT');
    

    你在定义工厂时不需要在匿名函数中添加更多参数。

    编辑:

    只是补充一下依赖注入的问题。如果你跟踪源代码,它不会在下面做任何依赖注入。

    $factory->define(...)
    

    只设置定义数组

    public function define($class, callable $attributes, $name = 'default')
    {
        $this->definitions[$class][$name] = $attributes;
    }
    

    打电话

    Faker\Factory::create();
    

    factory(App\User::class)->create();
    
    $factory->of($class)
    

    调用实例化 FactoryBuilder 的“of”方法 (参见 Illuminate\Database\Eloquent\Factory.php 的第 169-172 行)

    public function of($class, $name = 'default')
    {
        return new FactoryBuilder($class, $name, $this->definitions, $this->faker);
    }
    

    之后,它链接 FactoryBuilder 的“create”方法,该方法调用“make”方法,该方法也调用“makeInstance”

    protected function makeInstance(array $attributes = [])
    {
        return Model::unguarded(function () use ($attributes) {
            if (! isset($this->definitions[$this->class][$this->name])) {
                throw new InvalidArgumentException("Unable to locate factory with name [{$this->name}].");
            }
    
            $definition = call_user_func($this->definitions[$this->class][$this->name], $this->faker, $attributes);
    
            return new $this->class(array_merge($definition, $attributes));
        });
    }
    

    注意“makeInstance”中的“call_user_func”,它负责调用作为定义的第二个参数创建的匿名函数(在ModelFactory.php 中)。它专门只将 2 个参数传递给可调用函数,它们是:

    ...$this->faker, $attributes);
    

    第一个参数只传递了一个faker,第二个参数传递了一个属性数组(这是你之前在ErrorException中看到的那个)

    这意味着你只能这样定义你的工厂:

    $factory->define(App\User::class, 
        function (Faker\Generator $faker, $attributes=array()) {
    
        return [
            'name' => $faker->name,
            'email' => $faker->email,
            'password' => bcrypt(str_random(10)),
            'remember_token' => str_random(10),
        ];
    });
    

    如果你真的需要其他类,你可以在“define”之外初始化它,并在函数中使用它,如下所示:

    $sampleInstance = app(App\Sample::class);
    
    $factory->define(App\User::class, 
        function (Faker\Generator $faker, $attributes=array()) use($sampleInstance){
    
        //...do something here 
        //...or process the $attributes received
        //...or call a method like
        $sampleData = $sampleInstance->doSomething();        
    
        return [
            'someField' => $sampleData,
            'name' => $faker->name,
            'email' => $faker->email,
            'password' => bcrypt(str_random(10)),
            'remember_token' => str_random(10),
        ];
    });
    

    【讨论】:

    • 谢谢,好点子!但问题是,如果我想在这个匿名函数中定义其他参数,我该怎么做呢?特别是,如何让类的对象像这样初始化?或者,编写好的代码是不可能的,或者没有必要?
    • @Staszek 我已经更新了我的答案以解决您的问题
    • @Staszek 如果它很好地解决了您的问题,请接受它作为答案。
    • 很久以前就已经这样做了。由于缺乏声望点,无法对其进行投票。再次感谢!
    • 不用担心。谢谢@Staszek
    【解决方案2】:

    你可以把这个设置放到AppServiceProvider的register()中:

    $this->app->singleton(\Faker\Generator::class, function () {
            return \Faker\Factory::create('it_IT');
    });
    

    【讨论】:

      猜你喜欢
      • 2012-08-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-20
      • 1970-01-01
      相关资源
      最近更新 更多