Las interfaces son una gran parte del marco de Laravel. También puede llamarlos contratos, así es como se hace referencia a ellos en Github. Esta es la colección de interfaces que utiliza el propio framework Laravel. Al navegar por el repositorio de contratos en Github, puede familiarizarse con toda la API del marco. Todas las clases concretas del marco implementan estas diversas interfaces. De hecho, a la hora de conocer un proyecto de software, no es mala idea empezar navegando por todas las interfaces. Esto le indica qué métodos estarán disponibles para usted.
Repositorio de caché
Como ejemplo, podemos echar un vistazo al repositorio Cache.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | <?php namespace Illuminate\Contracts\Cache; use Closure; interface Repository { /** * Determine if an item exists in the cache. * * @param string $key * @return bool */ public function has($key); /** * Retrieve an item from the cache by key. * * @param string $key * @param mixed $default * @return mixed */ public function get($key, $default = null); /** * Retrieve an item from the cache and delete it. * * @param string $key * @param mixed $default * @return mixed */ public function pull($key, $default = null); /** * Store an item in the cache. * * @param string $key * @param mixed $value * @param \DateTime|int $minutes * @return void */ public function put($key, $value, $minutes); /** * Store an item in the cache if the key does not exist. * * @param string $key * @param mixed $value * @param \DateTime|int $minutes * @return bool */ public function add($key, $value, $minutes); /** * Increment the value of an item in the cache. * * @param string $key * @param mixed $value * @return int|bool */ public function increment($key, $value = 1); /** * Decrement the value of an item in the cache. * * @param string $key * @param mixed $value * @return int|bool */ public function decrement($key, $value = 1); /** * Store an item in the cache indefinitely. * * @param string $key * @param mixed $value * @return void */ public function forever($key, $value); /** * Get an item from the cache, or store the default value. * * @param string $key * @param \DateTime|int $minutes * @param \Closure $callback * @return mixed */ public function remember($key, $minutes, Closure $callback); /** * Get an item from the cache, or store the default value forever. * * @param string $key * @param \Closure $callback * @return mixed */ public function sear($key, Closure $callback); /** * Get an item from the cache, or store the default value forever. * * @param string $key * @param \Closure $callback * @return mixed */ public function rememberForever($key, Closure $callback); /** * Remove an item from the cache. * * @param string $key * @return bool */ public function forget($key); } |
Al observar esta interfaz, podemos ver que cualquier implementación concreta deberá cumplir con los métodos definidos en el contrato. Podemos ver esto sería cosas como has()
, get()
, pull()
, put()
, add()
, increment()
, decrement()
, forever()
, remember()
, sear()
, rememberForever()
, y forget()
. Mientras miramos esto, podemos ver que es una excelente documentación. ¿Necesita saber qué métodos están disponibles para usted en una clase? Basta con mirar la interfaz que implementa. El componente de contratos de Laravel es una colección de interfaces para todas las diversas API que el Framework pone a disposición de los usuarios.
Enlaces de interfaz preconfigurados
Cualquier clase concreta puede implementar cualquier interfaz que desee siempre que se adhiera al contrato. Entonces, ¿cómo sabemos qué implementación obtendremos cuando solicitemos una interfaz específica? Si hace uso de Laravel, por supuesto, querrá hacer uso de la funcionalidad del marco, no solo de sus interfaces. Podemos encontrar el mapeo en registerCoreContainerAliases ()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | public function registerCoreContainerAliases() { $aliases = [ 'app' => ['Illuminate\Foundation\Application', 'Illuminate\Contracts\Container\Container', 'Illuminate\Contracts\Foundation\Application'], 'auth' => ['Illuminate\Auth\AuthManager', 'Illuminate\Contracts\Auth\Factory'], 'auth.driver' => ['Illuminate\Contracts\Auth\Guard'], 'blade.compiler' => ['Illuminate\View\Compilers\BladeCompiler'], 'cache' => ['Illuminate\Cache\CacheManager', 'Illuminate\Contracts\Cache\Factory'], 'cache.store' => ['Illuminate\Cache\Repository', 'Illuminate\Contracts\Cache\Repository'], 'config' => ['Illuminate\Config\Repository', 'Illuminate\Contracts\Config\Repository'], 'cookie' => ['Illuminate\Cookie\CookieJar', 'Illuminate\Contracts\Cookie\Factory', 'Illuminate\Contracts\Cookie\QueueingFactory'], 'encrypter' => ['Illuminate\Encryption\Encrypter', 'Illuminate\Contracts\Encryption\Encrypter'], 'db' => ['Illuminate\Database\DatabaseManager'], 'db.connection' => ['Illuminate\Database\Connection', 'Illuminate\Database\ConnectionInterface'], 'events' => ['Illuminate\Events\Dispatcher', 'Illuminate\Contracts\Events\Dispatcher'], 'files' => ['Illuminate\Filesystem\Filesystem'], 'filesystem' => ['Illuminate\Filesystem\FilesystemManager', 'Illuminate\Contracts\Filesystem\Factory'], 'filesystem.disk' => ['Illuminate\Contracts\Filesystem\Filesystem'], 'filesystem.cloud' => ['Illuminate\Contracts\Filesystem\Cloud'], 'hash' => ['Illuminate\Contracts\Hashing\Hasher'], 'translator' => ['Illuminate\Translation\Translator', 'Symfony\Component\Translation\TranslatorInterface'], 'log' => ['Illuminate\Log\Writer', 'Illuminate\Contracts\Logging\Log', 'Psr\Log\LoggerInterface'], 'mailer' => ['Illuminate\Mail\Mailer', 'Illuminate\Contracts\Mail\Mailer', 'Illuminate\Contracts\Mail\MailQueue'], 'auth.password' => ['Illuminate\Auth\Passwords\PasswordBrokerManager', 'Illuminate\Contracts\Auth\PasswordBrokerFactory'], 'auth.password.broker' => ['Illuminate\Auth\Passwords\PasswordBroker', 'Illuminate\Contracts\Auth\PasswordBroker'], 'queue' => ['Illuminate\Queue\QueueManager', 'Illuminate\Contracts\Queue\Factory', 'Illuminate\Contracts\Queue\Monitor'], 'queue.connection' => ['Illuminate\Contracts\Queue\Queue'], 'redirect' => ['Illuminate\Routing\Redirector'], 'redis' => ['Illuminate\Redis\Database', 'Illuminate\Contracts\Redis\Database'], 'request' => ['Illuminate\Http\Request', 'Symfony\Component\HttpFoundation\Request'], 'router' => ['Illuminate\Routing\Router', 'Illuminate\Contracts\Routing\Registrar'], 'session' => ['Illuminate\Session\SessionManager'], 'session.store' => ['Illuminate\Session\Store', 'Symfony\Component\HttpFoundation\Session\SessionInterface'], 'url' => ['Illuminate\Routing\UrlGenerator', 'Illuminate\Contracts\Routing\UrlGenerator'], 'validator' => ['Illuminate\Validation\Factory', 'Illuminate\Contracts\Validation\Factory'], 'view' => ['Illuminate\View\Factory', 'Illuminate\Contracts\View\Factory'], ]; foreach ($aliases as $key => $aliases) { foreach ($aliases as $alias) { $this->alias($key, $alias); } } } |
Es en este método donde se declaran todos los alias predeterminados y sus asignaciones. Todo esto vuelve al concepto de poner clases en el contenedor y resolver objetos fuera del contenedor de servicios, también conocido como contenedor de IoC. Es debido al contenedor de servicios y al mapeo de alias que puede hacer cosas como las siguientes, que de hecho son exactamente lo mismo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // use a facade Route::get('/', function () { return 'Interfaces are cool!'; }); // pass in key alias of the component app('router')->get('/', function () { return 'Interfaces are cool!'; }); // use the app object with array access // passing in key alias app()['router']->get('/', function () { return 'Interfaces are cool!'; }); // pass in the class itself app('Illuminate\Routing\Router')->get('/', function () { return 'Interfaces are cool!'; }); // pass in the interface or contract app('Illuminate\Contracts\Routing\Registrar')->get('/', function () { return 'Interfaces are cool!'; }); |
Ahora, este es un ejemplo del comportamiento que obtenemos para la clase de enrutamiento integrada de Laravel. Además de toda esta flexibilidad y azúcar sintáctico, el otro gran beneficio de todos estos contratos es que puede cambiar muy fácilmente la implementación predeterminada por otra cosa. Personalmente, hasta ahora no he tenido ninguna razón para tener que hacer esto. Si tiene una situación en la que un requisito particular de un proyecto exige que se utilice una clase particular o implementación de un componente, entonces no está fuera de suerte. Aún puede usar Laravel, solo necesita registrar su propio componente o clase en el contenedor de servicios. Luego, cuando solicite esa interfaz en su código, se le dará la implementación correcta. Aquí es donde puede obtener más información sobre el contenedor de servicios de Laravel. Examinemos un poco más sobre cómo funciona esto.
Registrarse con el contenedor de servicios (poner clases en el contenedor)
A menudo escuchará la frase "registrarse en el contenedor de servicios" o "vincular al contenedor de servicios". Cuando escucho esto, simplemente pienso “Poner clases a disposición de la aplicación a través del contenedor de servicios”. Piense en el contenedor de servicios como una operación bidireccional. Registramos o colocamos clases en el contenedor de servicios, y luego podemos resolver, o sacar del contenedor de servicios, un objeto o instancia de cualquier clase registrada que nos gustaría utilizar. Esto suele suceder con el método register () de un proveedor de servicios. Por ejemplo, aquí está ese mismo registro para el componente Cache de Laravel.
| public function register() { $this->app->singleton('cache', function ($app) { return new CacheManager($app); }); $this->app->singleton('cache.store', function ($app) { return $app['cache']->driver(); }); $this->app->singleton('memcached.connector', function () { return new MemcachedConnector; }); $this->registerCommands(); } |
Podemos ver que es el método singleton () el que hace el trabajo por nosotros. Esto es casi lo mismo que si llamara al método bind () . La diferencia entre los dos es que el patrón singleton le dará al desarrollador una instancia compartida del objeto en todo momento. No se crea un objeto nuevo cada vez. Ahora, en el fragmento de arriba, hay algunas opciones para varias claves. Esos son cache
, cache.store
y memcached.connector
. Cuál se resuelve depende de la configuración de la aplicación. Cuando se realiza una solicitud de una de esas claves, es el cierre el que devolverá una instancia de la clase dada.
Aquí está el método singleton () que es parte de la clase Container.
| public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); } |
Este método en realidad hace uso del método bind () , pero establece el tercer parámetro en true
, lo que indica que se trata de una instancia compartida. Aquí está el método bind () .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | public function bind($abstract, $concrete = null, $shared = false) { $abstract = $this->normalize($abstract); $concrete = $this->normalize($concrete); // If the given types are actually an array, we will assume an alias is being // defined and will grab this "real" abstract class name and register this // alias with the container so that it can be used as a shortcut for it. if (is_array($abstract)) { list($abstract, $alias) = $this->extractAlias($abstract); $this->alias($abstract, $alias); } // If no concrete type was given, we will simply set the concrete type to the // abstract type. This will allow concrete type to be registered as shared // without being forced to state their classes in both of the parameter. $this->dropStaleInstances($abstract); if (is_null($concrete)) { $concrete = $abstract; } // If the factory is not a Closure, it means it is just a class name which is // bound into this container to the abstract type and we will just wrap it // up inside its own Closure to give us more convenience when extending. if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); } $this->bindings[$abstract] = compact('concrete', 'shared'); // If the abstract type was already resolved in this container we'll fire the // rebound listener so that any objects which have already gotten resolved // can have their copy of the object updated via the listener callbacks. if ($this->resolved($abstract)) { $this->rebound($abstract); } } |
Lea los comentarios para comprender cómo funciona todo.
Resolver fuera del contenedor de servicios (sacar objetos del contenedor)
Resolver objetos fuera del contenedor de servicios simplemente significa que cuando va a usar un componente u objeto en el código de su aplicación, es el contenedor de servicios el que maneja la construcción de las dependencias y requisitos necesarios para simplemente entregar un objeto que está listo para hacerlo. trabajo. Es una conveniencia para el desarrollador y ocurre principalmente sin que usted se dé cuenta.
Considere la app()
función auxiliar, que llama a un make()
método en la clase de aplicación.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | if (! function_exists('app')) { /** * Get the available container instance. * * @param string $make * @param array $parameters * @return mixed|\Illuminate\Foundation\Application */ function app($make = null, $parameters = []) { if (is_null($make)) { return Container::getInstance(); } return Container::getInstance()->make($make, $parameters); } } |
Aquí está ese make()
método al que difiere.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * Resolve the given type from the container. * * (Overriding Container::make) * * @param string $abstract * @param array $parameters * @return mixed */ public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($abstract); if (isset($this->deferredServices[$abstract])) { $this->loadDeferredProvider($abstract); } return parent::make($abstract, $parameters); } |
Nuevamente, los comentarios nos ayudan a comprender lo que está sucediendo. En última instancia, el trabajo se realiza mediante el método make () del contenedor, que es el padre de la aplicación. Aquí está el método make ().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public function make($abstract, array $parameters = []) { $abstract = $this->getAlias($this->normalize($abstract)); // If an instance of the type is currently being managed as a singleton we'll // just return an existing instance instead of instantiating new instances // so the developer can keep using the same objects instance every time. if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } $concrete = $this->getConcrete($abstract); // We're ready to instantiate an instance of the concrete type registered for // the binding. This will instantiate the types, as well as resolve any of // its "nested" dependencies recursively until all have gotten resolved. if ($this->isBuildable($concrete, $abstract)) { $object = $this->build($concrete, $parameters); } else { $object = $this->make($concrete, $parameters); } // If we defined any extenders for this type, we'll need to spin through them // and apply them to the object being built. This allows for the extension // of services, such as changing configuration or decorating the object. foreach ($this->getExtenders($abstract) as $extender) { $object = $extender($object, $this); } // If the requested type is registered as a singleton we'll want to cache off // the instances in "memory" so we can return it later without creating an // entirely new instance of an object on each subsequent request for it. if ($this->isShared($abstract)) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); $this->resolved[$abstract] = true; return $object; } |
Podemos ver que después de toda la lógica de este método, finalmente se nos devuelve un Objeto. Este objeto es una instancia de lo que solicitó en el código de su aplicación. En otras palabras, dependiendo de la clave que solicitó, obtendrá el (Objeto | Servicio | Componente) deseado.
Una cosa a tener en cuenta en el make()
método de la clase Application es que hace uso de un getAlias()
método. Así es como puede pasar la clave, el alias, la clase o el contrato al app()
método, y el marco sabe cómo resolver todo correctamente. Aquí está ese mismo método.
| protected function getAlias($abstract) { return isset($this->aliases[$abstract]) ? $this->aliases[$abstract] : $abstract; } |
Podemos ver que hace uso de un operador ternario conveniente para verificar si ya hay un alias registrado y, de ser así, devolverlo. De lo contrario, devolvemos el valor de la variable abstracta $ que probablemente sea la clave. Así es como el contrato o la interfaz se normalizarían en una clave de alias estándar. En otras palabras, si pasa algo como Illuminate \ Contracts \ Routing \ Registrar, el software puede traducir esto a la clave del enrutador .
Resumen de contratos y alias de Laravel
En este tutorial, echamos un vistazo más de cerca al componente de alias y contratos de Laravel. Descubrimos que las interfaces en sí mismas actúan como una excelente documentación para ayudar a un desarrollador a explorar la API expuesta por cualquier clase dada del marco. También investigamos cómo, al crear instancias de objetos a través del contenedor de servicios, hay mucha flexibilidad en cómo hacerlo. Al observar de cerca cómo funciona todo esto, podemos ver cómo las clases Container y Application son realmente la carne y las papas de Laravel. Registramos clases en el contenedor de servicios y extraemos objetos de él según sea necesario. Esta es una base clave de cómo funciona Laravel.
0 Comentarios