Laravel5.2之PHP重載(overloading)

說明:本文主要講述PHP中重載概念,因爲Laravel框架中常常使用這塊知識點,而且PHP的重載概念又與其餘OOP語言如JAVA中重載概念不同,故複習並記錄相關知識點。同時,做者會將開發過程當中的一些截圖和代碼黏上去,提升閱讀效率。php

重載(overloading)

在Laravel中就大量應用了重載相關知識,如在IlluminateSupportFacadesFacade中就用到了方法重載知識:使用魔術方法__callStatic()來動態建立類中未定義或不可見的靜態方法。PHP中重載概念與其餘的OOP語言如JAVA語言中重載概念還不同,PHP中重載概念主要是:動態的建立類屬性和方法,而不是通常的類中方法名同樣而參數不同。PHP中經過引入魔術方法來實現動態的建立類屬性和方法,包括屬性重載的魔術方法和方法重載的魔術方法。固然,重載是在類的外部發生的,因此全部魔術方法必須聲明public,並且參數不能引用傳遞。git

PHP中是能夠動態建立一個類中未定義屬性或方法的,這也是PHP這個語言的一個比較靈活的特性,如:github

class Person {

}

$person = new Person();
$person->name = 'PHP';
echo $person->name.PHP_EOL;
$person->age('18');

Person類中沒有屬性$name和方法age(),但PHP能夠動態建立,echo出的$name值是'PHP',訪問未定義的age()方法並不報錯。bootstrap

屬性重載

PHP中引入了4個魔術方法來實現屬性重載:數組

  • __set(string $name, array $value)app

  • __get(string $name)框架

  • __isset(string $name)學習

  • __unset(string $name)ui

一、當在類中定義魔術方法__set()時,給未定義或不可見屬性賦值時會先觸發__set(),可使用__set()魔術方法來禁止動態建立屬性:this

class Person {
    public function __set($name, $value)
    {
        if (isset($this->$name)) {
            return $this->$name = $value;
        } else {
            return null;
        }
    }
}

$person = new Person();
$person->name = 'PHP';
echo $person->name.PHP_EOL;

這時想要動態建立$name屬性就不能夠了,返回null。

二、當在類中定義魔術方法__get()時,當讀取未定義或不可見屬性時就觸發__get()方法:

class Person {
    private $sex;
    public function __set($name, $value)
    {
        if (isset($this->$name)) {
            return $this->$name = $value;
        } else {
            return null;
        }
    }

    public function __get($name)
    {
        return $name;
    }
}

$person = new Person();
$person->name = 'PHP';
echo $person->name.PHP_EOL;
echo $person->sex.PHP_EOL;

若是不寫魔術方法__get(),當讀取不可見屬性$sex就報錯,而這裏返回的是namesex字符串。

三、當在類中定義魔術方法__isset()時,當對未定義或不可見屬性調用isset()或empty()方法時,就會先觸發__isset()魔術方法:

class Person {
    private $sex;
    public function __set($name, $value)
    {
        if (isset($this->$name)) {
            return $this->$name = $value;
        } else {
            return null;
        }
    }

    public function __get($name)
    {
        return $name;
    }

    public function __isset($name)
    {
        echo $name;
    }
}

$person = new Person();
$person->name = 'PHP';
echo $person->name.PHP_EOL;
echo $person->sex.PHP_EOL;
echo isset($person->address).PHP_EOL;

若是沒有魔術方法__isset()最後一行返回空,不然就觸發該魔術方法。

四、一樣的,魔術方法__unset()當使用unset()方法時觸發:

class Person {
    private $sex;
    public function __set($name, $value)
    {
        if (isset($this->$name)) {
            return $this->$name = $value;
        } else {
            return null;
        }
    }

    public function __get($name)
    {
        return $name;
    }

    public function __isset($name)
    {
        echo $name;
    }

    public function __unset($name)
    {
        echo $name.PHP_EOL;
    }
}

$person = new Person();
$person->name = 'PHP';
echo $person->name.PHP_EOL;
echo $person->sex.PHP_EOL;
echo isset($person->address).PHP_EOL;
unset($person->name);

方法重載

上面是類屬性重載,當類方法重載時,PHP提供了兩個魔術方法:__call()和__callStatic(),__call()是動態建立對象方法觸發,__callStatic()是動態建立類方法觸發:

class Person {
    private $sex;
    public function __set($name, $value)
    {
        if (isset($this->$name)) {
            return $this->$name = $value;
        } else {
            return null;
        }
    }

    public function __get($name)
    {
        return $name;
    }

    public function __isset($name)
    {
        echo $name;
    }

    public function __unset($name)
    {
        echo $name.PHP_EOL;
    }

    public function __call(string $method, array $args)
    {
        echo $method.'/'.implode(',', $args).PHP_EOL;
    }

    public function __callStatic(string $method, array $args)
    {
        echo $method.'/'.implode(',', $args).PHP_EOL;
    }
}

$person = new Person();

$person->name = 'PHP';
echo $person->name.PHP_EOL;
echo $person->sex.PHP_EOL;
echo isset($person->address).PHP_EOL;
unset($person->name);

$person->age('18');
Person::education('Master');

當調用對象方法age()時觸發__call()魔術方法,且$args是一個數組,是要傳遞給$method方法的參數。方法返回字符串:age/18education/Master

Laravel中方法重載使用

在使用Laravel的Facade這種模式時,是經過Facade幫咱們代理從容器Container中取出所須要的服務Service,就不須要經過$app['config']這種方式取服務了,如:

$callback = Config::get('github.callback');

可是查看源碼 IlluminateSupportFacadesConfig,發現並無get()這個靜態方法:

<?php

namespace Illuminate\Support\Facades;

/**
 * @see \Illuminate\Config\Repository
 */
class Config extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'config';
    }
}

利用上面知識,當調用一個類中未定義或不可見的靜態方法時,必然是調用了__callStatic()方法,發現IlluminateSupportFacadesFacade這個抽象類中定義了魔術方法__callStatic():

public static function __callStatic($method, $args)
    {
        $instance = static::getFacadeRoot();

        if (! $instance) {
            throw new RuntimeException('A facade root has not been set.');
        }

        switch (count($args)) {
            case 0:
                return $instance->$method();
            case 1:
                return $instance->$method($args[0]);
            case 2:
                return $instance->$method($args[0], $args[1]);
            case 3:
                return $instance->$method($args[0], $args[1], $args[2]);
            case 4:
                return $instance->$method($args[0], $args[1], $args[2], $args[3]);
            default:
                return call_user_func_array([$instance, $method], $args);
        }
    }

其中,

/**
     * Get the root object behind the facade.
     *
     * @return mixed
     */
    public static function getFacadeRoot()
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());//這裏調用Config::getFacadeAccessor(),返回'config',static是靜態延遲綁定
    }
    
    /**
     * Resolve the facade root instance from the container.
     *
     * @param  string|object  $name
     * @return mixed
     */
    protected static function resolveFacadeInstance($name)
    {
        if (is_object($name)) {
            return $name;
        }

        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }
        //這裏是使用$app['config']從容器中解析,也就是實際上Facade貌似是幫咱們從容器中解析Service,其實也是經過$app['config']這種方式去解析。
        //固然,有了Facade後,從容器中解析服務就不用受限於$app這個容器變量了。
        return static::$resolvedInstance[$name] = static::$app[$name];
    }

看到這裏,咱們知道當使用Config::get()方法時,會從容器中解析出名稱爲'config'這個Service,也就是這個Service中有咱們須要的get()方法,那哪個Service名字叫作'config'。實際上,觀察Laravel源碼包的目錄結構也知道在哪了:IlluminateConfigRepository,這個服務就是咱們須要的,裏面get()方法源碼:

/**
     * Get the specified configuration value.
     *
     * @param  string  $key
     * @param  mixed   $default
     * @return mixed
     */
    public function get($key, $default = null)
    {
        return Arr::get($this->items, $key, $default);
    }

既然這個服務Service叫作config,那麼容器類Application剛啓動時就已經把全部須要的服務註冊進來了,而且取了名字。實際上,'config'服務是在IlluminateFoundationBootstrapLoadConfiguration註冊的,看bootstrap()方法源碼:

/**
     * Bootstrap the given application.
     *
     * @param  \Illuminate\Contracts\Foundation\Application  $app
     * @return void
     */
    public function bootstrap(Application $app)
    {
        $items = [];

        // First we will see if we have a cache configuration file. If we do, we'll load
        // the configuration items from that file so that it is very quick. Otherwise
        // we will need to spin through every configuration file and load them all.
        if (file_exists($cached = $app->getCachedConfigPath())) {
            $items = require $cached;

            $loadedFromCache = true;
        }

        $app->instance('config', $config = new Repository($items)); //在這裏註冊名叫config的服務,服務實體是Repository類

        // Next we will spin through all of the configuration files in the configuration
        // directory and load each one into the repository. This will make all of the
        // options available to the developer for use in various parts of this app.
        if (! isset($loadedFromCache)) {
            $this->loadConfigurationFiles($app, $config);
        }

        $app->detectEnvironment(function () use ($config) {
            return $config->get('app.env', 'production');
        });

        date_default_timezone_set($config['app.timezone']);

        mb_internal_encoding('UTF-8');
    }

這個啓動方法作了一些環境監測、時間設置和編碼設置。使用其餘的Facade獲取其餘Service也是這樣的過程。

總結:基本學習了PHP的重載知識後,對使用Laravel的Facade這個方式來獲取服務時有了更深刻的瞭解。總之,多多使用Laravel來作一些東西和多多學習Laravel源碼並模仿之,也是一件有趣的事情。

相關文章
相關標籤/搜索