首页 > Laravel 里面model如何重用

Laravel 里面model如何重用

我们公司现在在用Laravel开发项目,同时还增加了Biz层和Repositories层,来实现业务逻辑封装,反而model里面什么代码都没有。
Controller里写代码的时候,尝尝困扰我的问题是如果复用Biz对象,Repositories对象和Model对象。
以前用Yii开发项目的时候,有一个工厂模式,所以调用Model的时候,基本都不new,都是字节用 XXX::model() 来调用的,一个对象被new一次就够了,能有效节省内存。
Controller代码:

$productModel = Product::model()->getList('xxxxxxxxx');

多简单,有没有?

Laravel里,Model好像没有工厂,要调用,都需要实例,假如Repositories里面封装了5个方法,每个都使用了Model,那么我在Controller里调用了这5个方法,Model就被new了5次。
目前在网上看到一种办法,就是在Repositories的构造函数里注入Model对象,放在Repositories的私有成员变量里,这样5个方法都调用当前类的私有变量就可以了。但使用起来就麻烦了,在Controller里写代码的时候,需要这样写:

$xxxBiz = new XXXBiz(\xxx\xxx\Repositories);

在Repositories里需要这样写:

$xxxRepositories = new XXXRepositories(\xxx\xx\xxxModel);

在new一个Biz的时候,还必须传入Repositories对象,而且命名空间还老长,我基本都是在拼字符串了,写代码效率超低,还要打开这写文件,去把名字拼出来。

想问一下大家在用Laravel开发项目的时候,是如何解决Model这种逻辑层类复用的情况?


通过依赖注入呀
直接可以注入到Controller中
可以看看这个文章
http://slides.com/howtomakeaturn/model#/


我司是继承一个BaseRepository,BaseRepository中定义

protected function getUserCouponModel($new = false)
{
    if (is_null($this->userCouponModel) || true === $new) {
        $this->userCouponModel = new UserCouponModel();
    }
    return $this->userCouponModel;
}

CouponRepository中

public function create($couponID)
{
    $attributes = [
        'couponID' => $couponID,
    ];

    return $this->getUserCouponModel(true)->create($attributes);
}

Biz中类似,继承一个BaseBiz,然后方法这么写

public function create($fields)
{
    return $this->getCouponRepository()->create($fields);
}

Controller中调用

$biz = new Biz();
$biz->create($fields);

Controller ---> Biz ---> Repository


我假定你对Laravel还不是很了解。

第一,Laravel的Model也就是模型,不需要显式的实例化,调用方式像下面这样(摘自官方文档):

$flights = App\Flight::where('active', 1)
               ->orderBy('name', 'desc')
               ->take(10)
               ->get();

第二,你的描述有错误。你寻找的不是工厂模式(factory pattern),而是单例模式(singleton pattern),对象在一次请求生命周期之内,最多只需要实例化一次。在Laravel当中,需要用到IOC(inversion of control)容器或者说是服务容器(service container)。像下面这样:

// 先绑定需要实例化的对象或者服务
$this->app->singleton('FooBar', function ($app) {
    return new FooBar($app['SomethingElse']);
});
// 调用对象或服务有多种方式,比如以下两种:
// 第一种
$fooBar = $this->app->make('FooBar'); // 显式解析
$fooBar = $this->app['FooBar']; // 像访问数组一样调用之前被显式解析(实例化)过的对象或服务
// 第二种
// 通过类型声明(type hinting)来自动解析,无需显式解析(实例化),直接调用,请参考最后附上的文档
// 除了单例模式外,当然还支持工厂模式,即每次调用,返回一个新的实例,像下面这样:
$this->app->bind('HelpSpot\API', function ($app) {
    return new HelpSpot\API($app['HttpClient']);
});

以上只是简单的摘录,具体用法,请参考Laravel官方优秀的文档,链接如下:
Service Container(IOC容器/服务容器)


0x0 前言

有趣的问题,Yii 在业界也是被公认为性能比 Laravel 高的一个框架。于是我想单从 ActiveRecord 的构造,看看两大框架的具体实现。

0x1 Laravel 的 Eloquent 框架

在 Laravel 中可以很轻松地使用关系查询:

    $user = User::find(1);

我在 User 类里并没有找到 find 方法,WTF,发生了什么!?

User 的基类是 Model,使用静态调用,所以会调用 Model 的 __callStatic 魔术方法:

    public static function __callStatic($method, $parameters)
    {
        // $method = 'find';
        // $parameters = [1];
        
        // 实例化当前类,即 User 模块
        $instance = new static;
        // 调用当前实例的 find 方法
        return call_user_func_array([$instance, $method], $parameters);
    }

其实就是再调用 __call 魔术方法:

    public function __call($method, $parameters)
    {
        //...

        $query = $this->newQuery();

        return call_user_func_array([$query, $method], $parameters);
    }

追根溯源,我们发现其实 find 方法来自 Illuminate\Database\Eloquent\Builder,而该类内部使用 Illuminate\Database\Query\Builder 的实现。

等等,Illuminate\Database\Eloquent\BuilderIlluminate\Database\Query\Builder 的区别是啥?

其实 Eloquent\Builder 是对 Query\Builder 的进一步封装,以便更好的实现关系对象查询。

那么,其实过程为:

也就是说,你每次 静态调用 Model 的方法,都会实例化一次 Model,走一次过程。

0x2 Yii 1.1 中的 CActiveRecord

题主既然用到 model 方法,那应该是 1.1 的版本,模块继承自 CActiveRecord(Yii2 中则继承自 Yii\db\ActiveRecord)。

好了,伙计们,现在使用 Yii 实现关系查询,先定义:

    class User extends CActiveRecord
    {
        public static function model($className=__CLASS__)
        {
            return parent::model($className);
        }
    }

查询:

    $user = User::model()->findAllByPk(1);

明显查询对象来自 model,看看父类怎么实现这个函数:

    public static function model($className=__CLASS__)
    {
        // 如果已经实例化了则直接返回该实例
        if(isset(self::$_models[$className]))
            return self::$_models[$className];
        else
        {
            // 初始化,并将保存当前实例
            $model=self::$_models[$className]=new $className(null);
            $model->attachBehaviors($model->behaviors());
            return $model;
        }
    }

findAllByPk 方法直接封装于 CActiveRecord 内部:

    public function findByPk($pk,$condition='',$params=array())
    {
        // ...

        $criteria = $this->getCommandBuilder()->createPkCriteria($this->getTableSchema(),
            $pk, $condition, $params, $prefix);
            
        return $this->query($criteria);
    }

所以其过程为:

0x3 使用 Laravel 的依赖注入

通常情况下(无参数的构造函数或注入参数已配置),Laravel 会自动帮你实例化:

<?php namespace App\Repositories;

use App\User;    
    
class Repository {
    
    protected $user;
    
    public __construct(User $user) {
        $this->user = $user;
    }
}

所以你可以很轻松复用同一个对象:

class Repository {
    
    protected $user;
    
    public __construct(User $user) {
        $this->user = $user;
    }
    
    public function first() {
        $this->user->first();
    }
    
    public function find(Request $request, $id) {
        if (Gate::denies('view', $request->user())) {
            abort(403);
        }
        
        return $this->user->find($id);
    }
    
    public function excited() {
        return $this->user->where('name', 'bigNews')->get();
    }
}

实现完仓库后,你需要手动实例化吗:

    $repo = new App\Repositories\Repository(new App\User());

不,这不符合 Laravel 的哲♂学,你可以如此简单:

<?php
    use App\Repositories\Repository;
    
    public function index(Repository $repo) {
        return $repo->first();
    }

是的,没错,你无需手动构造,传入 User 实例等等,一切只是一个简单的自动注入。而且题主注意到这里使用了命名空间,所以你只需 use 一次。(当然如果你不想拼写这么长的命名空间,那你是时候换一款 IDE 了,PhpStorm 中可以使用 Alt + Enter 快速导入

0x4 最后

对于静态和非静态开销的问题,在 StackOverflow 上有一篇讨论:http://stackoverflow.com/questions/14727...

所以说到底还是看业务需求嘛23333

【热门文章】
【热门文章】