Laravel工厂重构中依赖属性的正确处理方法

34次阅读

Laravel工厂重构中依赖属性的正确处理方法

本文旨在解决Laravel工厂重构中常见的“Closure object cannot have properties”错误,该错误通常发生在尝试直接访问被定义为闭包的变量的属性时。我们将深入探讨在工厂定义中如何利用闭包进行属性的惰性求值和依赖注入,特别是当一个属性的值依赖于另一个可能由工厂自身创建或外部传入的属性时,提供清晰的解决方案和最佳实践。

理解“Closure object cannot have properties”错误

laravel工厂的重构过程中,将旧版基于闭包的$factory-youjiankuohaophpcndefine定义迁移到新的类式工厂(class-based factories)时,开发者可能会遇到“closure object cannot have properties”的错误。这个错误的核心在于对php闭包(closure)的误解以及它们在工厂属性定义中的求值时机。

原始的$factory->define方法通常在一个大的闭包中执行所有逻辑,包括创建依赖模型。例如:

$factory->define(EmploymentAllowance::class, function (Faker $faker, array $attributes) {     $employment = Arr::exists($attributes, 'employment_id')         ? Employment::where('id', $attributes['employment_id'])->first()         : Employment::factory()->create(); // $employment在此处被解析为Employment模型实例      return [         'employment_id' => $employment->id, // 访问模型实例的id属性         'calendar_id' => fn () => Calendar::factory()->create([             'company_id' => $employment->company_id, // 访问模型实例的company_id属性         ]),     ]; });

在这个例子中,$employment变量在return语句执行之前就已经被赋值为一个Employment模型实例。因此,后续访问$employment->id或$employment->company_id是完全合法的。

然而,当尝试将这种逻辑直接移植到类式工厂的definition()方法中时,如果将$employment定义为一个闭包,然后立即尝试访问其属性,就会出现问题:

public function definition() {     // $employment 被定义为一个闭包,而不是一个Employment模型实例     $employment = fn (array $attributes) => Arr::exists($attributes, 'employment_id')         ? Employment::where('id', $attributes['employment_id'])->first()         : Employment::factory()->create();      return [         'employment_id' => $employment->id, // 错误:尝试访问一个闭包对象的'id'属性         'calendar_id' => fn () => Calendar::factory()->create([             'company_id' => $employment->company_id, // 同样会出错         ]),     ]; }

在上述重构后的代码中,$employment被赋值为一个闭包对象本身,而不是该闭包的执行结果。因此,当代码试图访问$employment->id时,PHP会抛出“Closure object cannot have properties”错误,因为闭包对象本身并没有id属性。

解决方案:利用闭包进行惰性求值和属性依赖

解决这个问题的关键在于理解Laravel工厂的definition()方法中,每个属性的值都可以是一个具体的值,也可以是一个闭包。当属性的值是一个闭包时,Laravel会在实际需要该属性时才执行这个闭包,并且会将当前工厂调用传入的$attributes数组作为参数传递给闭包。这个$attributes数组将包含所有已经解析的属性值,包括那些由其他闭包生成的属性。

以下是针对上述场景的正确实现方式:

use appModelsEmployment; use AppModelsCalendar; use IlluminateDatabaseEloquentFactoriesFactory;  class EmploymentAllowanceFactory extends Factory {     /**      * The name of the factory's corresponding model.      *      * @var string      */     protected $model = EmploymentAllowance::class;      /**      * Define the model's default state.      *      * @return array      */     public function definition()     {         return [             // employment_id 属性的定义:             // 这是一个闭包,它会在需要 employment_id 时被执行。             // 如果工厂调用时没有传入 employment_id,则会创建一个新的 Employment 实例并返回其 id。             // createOne() 方法确保只创建一个实例,如果已存在则不会重复创建。             'employment_id' => fn() => Employment::factory()->createOne()->id,              // calendar_id 属性的定义:             // 这是一个闭包,它会在需要 calendar_id 时被执行。             // 闭包接收一个 $attributes 数组,该数组包含所有已解析的属性,             // 包括上面由 'employment_id' 闭包生成的 employment_id。             'calendar_id' => function (array $attributes) {                 // 根据已解析的 employment_id 获取对应的 Employment 模型实例                 // 即使 employment_id 是由上面的闭包生成的,它也会在此时被解析并存在于 $attributes 中                 $employment = Employment::findOrFail($attributes['employment_id']);                  // 使用获取到的 Employment 实例的 company_id 创建 Calendar 实例                 return Calendar::factory()->create([                     'company_id' => $employment->company_id,                 ])->id; // 返回 Calendar 的 id             },         ];     } }

关键点解析与最佳实践

  1. 惰性求值(Lazy Evaluation):将属性值定义为闭包,可以实现惰性求值。这意味着只有当工厂真正需要该属性的值时,闭包才会被执行。这对于创建依赖于其他属性或需要复杂逻辑的属性非常有用。

    Laravel工厂重构中依赖属性的正确处理方法

    AlibabaWOOD

    阿里巴巴打造的多元电商视频智能创作平台

    Laravel工厂重构中依赖属性的正确处理方法37

    查看详情 Laravel工厂重构中依赖属性的正确处理方法

  2. $attributes 参数:当一个闭包作为工厂属性的值时,它会接收一个$attributes数组作为参数。这个数组包含了所有已经解析的属性值,包括那些在当前闭包之前定义的、同样由闭包生成的属性。这是处理属性间依赖的关键。

  3. createOne() 方法:在Laravel 8+ 中,->createOne()方法是一个有用的补充。它会尝试创建一个模型实例,但如果模型已存在(例如,通过传递id),它会返回现有实例。在工厂中用于生成依赖模型时,它通常比->create()更安全,因为它避免了不必要的重复创建。

  4. findOrFail() 获取依赖模型:在依赖属性的闭包中,使用findOrFail($attributes[‘dependent_id’])是获取已解析依赖模型实例的可靠方式。这确保了无论dependent_id是外部传入还是由工厂自身创建,都能正确地获取到对应的模型。

  5. 返回最终值:工厂的definition()方法中,每个属性的闭包最终应返回该属性的最终值(例如,一个ID或一个字符串),而不是一个模型实例,除非该属性本身就是存储模型实例(这在数据库字段中不常见)。在上面的calendar_id示例中,我们返回了Calendar的id。

通过上述方法,我们可以优雅地处理Laravel工厂中属性间的复杂依赖关系,同时避免“Closure object cannot have properties”这类错误,使得工厂定义更加健壮和可维护。

以上就是Laravel工厂重构中依赖属性的正确处理方法的详细内容,更多请关注php laravel app ai php闭包 php laravel Object define Calendar 字符串 class 闭包 对象 数据库 重构

php laravel app ai php闭包 php laravel Object define Calendar 字符串 class 闭包 对象 数据库 重构

text=ZqhQzanResources