搜尋

首頁  >  問答  >  主體

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 天前767

全部回覆(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
  • 取消回覆