Статьи

Как работают фасады Laravel и как их использовать в других местах

Шаблон Facade — это шаблон проектирования программного обеспечения, который часто используется в объектно-ориентированном программировании. Фактически фасад — это класс, заключающий в себе сложную библиотеку, чтобы обеспечить более простой и читаемый интерфейс. Шаблон Facade также можно использовать для предоставления унифицированного и хорошо разработанного API для группы сложных и плохо разработанных API.

Facades diagram

Каркас Laravel имеет особенность, похожую на этот рисунок, также называемую Фасады. В этом уроке мы узнаем, как перенести «Фасады» Ларавеля в другие рамки. Прежде чем мы продолжим, вы должны иметь общее представление о контейнерах Ioc .

Давайте сначала пройдемся по внутренним рабочим частям фасадов Laravel, а затем обсудим, как мы можем адаптировать эту функцию к другим средам.

Фасады в Ларавеле

Фасад Laravel — это класс, который предоставляет статический интерфейс для служб внутри контейнера. Эти фасады, согласно документации, служат прокси для доступа к базовой реализации сервисов контейнера.

Однако в сообществе PHP было много споров об этом именовании . Некоторые утверждают, что этот термин следует изменить, чтобы избежать путаницы среди разработчиков, поскольку он не полностью реализует шаблон Facade. Если это название вас смущает, не стесняйтесь называть его так, как считаете нужным, но учтите, что базовый класс, который мы собираемся использовать, называется Facade в среде Laravel.

Как Фасады реализованы в Laravel

Как вы, наверное, знаете, каждый сервис внутри контейнера имеет уникальное имя. В приложении Laravel для доступа к сервису напрямую из контейнера мы можем использовать метод App::make() или вспомогательную функцию app() .

 <? php App :: make ( 'some_service' )-> methodName (); 

Как упоминалось ранее, Laravel использует классы фасадов, чтобы сделать сервисы доступными для разработчика более читабельным способом. Используя класс фасадов, нам нужно было бы написать следующий код, чтобы сделать то же самое:

 // ... someService :: methodName (); 
 // ... 

В Laravel все сервисы имеют класс фасадов. Эти классы фасадов расширяют базовый класс Facade, который является частью пакета Illuminate/Support . Единственное, что им нужно реализовать, — это метод getFacadeAccessor , который возвращает имя службы внутри контейнера.

В приведенном выше синтаксисе someService ссылается на класс фасада. methodName фактически является методом исходного сервиса в контейнере. Если мы посмотрим на этот синтаксис вне контекста Laravel, это будет означать, что есть класс с именем someService представляющий статический метод с именем methodName() , но это не то, как Laravel реализует этот интерфейс. В следующем разделе мы увидим, как работает базовый класс Facade Laravel за кулисами.

Базовый Фасад

Класс Facade имеет частное свойство с именем $app котором хранится ссылка на контейнер службы. Если нам нужно использовать фасады вне Laravel, мы должны явно установить контейнер с помощью setFacadeApplication() . Мы вернемся к этому в ближайшее время.

Внутри базового класса фасада был реализован магический метод __callStatic для обработки вызова статических методов, которые на самом деле не существуют. Когда мы вызываем статический метод для класса фасада Laravel, вызывается метод __callStatic, потому что класс фасада не реализовал этот метод. Следовательно, __callStatic извлекает соответствующую услугу из контейнера и вызывает метод против него.

Вот реализация метода __callStatic в базовом классе фасада:

 <? php // ... 
 /** * Handle dynamic, static calls to the object. * * @param string $method * @param array $args * @return mixed */ 
     public   static   function  __callStatic ( $method ,  $args ) 
     { $instance =   static :: getFacadeRoot (); 

         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 ); 
         } 
     } 

В приведенном выше методе getFacadeRoot() получает сервис из контейнера.

Анатомия Фасадного Класса

Каждый класс фасадов расширяет базовый класс. Единственное, что нам нужно реализовать, — это getFacadeAccessor() . Этот метод не делает ничего, кроме как возвращает имя службы в контейнере.

 <? php namespace   App \Facades ; 

 use   Illuminate \Support\Facades\Facade as   BaseFacade ; 

 class   SomeServiceFacade   extends   BaseFacade   { 

     /** * Get the registered name of the component. * * @return string */ 
     protected   static   function  getFacadeAccessor ()   {   return   'some.service' ;   } 

 } 

Псевдонимы

Поскольку фасады Laravel являются PHP-классами, нам нужно импортировать их, прежде чем мы сможем их использовать. Благодаря пространствам имен и поддержке автозагрузки в PHP все классы автоматически загружаются, когда мы обращаемся к ним по полному имени. PHP также поддерживает псевдонимы классов с помощью директивы use :

 use   App \Facades\SomeServiceFacade SomeServiceFacade : SomeMethod (); 

Тем не менее, мы должны делать это в каждом сценарии, где нам нужен этот конкретный класс фасадов. Laravel обрабатывает псевдонимы фасадов по-своему, используя загрузчик псевдонимов.

Как Laravel совмещает фасады

Все псевдонимы хранятся в массиве aliases в app.php конфигурации app.php , который находится в каталоге /config .

Если мы посмотрим на массив, то увидим, что каждое имя псевдонима сопоставляется с полным именем класса. Это означает, что мы можем использовать любое имя для класса фасадов:

 // .. 
 'aliases'   =>   [ 
     // ... 
     'FancyName'   =>   'App\Facades\SomeServiceFacade' , 
 ], 

Хорошо, теперь давайте посмотрим, как Laravel использует этот массив для наложения классов фасадов. На этапе начальной загрузки Laravel использует службу с именем AliasLoader которая является частью пакета Illuminate\Foundation . AliasLoader принимает массив aliases , выполняет AliasLoader по всем элементам и создает очередь функций __autoload используя PHP spl_autoload_register . Каждая функция __autoload отвечает за создание псевдонима для соответствующего класса фасада с помощью PHP-функции class_alias .

В результате нам не придется импортировать и псевдонимы классов перед их использованием, как мы обычно делаем с use директивы use . Поэтому всякий раз, когда мы пытаемся получить доступ к несуществующему классу, PHP будет проверять очередь __autoload для получения правильного автозагрузчика. К тому времени AliasLoader уже зарегистрировал все функции __autoload . Каждый автозагрузчик принимает причудливое имя класса и разрешает его в исходное имя класса в соответствии с массивом aliases . Наконец, он создает псевдоним для этого класса. Рассмотрим следующий вызов метода:

 <? php // FancyName is resolved to App\Facades\SomeServiceFacade according to the aliases array 

 FancyName :: someMethod () 

За кулисами FancyName в App\Facades\SomeServiceFacade .

Использование фасадов в других каркасах

Хорошо, теперь, когда мы хорошо понимаем, как Laravel обрабатывает свои фасады и псевдонимы, мы можем адаптировать фасадный подход Laravel к другим средам. В этой статье мы собираемся использовать фасады в рамках Silex. Однако вы можете адаптировать эту функцию и к другим платформам, следуя той же концепции.

Silex имеет свой собственный контейнер, так как он расширяет Pimple . Чтобы получить доступ к сервису внутри контейнера, мы можем использовать объект $app следующим образом:

 <? php $app [ 'some.service' ]-> someMethod () 

С помощью классов фасадов мы можем предоставить статический интерфейс для наших сервисов Silex. Кроме того, мы можем использовать сервис AliasLoader для создания значимых псевдонимов для этих фасадов. В результате мы сможем реорганизовать приведенный выше код следующим образом:

 <? php SomeService :: someMethod (); 

Требования

Чтобы использовать базовый класс фасадов, нам нужно установить пакет Illuminate\Support с помощью composer:

 composer require illuminate\support 

Этот пакет также содержит и другие сервисы, но пока нам просто нужен базовый класс Facade.

Создание Фасадов

Чтобы создать фасад для сервиса, нам просто нужно расширить базовый класс Facade и реализовать метод getFacadeAccessor .

В этом уроке давайте оставим все фасады под src/Facades path. Например, для службы с именем some.service , фасадный класс будет выглядеть следующим образом:

 <? php namespace   App \Facades use   Illuminate \Support\Facades\Facade ; 

 class   SomeServiceFacade   extends   Facade   { 

     /** * Get the registered name of the component. * * @return string */ 
     protected   static   function  getFacadeAccessor ()   {   return   'some.service' ;   } 

 } 

Обратите внимание, что мы разместили пространство имен в классе app\facades .

Остается только установить контейнер приложения на класс фасада. Как указывалось ранее, когда мы вызываем метод в статическом контексте для класса фасада, запускается __callStatic . _callStatic использует данные, возвращаемые getFacadeAccessor() чтобы идентифицировать службу внутри контейнера и пытается извлечь ее. Когда мы используем базовый класс Facade вне Laravel, объект контейнера не устанавливается автоматически, поэтому нам нужно будет сделать это вручную.

Для этого базовый класс фасада предоставляет метод с именем setFacadeApplication который устанавливает контейнер приложения для этого класса фасада.

В нашем файле app.php нам нужно добавить следующий код:

 <? php Illumiante \Support\Facade :: setFacadeApplication ( $app ); 

Это установит контейнер для всех фасадов, которые расширяют базовый класс фасадов.

Теперь вместо доступа к сервису из нашего контейнера мы можем использовать только что созданный класс фасадов, который также позволяет нам вызывать все методы в статическом контексте.

Реализация псевдонимов

Чтобы AliasLoader псевдоним классов фасадов, мы будем использовать AliasLoader который мы представили ранее. Aliasloader является частью Aliasloader illuminate\foundation . Мы можем либо загрузить весь пакет, либо просто позаимствовать код и сохранить его в виде файла.

Если вы просто хотите скопировать исходный файл, я предлагаю вам сохранить его в src/Facades . Вы можете использовать пространство имен класса AliasLoader на основе архитектуры вашего проекта.

Для этого примера давайте скопируем код и поместим его в пространство имен в app/facades .

Создание массива псевдонимов

Давайте создадим файл в нашей директории config именем aliases.php и поместим в него привязки псевдонима-фасада следующим образом:

 <? php return   [ 
     'FancyName'   =>   'App\Facades\SomeService' , 
 ]; 

FancyName — это имя, которое мы хотим использовать вместо App\Facades\SomeService .

Регистрация псевдонимов

AliasLoader — это одиночный сервис. Чтобы создать или получить экземпляр загрузчика псевдонимов, нам нужно вызвать метод getInstance с массивом псевдонимов в качестве аргумента. Наконец, чтобы зарегистрировать все псевдонимы, нам нужно вызвать его метод register .

Снова в файле app.php добавьте следующий код:

 <? php // ... $aliases =   require  __DIR__ .   '/../../config/aliases.php' ; 
 App \Facades\AliasLoader :: getInstance ( $aliases )-> register (); 

И это все, что нужно сделать! Теперь мы можем использовать сервис так:

 <? php FancyName :: methodName (); 

Завершение

Класс Facade должен только реализовать метод getFacadeAccessor который возвращает имя службы внутри контейнера. Поскольку мы используем эту функцию вне среды Laravel, мы должны явно установить контейнер службы, используя метод setFacadeApplication() .

Чтобы ссылаться на классы фасада, мы должны либо использовать полностью определенные имена классов, либо импортировать их с use директивы use PHP. В качестве альтернативы, мы можем следовать способу псевдонимов фасадов Laravel, используя загрузчик псевдонимов.

Вопросов? Комментарии? Оставь их ниже! Спасибо за прочтение!