Как работает Yii::app()?

Насколько я понимаю, Yii::app() это метод через который можно получить доступ к сервисам, зарегистрированым во фремворке. Что такое DI и Service Locator я знаю и понимаю что Yii::app() реализует к ним доступ. Но как именно? Как в метод app() попадает имя сервиса, как это реализовать?
  • Вопрос задан
  • 5112 просмотров
Решения вопроса 1
copist
@copist
Empower people to give
1. Инициализация Yii::app()

Смотри, в файле www/index.php такие строки
1. $config = APP_PATH . '/protected/config/main.php';
2. $app = \Yii::createWebApplication($config); // @var CWebApplication $app
3. $app->run();


При отработке строки 2 выполняется код
1. class YiiBase {
2. {
3.     // ... другой код
4. 
5.     public static function createWebApplication($config=null)
6.     {
7.         return self::createApplication('CWebApplication',$config);
8.     }
9.
10.    public static function createApplication($class,$config=null)
11.    {
12.        return new $class($config);
13.    }
14. }


В строке 12 с учётом динамического связывания выполняется return new CWebApplication($config);

Если пройти по родительским классам, то можно найти, что
class CWebApplication extends CApplication ...

и далее
1. abstract class CApplication extends CModule
2. {
3.     // ... другой код
4. 
5.     public function __construct($config=null)
6.     {
7.         Yii::setApplication($this);
8.         // ... и другой код
9.     }
10. }


В строке 7 вызывается Yii::setApplication($this);, а если посмотреть класс Yii и его родителя YiiBase, то можно увидеть как в итоге инициализируется переменная для конструкции Yii::app()
class Yii extends YiiBase { }

class YiiBase {
{
    private static $_app;
    public static function setApplication($app) // тут определяется значение self::$_app для Yii::app()
    {
        if(self::$_app===null || $app===null)
            self::$_app=$app;
        else
            throw new CException(Yii::t('yii','Yii application can only be created once.'));
    }
    public static function app() // тут можно прочитать значение self::$_app через Yii::app()
    {
        return self::$_app;
    }
}


Теперь понятно, что при инициализации сущности типа CWebApplication эта сущность установилась в приватную переменную YiiBase::$_app, которая доступна через вызов Yii::app()

Это была инициализация

2. Использование Yii::app()->module или Yii::app()->component

Ещё раз посмотри на класс CApplication
abstract class CApplication extends CModule
{
    public function __construct($config=null)
    {
        Yii::setApplication($this);

        // ... другой код

        $this->configure($config);

        // ... другой код
    }
}


В методе configure($config) происходит подготовка данных, которые понадобятся позже для таких вызовов как (к примеру) Yii::app()->db

Давай посмотрим на класс Module, в котором и реализуется логика таких вызовов
abstract class CModule extends CComponent
{
    public function configure($config) // сохранить всё что передали по переменным
    {
        if(is_array($config))
        {
            foreach($config as $key=>$value)
                $this->$key=$value;
        }
    }

    public function __get($name) // если кто-то пытается вызвать несуществующее свойство, например Yii::app()->db
    {
        if($this->hasComponent($name)) // проверить что есть настройки или готовая сущность компоненты
            return $this->getComponent($name); // вернуть сущность компоненты
        else
            return parent::__get($name);
    }

    public function hasComponent($id) // проверить что есть настройки или готовая сущность компоненты
    {
        return isset($this->_components[$id]) || isset($this->_componentConfig[$id]);
    }

    public function getComponent($id,$createIfNull=true) // вернуть сущность компоненты
    {
        if(isset($this->_components[$id])) // если есть готовая сущность компоненты, вернуть её
            return $this->_components[$id];
        elseif(isset($this->_componentConfig[$id]) && $createIfNull)
        {
            $config=$this->_componentConfig[$id];
            if(!isset($config['enabled']) || $config['enabled'])
            {
                unset($config['enabled']);
                $component=Yii::createComponent($config); // создать новую сущность компоненты
                $component->init();
                return $this->_components[$id]=$component; // сохранить и вернуть её
            }
        }
    }
}


Попробуем расшифровать вызов Yii::app()->db

Как я уже показал, Yii::app() - это сущность типа CWebApplication, а у неё нет публичного свойства $db, поэтому PHP вызывает магический метод __get() из базового класса CModule. (Посмотри в документации "магические методы")

Итак, свойства CWebApplication->db не существует и вызывается CModule->__get('db'), далее код считает, что, возможно производится вызов компоненты.

Метод CModule->hasComponent('db') проверяет, что ранее были заданы какие-то настройки указанной через файл конфигурации protected/config/main.php или иным способом. Если так, то то вызывается CModule->getComponent('db', ...), который вызывает Yii::createComponent($config), где $config - это найденные настройки указанной компоненты, например, параметры соединения с базой данных для компоненты db

Посмотрим, что делает Yii::createComponent($config)
class YiiBase
{
    public static function createComponent($config)
    {
        // посмотри сам, тут интересно
    }
}


Cложный метод. Суть его в инстанциировании новой сущности какого-то класса, 5 или 6 способов. Дело в том, что этот метод CModule::createComponent() может быть вызван откуда угодно и у него куча вариантов передачи параметров.

Итак, CModule::createComponent() создал и вернул новую сущность. И на выходе из метода CModule::getComponent() полученная сущность записывается в массив CModule->_components под именем 'db'.

Все последующие вызовы Yii::app()->db будут проверять наличие инстанциинованной сущности в массиве CModule->_components['db'] и использовать его, если он там найден

Заключение:

Вызов Yii::app()->db
А. Динамически инициализирует компоненту, по мере необходимости
Б. Настраивает её данными, которые в частновти можно определить через файл конфигурации protected/config/main.php
В. Инициализируют компоненту только один раз за цикл работы PHP приложения

Надеюсь я понятно изложил механизм ленивой инициализации компонент
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@feldwebel
Через конфиг. Указываешь имя компонента, и в массиве передаешь ему имя класса, а также список предопределённых параметров. Что-то типа
'components' => [
  'someService => [
    'class' => 'path\to\class\ClassName',
    'param1' => 'one',
    'param2' => 'two',
  ]
]

Соответственно, в классе ClassName у тебя должны быть поля public $param1 и $param2, куда просаживаются значения из конфига.

www.bsourcecode.com/yiiframework2/how-to-create-cu...

Там есть ещё некоторая разница между первым и вторым Yii в вызове компонента. В первом Yii::app(), а во втором Yii::$app
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы