In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly introduces "Laravel container, control inversion and dependency injection instance analysis". In daily operation, I believe many people have doubts about Laravel container, control inversion and dependency injection instance analysis. The editor consulted all kinds of data and sorted out simple and useful operation methods. I hope it will be helpful to answer the doubts of "Laravel container, control inversion and dependency injection instance analysis". Next, please follow the editor to study!
With the increasing scale of applications, the dependency relationship between objects is becoming more and more complex, and the degree of coupling is getting higher and higher, and multiple dependencies between objects often occur. For such a large and complex application, any modification may affect the whole body, which causes a lot of trouble for the later maintenance of the application.
In order to solve the problem of high coupling between objects, the idea of inversion of control (IoC) was born in . The so-called inversion of control is a design principle in object-oriented programming, which aims to reduce the degree of coupling between codes. In Laravel, control inversion is achieved through dependency injection (DI).
The basic idea of control inversion is to decouple the dependency relationship between objects with the help of IoC container. After the introduction of the IoC container, the control of all objects is handed over to the IoC container, and the IoC container becomes the core of the whole system, gluing all objects together to play a role. Containers in Laravel play this role.
⒈ container
What calls a container, in Laravel, refers to the\ Illuminate\ Foundation\ Application object, which is created by the Laravel framework at startup.
# public/index.php$app = require_once _ _ DIR__.'/../bootstrap/app.php';# bootstrap/app.php$app = new Illuminate\ Foundation\ Application ($_ ENV ['APP_BASE_PATH']?? Dirname (_ _ DIR__))
in the process of creating a container, Laravel will also do some basic binding and service registration to the container. Laravel first binds the container instance to app and Illuminate\ Container\ Container; then Laravel registers the underlying service provider with the container instance, including events, logs, and routing service providers; finally, Laravel registers the framework core class with its corresponding alias to the container instance.
/ / namespace Illuminate\ Foundation\ Applicationpublic function _ construct ($basePath = null) {if ($basePath) {$this- > setBasePath ($basePath);} $this- > registerBaseBindings (); $this- > registerBaseServiceProviders (); $this- > registerCoreContainerAliases ();} protected function registerBaseBindings () {static::setInstance ($this); $this- > instance ('app', $this); $this- > instance (Container::class, $this) / *... * /} protected function registerBaseServiceProviders () {$this- > register (new EventServiceProvider ($this)); $this- > register (new LogServiceProvider ($this)); $this- > register (new RoutingServiceProvider ($this)) } public function registerCoreContainerAliases () {foreach (['app' = > [self::class,\ Illuminate\ Contracts\ Container\ Container::class,\ Illuminate\ Contracts\ Foundation\ Application::class,\ Psr\ Container\ ContainerInterface::class], / *... * /' db' = > [\ Illuminate\ Database\ DatabaseManager::class,\ Illuminate\ Database\ ConnectionResolverInterface::class], 'db.connection' = > [\ Illuminate\ Database\ Connection::class] \ Illuminate\ Database\ ConnectionInterface::class], / *... * / 'request' = > [\ Illuminate\ Http\ Request::class,\ Symfony\ Component\ HttpFoundation\ Request::class],' router' = > [\ Illuminate\ Routing\ Router::class,\ Illuminate\ Contracts\ Routing\ Registrar::class,\ Illuminate\ Contracts\ Routing\ BindingRegistrar::class] / *... * /] as $key = > $aliases) {foreach ($aliases as $alias) {$this- > alias ($key, $alias) } / / namespace Illuminate\ Container\ Containerpublic function alias ($abstract, $alias) {if ($alias = $abstract) {throw new LogicException ("[{$abstract}] is aliased to itself.");} $this- > aliases [$alias] = $abstract; $this- > abstractAliases [$abstract] [] = $alias;}
After has completed these three basic steps of registration, we can easily access the object instances that have been registered in the container. For example, you can access the container itself directly through $app ['app'] or $app [' Illuminate\ Container\ Container'], and you can also access database connections directly through $app ['db'].
Registration of ⒉ service providers and access to services
Register a service provider
registers the underlying service provider during container creation, which is done by calling the register () method.
/ / namespace Illuminate\ Foundation\ Applicationpublic function register ($provider, $force = false) {if (($registered = $this- > getProvider ($provider)) & &! $force) {return $registered;} if (is_string ($provider)) {$provider = $this- > resolveProvider ($provider);} $provider- > register () If (property_exists ($provider, 'bindings')) {foreach ($provider- > bindings as $key = > $value) {$this- > bind ($key, $value);}} if (property_exists ($provider,' singletons')) {foreach ($provider- > singletons as $key = > $value) {$this- > singleton ($key, $value);} $this- > markAsRegistered ($provider) If ($this- > isBooted ()) {$this- > bootProvider ($provider);} return $provider;}
Laravel first determines whether the specified service provider is registered in the container (by calling the getProvider () method). If the specified service provider is already registered in the container and the registration operation is not mandatory, then directly return the registered service provider.
If does not meet the above conditions, then Laravel will begin to register the service provider. At this point, if the parameter is passed as a string, Laravel defaults to the class name of the service provider and instantiates it (via the resolveProvider () method). After that, the register () method defined by the service provider is called to register. In the case of a log service provider, the method body of its register () method is as follows
/ / namespace Illuminate\ Log\ LogServiceProviderpublic function register () {$this- > app- > singleton ('log', function ($app) {return new LogManager ($app);});}
The function of the register () method is to register the Illuminate\ Log\ LogManager object with the container in singleton mode. After registration, an item is added to the container's $bindings property.
$app- > bindings ['log'] = [' concrete' = > 'Illuminate\ Log\ LogManager {# 162}', 'shared' = > true,]
if the service provider itself also defines the $bindings property and the $singletons property, then Laravel also calls the corresponding bind () method and singleton () method to complete the registration of these service provider custom bindings.
After , Laravel marks the service provider as registered, and then invokes the boot () method defined by the service provider to start the service provider (provided that the application is started).
When registers bindings with the container, there are two methods, bind () and singleton (), which differ only in whether the registered binding is singleton mode, that is, whether the shared property is true.
/ / namespace Illuminate\ Container\ Containerpublic function singleton ($abstract, $concrete = null) {$this- > bind ($abstract, $concrete, true);} public function bind ($abstract, $concrete = null, $shared = false) {/ / remove the old binding $this- > dropStaleInstances ($abstract); if (is_null ($concrete)) {$concrete = $abstract;} if (! $concrete instanceof Closure) {if (! Is_string ($concrete)) {throw new TypeError (self::class.'::bind (): Argument # 2 ($concrete) must be of type Closure | string | null');} $concrete = $this- > getClosure ($abstract, $concrete);} $this- > bindings [$abstract] = compact ('concrete',' shared'); if ($this- > resolved ($abstract)) {$this- > rebound ($abstract) } protected function getClosure ($abstract, $concrete) {return function ($container, $parameters = []) use ($abstract, $concrete) {if ($abstract = = $concrete) {return $container- > build ($concrete);} return $container- > resolve ($concrete, $parameters, $raiseEvents = false);};}
still uses the log service provider as an example, which registers in singleton mode at registration time, and the $concrete parameter is a closure. Before the binding begins, Laravel first deletes the old binding. Since $concrete is a closure at this time, Laravel does nothing but store the binding information in the $bindings attribute.
Access servic
after the registration of the service provider is completed, we can access the service in the same way as the database connection mentioned above. Still take the log service as an example, we can access the log service through $app ['log']. In addition, in Laravel, we can also use facade to access the service, for example, we can call Illuminate\ Support\ Facades\ Log::info () to log.
/ / namespace Illuminate\ Support\ Facades\ Logclass Log extends Facade {protected static function getFacadeAccessor () {return 'log';}} / / namespace Illuminate\ Support\ Facades\ Facadepublic static function _ _ callStatic ($method, $args) {$instance = static::getFacadeRoot (); / *. * / return $instance- > $method (. $args);} public static function getFacadeRoot () {return static::resolveFacadeInstance (static::getFacadeAccessor ()) } protected static function resolveFacadeInstance ($name) {if (is_object ($name)) {return $name;} if (isset (static::$resolvedInstance [$name)) {return static::$resolvedInstance [$name];} if (static::$app) {return static::$resolvedInstance [$name] = static::$app [$name];}}
When logs through static calls, it will first access the magic method _ _ callStatic () in Facade. The first thing of this method is to parse the corresponding service instance of facade, and then call the method under the service instance to perform the corresponding function. A getFacadeAccessor () method is defined in each facade, which returns a tag. In the log service, this tag is the key of the log service provider's closure in the container's $bindings attribute. That is, you end up with $app ['log'] in the facade way.
so why can objects / services registered in the container be accessed through associative arrays? Illuminate\ Container\ Container implements ArrayAccess and defines the OffsetGet () method, while Illuminate\ Foundation\ Application inherits Container, and $app is an object instantiated by Application, so Container's OffsetGet () method is accessed when accessing objects registered in the container through an associative array. The make () method of Container is called in the OffsetGet () method, and the resolve () method is called in the make () method. The resolve () method eventually parses and returns the corresponding object.
/ / namespace Illuminate\ Containerpublic function offsetGet ($key) {return $this- > make ($key);} public function make ($abstract, array $parameters = []) {return $this- > resolve ($abstract, $parameters);} protected function resolve ($abstract, $parameters = [], $raiseEvents = true) {/ *. * / $this- > with [] = $parameters; if (is_null ($concrete)) {$concrete = $this- > getConcrete ($abstract) } if ($this- > isBuildable ($concrete, $abstract)) {$object = $this- > build ($concrete);} else {$object = $this- > make ($concrete);} / *. * / $this- > resolved [$abstract] = true; array_pop ($this- > with); return $object } protected function getConcrete ($abstract) {if (isset ($this- > bindings [$abstract])) {return $this- > bindings [$abstract] ['concrete'];} return $abstract;} protected function isBuildable ($concrete, $abstract) {return $concrete = = $abstract | $concrete instanceof Closure;} public function build ($concrete) {if ($concrete instanceof Closure) {return $concrete ($this, $this- > getLastParameterOverride ()) } / *... * /} protected function getLastParameterOverride () {return count ($this- > with)? End ($this- > with): [];}
needs to note here that when parsing the log service instance through $app ['log'], the $concrete in the resolve () method gets a closure, which causes the isBuildable () method to return true, so Laravel will directly call the build () method. Since $concrete is a closure at this time, the closure function is executed directly in the build () method, and the LogManager instance is returned.
⒊ request processing
after the basic binding and service registration is complete, the container is created successfully and $app is returned. Laravel then registers the kernel (including the Http kernel and the Console kernel) and exception handling with the container. Then Laravel starts processing the request.
/ / namespace bootstrap/app.php$app- > singleton (Illuminate\ Contracts\ Http\ Kernel::class, App\ Http\ Kernel::class); $app- > singleton (Illuminate\ Contracts\ Console\ Kernel::class, App\ Console\ Kernel::class); $app- > singleton (Illuminate\ Contracts\ Debug\ ExceptionHandler::class, App\ Exceptions\ Handler::class); / / public/index.php$kernel = $app- > make (Illuminate\ Contracts\ Http\ Kernel::class) $response = $kernel- > handle ($request = Request::capture ())-> send (); $kernel- > terminate ($request, $response)
Before starts processing the request, Laravel first parses the Http kernel object $kernel, that is, the object instantiated by App\ Http\ Kernel. App\ Http\ Kernel inherits Illuminate\ Foundation\ Kernel, so $kernel actually calls the handle () method in Illuminate\ Foundation\ Kernel.
Namespace Illuminate\ Foundation\ Httpuse Illuminate\ Contracts\ Debug\ ExceptionHandlerpublic function handle ($request) {try {$request- > enableHttpMethodParameterOverride (); $response = $this- > sendRequestThroughRouter ($request);} catch (Throwable $e) {$this- > reportException ($e); $response = $this- > renderException ($request, $e);} $this- > app ['events']-> dispatch (new RequestHandled ($request, $response)); return $response } / / report error protected function reportException (Throwable $e) {$this- > app [ExceptionHandler:: class]-> report ($e);} / / render error message protected function renderException ($request, Throwable $e) {return $this- > app [ExceptionHandler:: class]-> render ($request, $e);}
If there are any exceptions or errors in the handle () method during the processing of the request, Laravel will call the exception handling object that has been registered in the container to report the exception and render the returned information.
After has successfully created the container, Laravel registers the binding between Illuminate\ Contracts\ Debug\ ExceptionHandler and App\ Exceptions\ Handler in the container, so Laravel actually calls the methods in App\ Exceptions\ Handler when handling exceptions. In the actual development process, developers can customize the report () and render () methods in App\ Exceptions\ Handler according to their own needs.
In PHP 7, `Exception` and `Error` are two different types, but they both inherit `Throwable`, so the `Throwable` object is captured in the `Throwable` method.
Before begins to process the request, Laravel performs some boot boots, including loading environment variables, configuration information, and so on, which play a very important role in the running of Laravel.
/ / namespace Illuminate\ Foundation\ Http\ Kernelprotected $bootstrappers = [\ Illuminate\ Foundation\ Bootstrap\ LoadEnvironmentVariables::class,\ Illuminate\ Foundation\ Bootstrap\ LoadConfiguration::class,\ Illuminate\ Foundation\ Bootstrap\ HandleExceptions::class,\ Illuminate\ Foundation\ Bootstrap\ RegisterFacades::class,\ Illuminate\ Foundation\ Bootstrap\ RegisterProviders::class,\ Illuminate\ Foundation\ Bootstrap\ BootProviders::class,]; protected function sendRequestThroughRouter ($request) {/ *. * / $this- > bootstrap () / *... * /} public function bootstrap () {if (! $this- > app- > hasBeenBootstrapped ()) {$this- > app- > bootstrapWith ($this- > bootstrappers ());}} / / namespace Illuminate\ Foundation\ Applicationpublic function bootstrapWith (array $bootstrappers) {$this- > hasBeenBootstrapped = true; foreach ($bootstrappers as $bootstrapper) {$this ['events']-> dispatch (' bootstrapping:'$bootstrapper, [$this]) $this- > make ($bootstrapper)-> bootstrap ($this); $this ['events']-> dispatch (' bootstrapped:'. $bootstrapper, [$this]);}}
can see from the code that the boot startup process is actually a call to the bootstrap () method in each class. Where:
LoadEnvironmentVariables is used to load environment variables
LoadConfiguration is used to load configuration files in the config directory
HandleExceptions is used to set the error reporting level of PHP and the corresponding exception and error handling functions. In addition, it also sets PHP's program to terminate the execution function.
/ / namespace Illuminate\ Foundation\ Bootstrap\ HandleExceptionspublic function bootstrap (Application $app) {/ *... * / $this- > app = $app; error_reporting (- 1); set_error_handler ([$this, 'handleError']); set_exception_handler ([$this,' handleException']); register_shutdown_function ([$this, 'handleShutdown']) / *. * /} public function handleError ($level, $message, $file =', $line = 0, $context = []) {if (error_reporting () & $level) {/ *. * / throw new ErrorException ($message, 0, $level, $file, $line);} public function handleException (Throwable $e) {/ *. * / $this- > getExceptionHandler ()-> report ($e) / *. * /} public function handleShutdown () {if (! Is_null ($error = error_get_last () & & $this- > isFatal ($error ['type'])) {$this- > handleException ($this- > fatalErrorFromPhpError ($error, 0));}} protected function getExceptionHandler () {return $this- > app- > make (\ Illuminate\ Contracts\ Debug\ ExceptionHandler::class);}
can see from the above code that although exceptions, errors, and program termination handlers are defined in HandleExceptions, in either case, the methods in App\ Exceptions\ Handler are finally called to handle exceptions or errors.
The role of RegisterFacades is to register configuration files and custom alias classes in third-party packages, and a very important role is to set the $app property for the Illuminate\ Support\ Facades\ Facade class.
/ / namespace Illuminate\ Foundation\ Bootstrap\ RegisterFAcadespublic function bootstrap (Application $app) {Facade::clearResolvedInstances (); Facade::setFacadeApplication ($app); AliasLoader::getInstance (array_merge ($app- > make ('config')-> get (' app.aliases', []), $app- > make (PackageManifest::class)-> aliases ())-> register ();}
& emsp when we ask the service registered in the container via facade, the static::$app used by Facade when parsing the service instance in the container is set at this time.
The role of RegisterProviders is to register configuration files and service providers defined in third-party packages
/ / namespace Illuminate\ Foundation\ Bootstrap\ RegisterProviderspublic function bootstrap (Application $app) {$app- > registerConfiguredProviders ();} public function registerConfiguredProviders () {$providers = Collection::make ($this- > make ('config')-> get (' app.providers'))-> partition (function ($provider) {return strpos ($provider, 'Illuminate\\) = 0;}) $providers- > splice (1,0, [$this- > make (PackageManifest::class)-> providers ()]); (new ProviderRepository ($this, new Filesystem, $this- > getCachedServicesPath ())-> load ($providers- > collapse ()-> toArray ());}
During the actual registration process of , Laravel will register in the order of Laravel framework service provider > third-party package service provider > developer-defined service provider.
BootProviders invokes the boot () method of the service provider that is already registered in the container sequentially (provided that the boot () method defined by the service provider)
After the boot is complete, the Laravel begins to process the request. The first thing to do is to apply the global middleware to the request. After that, Laravel will distribute the request to the appropriate route for processing. Before processing, you need to find the corresponding route object Illuminate\ Routing\ Route according to request. In Laravel, in addition to global middleware, there are some middleware that only act on specific routes or routing packets, and these middleware will be applied to request. After all this work is done, the routing object begins to execute the code and complete the request.
/ / namespace Illuminate\ Foundation\ Http\ Kernelprotected function sendRequestThroughRouter ($request) {/ *... * / return (new Pipeline ($this- > app))-> send ($request)-> through ($this- > app- > shouldSkipMiddleware ()? []: $this- > middleware)-> then ($this- > dispatchToRouter ()) } protected function dispatchToRouter () {return function ($request) {$this- > app- > instance ('request', $request); return $this- > router- > dispatch ($request);};} / namespace Illuminate\ Routing\ Routerpublic function dispatch (Request $request) {$this- > currentRequest = $request; return $this- > dispatchToRoute ($request);} public function dispatchToRoute (Request $request) {return $this- > runRoute ($request, $this- > findRoute ($request)) } protected function runRoute (Request $request, Route $route) {/ *... * / return $this- > prepareResponse ($request, $this- > runRouteWithinStack ($route, $request)) } protected function runRouteWithinStack (Route $route, Request $request) {/ *... * / return (new Pipeline ($this- > container))-> send ($request)-> through ($middleware)-> then (function ($request) use ($route) {return $this- > prepareResponse ($request) $route- > run () });} ⒋ dependency injection
When routes in Laravel are registered, the action can be either a controller method or a closure. However, no matter which form it is, you need to pass parameters, and passing parameters will encounter the need for dependency injection.
When the Route object executes the run () method, the controller method call or the closure function call is made according to the type of action. But both methods eventually need to parse the parameters, and if class is used in the parameters, dependency injection is required.
/ / namespace Illuminate\ Routing\ Routerpublic function run () {$this- > container = $this- > container?: new Container; try {if ($this- > isControllerAction ()) {return $this- > runController ();} return $this- > runCallable ();} catch (HttpResponseException $e) {return $e-> getResponse () }} protected function runController () {return $this- > controllerDispatcher ()-> dispatch ($this, $this- > getController (), $this- > getControllerMethod ());} protected function runCallable () {/ *... * / return $callable (. Array_values ($this- > resolveMethodDependencies ($this- > parametersWithoutNulls (), new ReflectionFunction ($callable) } / / namespace Illuminate\ Routing\ ControllerDispatcherpublic function dispatch (Route $route, $controller, $method) {$parameters = $this- > resolveClassMethodDependencies ($route- > parametersWithoutNulls (), $controller, $method); / *. * / / namespace Illuminate\ Routing\ RouteDependencyResolverTraitprotected function resolveClassMethodDependencies (array $parameters, $instance, $method) {/ *. * / return $this- > resolveMethodDependencies ($parameters, new ReflectionMethod ($instance, $method)) } public function resolveMethodDependencies (array $parameters, ReflectionFunctionAbstract $reflector) {/ *... * / foreach ($reflector- > getParameters () as $key = > $parameter) {$instance = $this- > transformDependency ($parameter, $parameters, $skippableValue); / *. * /} return $parameters;} protected function transformDependency (ReflectionParameter $parameter, $parameters, $skippableValue) {$className = Reflector::getParameterClassName ($parameter) If ($className & &! $this- > alreadyInParameters ($className, $parameters)) {return $parameter- > isDefaultValueAvailable ()? Null: $this- > container- > make ($className);} return $skippableValue;}
During the execution of , Laravel first gets the parameter list through reflection (ReflectionMethod for controller methods and ReflectionFunction for closure functions). After getting the parameter list, Laravel still uses reflection to determine the parameter type one by one. No special handling is required if the parameter type is PHP's built-in type, but if the parameter is not a PHP built-in type, you need to use reflection to resolve the specific type of the parameter. After parsing the specific type of the parameter, it is immediately determined whether the object of that type already exists in the parameter list. If it does not exist and the default value is not set for the type, then you need to create an instance of the type through the container.
still needs to use the resolve () method to create an instance of the specified class through the container. The case of parsing a closure function using the resolve () method has been described earlier, so here the value describes the case where class is instantiated.
/ / namespace Illuminate\ Container\ Containerpublic function build ($concrete) {/ *... * / try {$reflector = new ReflectionClass ($concrete);} catch (ReflectionException $e) {throw new BindingResolutionException ("Target class [$concrete] does not exist.", 0, $e);} if (! $reflector- > isInstantiable ()) {return $this- > notInstantiable ($concrete);} $this- > buildStack [] = $concrete; $constructor = $reflector- > getConstructor () If (is_null ($constructor)) {array_pop ($this- > buildStack); return new $concrete;} $dependencies = $constructor- > getParameters (); try {$instances = $this- > resolveDependencies ($dependencies);} catch (BindingResolutionException $e) {array_pop ($this- > buildStack); throw $e;} array_pop ($this- > buildStack); return $reflector- > newInstanceArgs ($instances) } protected function resolveDependencies (array $dependencies) {$results = []; foreach ($dependencies as $dependency) {if ($this- > hasParameterOverride ($dependency)) {$results [] = $this- > getParameterOverride ($dependency); continue;} $result = is_null (Util::getParameterClassName ($dependency))? $this- > resolvePrimitive ($dependency): $this- > resolveClass ($dependency) If ($dependency- > isVariadic ()) {$results = array_merge ($results, $result);} else {$results [] = $result;}} return $results;}
When the container instantiates class, it still obtains the basic information of class through reflection. For some class that cannot be instantiated (such as interface, abstract class), Laravel will throw an exception; otherwise, Laravel will continue to get information about the constructor of class. For class with no constructor, it means that these class do not need additional dependencies when instantiating, and can be instantiated directly through new; otherwise, the parameter list information of the constructor is still parsed through reflection, and then the class used in these parameter lists is instantiated one by one. After all the class in these parameter lists are instantiated, the preparation for creating a class through the container is completed. At this time, the container can successfully create an instance of the specified class and inject it into the controller method or closure.
At this point, the study on "Laravel container, control inversion and dependency injection instance analysis" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.