我们公司现在在用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这种逻辑层类复用的情况?
漂亮男人2017-05-16 16:55:44
有趣的问题,Yii 在业界也是被公认为性能比 Laravel 高的一个框架。于是我想单从 ActiveRecord 的构造,看看两大框架的具体实现。
在 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 的实现。
等等,IlluminateDatabaseEloquentBuilder 和 IlluminateDatabaseQueryBuilder 的区别是啥?
其实 EloquentBuilder 是对 QueryBuilder 的进一步封装,以便更好的实现关系对象查询。
题主既然用到 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);
}
通常情况下(无参数的构造函数或注入参数已配置),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 快速导入
对于静态和非静态开销的问题,在 StackOverflow 上有一篇讨论:http://stackoverflow.com/questions/14727...
所以说到底还是看业务需求嘛23333
高洛峰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容器/服务容器)
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
淡淡烟草味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() 就可以了