从Laravel源码看PHP设计模式

先前写在部门blog issue#12

Posted on September 7, 2016 in Laravel Design pattern

原文》https://github.com/100steps/Blogs/issues/12

工厂模式

用工厂方法或者类来实例化对象,而不是直接new。 首先我们需要创建一个工厂类,比如Factory.php。如果不使用工厂模式的,我们需要一个对象的时候通常需要
new Inexistencegirlfriend();
然而我们一般不只在一个地方需要这个对象,这个时候一旦对象发生变更,或者对象的某些属性发生变化,我们就需要一个一个的来改,非常麻烦。这个时候我们引入工厂类,在Factory.php

<?php
namespace Imagination;

class Factory
{
    static function getGirlfriend()
    {
        $GF = new girlfriend;
        return $GF;
    }
}

然后每次调用时$GF1 = ImaginationFactory::getGirlfriend()就可以避免四处修改的问题。
在Laravel中这样的设计模式很常见。

class CommentsController extends Controller {

    /**
     * Store a newly created resource in storage.
     *
     * @return Response
     */
    public function store()
    {
        if (Comment::create(Input::all())) {
            return Redirect::back();
        } else {
            return Redirect::back()->withInput()->withErrors('Fail to comment!');
        }
    }

}

单例模式

即确保某个类的对象仅被创建一次。比如我们在database里面存了很多女生的联系方式,如果我们用pdo的话每次查找都会new一个对象,势必会造成资源的浪费。所以我们就在connect之前做个判断。

class Database
{
    static private $db;

    private function __construct()
    {

    }

    static function getInstance()
    {
        if (empty(self::$db)) {
            self::$db = new self;
            return self::$db;
        } else {
            return self::$db;
        }
    }

    function where($where)
    {
        return $this;
    }

    function order($order)
    {
        return $this;
    }

    function limit($limit)
    {
        return $this;
    }

    function query($sql)
    {
        echo "SQL: $sql
";
    }
}

这里面比较关键的地方在于声明了一个私有变量和私有的构造方法,然后再在这个类里面new自己,就避免了在其他地方重复实例化的问题。这个时候我们已经没法直接new Database了,我们只能通过调用get Instance方法来建立连接。这里顺带讲一下PHP的链式操作的实现。在很多框架比如用完26个字母就不知道怎么办的thinkPHP和Laravel中对数据库的操作可以使用链式操作,这样可以使代码更为优雅。具体实现就是使用return this;,这样就可以用where($where)->order($order)->limit(1);来代替多行语句。 Laravel使用了三目运算符来代替if,显得更为优雅。

public function hasMany($related, $foreignKey = null, $localKey = null)
    {
        $foreignKey = $foreignKey ?: $this->getForeignKey();
        $instance = new $related();
        $localKey = $localKey ?: $this->getKeyName();
        return new HasMany($instance->newQuery(), $this, $instance->getTable() . '.' . $foreignKey, $localKey);
    }

注册模式

解决全局共享和交换对象的问题。实际上就是把实例好的对象放进一个数组,在任何地方要用的时候就去出来。就好比有一课树,我们把new好的$GF1,$GF2。。。一个一个挂上去,要用的时候再取出来。

class Register
{
    protected static $objects;

    /**
     * 把对象映射到树上
     * @param string $alias  对象的别名
     * @param array $object 储存所有对象
     */
    static function set($alias, $object)
    {
        self::$objects[$alias] = $object;
    }

    /**
     * 把对象从树上移除
     * @param  string $alias 对象的别名
     * @return [type]        [description]
     */
    function _unset($alias)
    {
        unset(self::$objects[$alias]);
    }
}

unset在PHP中是关键字,所以用_unset代替。这样的话我们就要在工厂类中用一下Register::set()方法,把new好的对象挂树上。为了调用方便,Register中还需要一个get()方法来取对象。

    static function get($key)
    {
        if (!isset(self::$objects[$key]))
        {
            return false;
        }
        return self::$objects[$key];
    }

这样我们也就不用再去使用单例模式了,直接从注册器中取Register::get()
Laravel中用了更优雅的方式。

<?php namespace IlluminateContractsAuth;

interface Registrar {

    /**
     * Get a validator for an incoming registration request.
     *
     * @param  array  $data
     * @return IlluminateContractsValidationValidator
     */
    public function validator(array $data);

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return User
     */
    public function create(array $data);

}

— PHP还有很多有用的设计模式,比如观察者模式、代理模式、装饰器模式等,因为时间不够就不写了。有兴趣的可以自行了解。Laravel的源码遵循PSR规范,建议先了解再来看源码。