【问题标题】:Laravel Custom Model MethodsLaravel 自定义模型方法
【发布时间】:2014-05-14 15:05:07
【问题描述】:

每当我向 Eloquent 模型添加额外的逻辑时,我最终不得不将其设为 static 方法(即不太理想)以便从模型的外观调用它。我已经尝试了很多关于如何以正确的方式执行此操作的方法,并且几乎所有结果都讨论了创建返回部分查询生成器接口的方法。我试图弄清楚如何添加可以返回任何内容并使用模型外观调用的方法。

例如,假设我有一个名为 Car 的模型并想要全部获取:

$cars = Car::all();

很好,除了现在,假设我想通过 make 将结果排序为一个多维数组,所以我的结果可能如下所示:

$cars = array(
  'Ford' => array(
     'F-150' => '...',
     'Escape' => '...',
  ),
  'Honda' => array(
     'Accord' => '...',
     'Civic' => '...',
  ),
);

以这个理论示例为例,我很想创建一个可以像这样调用的方法:

$cars = Car::getAllSortedByMake();

暂时,让我们忘记可怕的方法名称以及它与数据结构紧密耦合的事实。如果我在模型中创建这样的方法:

public function getAllSortedByMake()
{
   // Process and return resulting array
   return array('...');
}

最后在我的控制器中调用它,我会抛出这个异常:

不应静态调用非静态方法 Car::getAllSortedByMake(),假设 $this 来自不兼容的上下文

TL;DR:如何在不使其成为静态方法并使用模型外观调用的情况下添加对模型有意义的自定义功能?


编辑:

这是一个理论上的例子。也许改写这个问题会更有意义。为什么在 Eloquent 模型的外观上可以使用某些非静态方法,例如 all()which(),但没有添加到模型中的其他方法?这意味着正在使用__call魔术方法,但是我怎样才能让它识别我自己在模型中的功能呢?

可能比“排序”更好的例子是如果我需要对一条数据运行计算或算法:

$validSPG = Chemical::isValidSpecificGravity(-1.43);

对我来说,将类似的东西放在模型中是有意义的,因为它是特定于领域的。

【问题讨论】:

  • 首先有两个数据表:manufacturersmodels,所以manufacturers 包含“Ford”、“Honda”等,modelsmanufacturer_id 链接 modelmanufacturer 并包含“F-150”、“Escape”、“Accord”、“Civic”等
  • @MarkBaker 这是一个理论上的例子。我的问题更多的是基本层面,例如为什么all() 可以通过外观访问?它不是静态方法,这意味着正在使用__call 魔术方法。因此,为什么arbitraryMethodICreate() 无法访问?
  • Illuminate\Database\Eloquent\Modelall是静态方法
  • cilosis:原因很简单:all() 实际上是Model 上的静态方法,在这种情况下不会调用__callModel 类上有更多的静态方法,其他可以使用的方法Model::method()__callStatic 然后__call 魔术方法处理并传递给Eloquent Builder 类。

标签: php laravel laravel-4 eloquent facade


【解决方案1】:

我的问题更多的是基本层面,例如为什么 all() 可通过立面进入?

如果你查看 Laravel Core - all() 实际上是一个静态函数

public static function all($columns = array('*'))

你有两个选择:

public static function getAllSortedByMake()
{
    return Car::where('....')->get();
}

public function scopeGetAllSortedByMake($query)
{
    return $query->where('...')->get();
}

两者都允许你这样做

Car::getAllSortedByMake();

【讨论】:

  • 啊,我听说过 Eloquent 中的范围,但还没有使用过。谢谢!我看到all() 现在是静态的(这就是我没有验证的结果)。我一直听说这些方法不是,而是使用设计模式来显示以实现可用性,同时保持可测试性。
  • 是的 - 大多数函数都不是静态的 - 出于某种原因,只是一些模型函数 - 我相信 Taylor 有充分的理由 - 但它超出了我的知识范围。
  • 请注意"scopes should always return a query builder instance"。因此,如果您使用->get(),您应该使用静态方法而不是范围变体。另请注意,范围可以链接:Car::GetAllSortedByMake()->GetAllSortedByMake()->otherScopeYouHaveDefined()->where(...)->get().
  • 不要使用范围,因为查询不一样,因为范围是孤立的
【解决方案2】:

实际上,您可以扩展 Eloquent Builder 并将自定义方法放在那里。

扩展构建器的步骤:

1.创建自定义构建器

<?php

namespace App;

class CustomBuilder extends \Illuminate\Database\Eloquent\Builder
{
    public function test()
    {
        $this->where(['id' => 1]);

        return $this;
    }
}

2.将此方法添加到您的基础模型中:

public function newEloquentBuilder($query)
{
    return new CustomBuilder($query);
}

3.使用自定义构建器中的方法运行查询:

User::where('first_name', 'like', 'a')
    ->test()
    ->get();

以上代码生成的mysql查询将是:

select * from `users` where `first_name` like ? and (`id` = ?) and `users`.`deleted_at` is null

PS:

First Laurence 示例是更适合您的存储库而不是模型的代码,但您也不能使用这种方法管道更多方法:

public static function getAllSortedByMake()
{
    return Car::where('....')->get();
}

Second Laurence 例子是最糟糕的。

public function scopeGetAllSortedByMake($query)
{
    return $query->where('...')->get();
}

许多人建议使用范围来扩展 laravel 构建器,但这实际上是一个糟糕的解决方案,因为范围被 eloquent 构建器隔离,并且您不会在范围内和范围外使用相同的命令获得相同的查询。我提议 PR 来改变是否应该隔离范围,但 Taylor 忽略了我。

更多解释: 例如,如果您有这样的范围:

public function scopeWhereTest($builder, $column, $operator = null, $value = null, $boolean = 'and')
{
    $builder->where($column, $operator, $value, $boolean);
}

和两个雄辩的查询:

User::where(function($query){
    $query->where('first_name', 'like', 'a');
    $query->where('first_name', 'like', 'b');
})->get();

User::where(function($query){
    $query->where('first_name', 'like', 'a');
    $query->whereTest('first_name', 'like', 'b');
})->get();

生成的查询将是:

select * from `users` where (`first_name` like ? and `first_name` like ?) and `users`.`deleted_at` is null

select * from `users` where (`first_name` like ? and (`id` = ?)) and `users`.`deleted_at` is null

乍一看,查询看起来相同,但事实并非如此。对于这个简单的查询可能无关紧要,但对于复杂的查询它确实如此,所以请不要使用范围来扩展构建器:)

【讨论】:

    【解决方案3】:

    为了更好的动态代码,而不是使用模型类名“Car”,

    只使用“静态”或“自我”

    public static function getAllSortedByMake()
    {
        //to return "Illuminate\Database\Query\Builder" class object you can add another where as you want
        return static::where('...');
    
        //or return already as collection object
        return static::where('...')->get();
    }
    

    【讨论】:

      【解决方案4】:

      Laravel 模型自定义方法 -> 最好的方法是使用特征

      • 第 1 步:创建特征
      • 第 2 步:将特征添加到模型中
      • 步骤#3:使用方法
      User::first()->confirmEmailNow()
      

      app/Model/User.php

      use App\Traits\EmailConfirmation;
      
      class User extends Authenticatable
      {
          use EmailConfirmation;
          //...
      }
      

      app/Traits/EmailConfirmation.php

      <?php
      namespace App\Traits;
      
      trait EmailConfirmation
      {
          /**
           * Set email_verified_at to now and save.
           *
           */
          public function confirmEmailNow()
          {
              $this->email_verified_at = now();
              $this->save();
              return $this;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-12
        • 1970-01-01
        • 2013-04-18
        • 2013-09-13
        • 2017-02-04
        相关资源
        最近更新 更多