搜索

首页  >  问答  >  正文

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这种逻辑层类复用的情况?

伊谢尔伦伊谢尔伦2756 天前768

全部回复(5)我来回复

  • 漂亮男人

    漂亮男人2017-05-16 16:55:44

    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 方法来自 IlluminateDatabaseEloquentBuilder,而该类内部使用 IlluminateDatabaseQueryBuilder 的实现。

    等等,IlluminateDatabaseEloquentBuilderIlluminateDatabaseQueryBuilder 的区别是啥?

    其实 EloquentBuilder 是对 QueryBuilder 的进一步封装,以便更好的实现关系对象查询。

    那么,其实过程为:

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

    0x2 Yii 1.1 中的 CActiveRecord

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

    好了,伙计们,现在使用 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

    回复
    0
  • 阿神

    阿神2017-05-16 16:55:44

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

    回复
    0
  • 高洛峰

    高洛峰2017-05-16 16:55:44

    我假定你对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容器/服务容器)

    回复
    0
  • PHP中文网

    PHP中文网2017-05-16 16:55:44

    我司是继承一个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

    回复
    0
  • 淡淡烟草味

    淡淡烟草味2017-05-16 16:55:44

    我是这么做的,在底层 model 里建立这个函数
    修改bootstrap/app.php 和 AppServiceProvider.php
    具体参考 Service Provider

        static public function load(){
            return app()->make(get_called_class());
        }

    在controller 里调用 Foo::load() 就可以了

    回复
    0
  • 取消回复