머리말
솔직히 사장님께서 처음으로 라라벨 프레임워크 매뉴얼을 읽어보라고 하신 날 아침, 저 같은 쓰레기가 라라벨을 접해본 적이 없어서 정말 절박했습니다. 진입장벽이 조금 높은 것은 사실이지만, 그래도 계속 읽어봐야 할 것 같습니다(아직 이해하지 못하고 사용하지 못한 것들이 많지만).
회사 프로젝트의 코드를 기반으로 점차 Laravel에 익숙해졌지만 여전히 종속성 주입, ORM 작업, 사용자 인증 및 내 프로젝트의 비즈니스 로직과 관련된 기타 작업과 같은 일부 피상적인 기능에 머물었습니다. 일부 아키텍처는 서비스 제공자, 서비스 컨테이너, 미들웨어, Redis 등 처음부터 설정해야 하는 기본적인 것들을 실제로 운영한 적이 없어서(사장님이 이미 처음부터 해놓으셨기 때문에) 설명서를 읽을 때 여전히 약간 혼란 스럽습니다.
그래서 시간이 나면 포럼을 뒤지고 구글에서 Laravel의 핵심 아키텍처에 대한 소개와 사용법을 많이 찾아봅니다. (설명서를 읽은 후 읽어보면 이해하기 더 쉬울 것입니다.) 좋은 웹사이트에서 가르치는 것은 라라벨 핵심 아키텍처에 대한 학습을 기록하는 것이라고 생각합니다.
웹사이트 주소: https://laraweb.net/ 이 웹사이트는 초보자에게 매우 적합한 내용이라고 생각합니다. 결국 일본어는 직접 번역하면 이해하기 쉽습니다.
서비스 컨테이너에 대하여
설명서에는 다음과 같이 소개되어 있습니다. Laravel 서비스 컨테이너는 클래스 종속성을 관리하고 종속성 주입을 수행하는 데 사용되는 도구입니다. 종속성 주입이라는 멋진 용어는 본질적으로 클래스의 종속성이 생성자 또는 경우에 따라 "setter" 메서드를 통해 클래스에 "주입"된다는 것을 의미합니다. . . . . . (무슨 뜻인지 정말 모르겠습니다.)
서비스 컨테이너는 클래스(서비스)의 인스턴스화를 관리하는 데 사용되는 메커니즘입니다. 서비스 컨테이너 사용법 바로보기
1. 서비스 컨테이너에 클래스 등록(bind)
$this->app->bind('sender','MailSender'); //$this->app成为服务容器。
2. 서비스 컨테이너에서 클래스 생성(make)
$sender = $this->app->make('sender'); //从服务容器($this->app)创建一个sender类。 在这种情况下,将返回MailSender的实例。
서비스 컨테이너의 가장 간단한 사용법입니다. 다음은 서비스 컨테이너 상세 소개입니다
(주요 참고자료 : https://www.cnblogs.com/lyzg/...)
laravel 컨테이너의 기본 이해
처음에는 index.php 파일이 Composer를 로딩합니다. 정의된 오토로더를 생성한 다음 bootstrap/app.php 스크립트에서 Laravel 애플리케이션의 인스턴스를 검색합니다. Laravel 자체에서 수행하는 첫 번째 작업은 애플리케이션/서비스 컨테이너의 인스턴스를 생성하는 것입니다.
$app = new Illuminate\Foundation\Application( dirname(__DIR__) );
이 파일은 요청이 laravel 프레임워크에 도달할 때마다 실행됩니다. 생성된 $app은 요청 수명 주기 전반에 걸쳐 고유한 laravel 프레임워크의 애플리케이션 인스턴스입니다. Laravel은 인증, 데이터베이스, 캐시, 메시지 큐 등을 포함한 많은 서비스를 제공합니다. $app는 컨테이너 관리 도구로서 거의 모든 서비스 구성 요소의 인스턴스화와 인스턴스의 수명주기 관리를 담당합니다. 특정 기능을 완료하기 위해 서비스 클래스가 필요한 경우 컨테이너를 통해 이 유형의 인스턴스만 확인하면 됩니다. 최종 사용 관점에서 볼 때 laravel 컨테이너에 의한 서비스 인스턴스 관리에는 주로 다음 측면이 포함됩니다.
- 서비스 바인딩 및 구문 분석
- 서비스 공급자 관리
- 별칭의 역할
- 종속성 주입
먼저 코드에서 컨테이너 인스턴스를 가져오는 방법을 이해하고 위의 네 가지 키를 알아보세요.
코드에서 컨테이너 인스턴스를 가져오는 방법
첫 번째는
$app = app(); //app这个辅助函数定义在\vendor\laravel\framework\src\Illuminate\Foundation\helper.php 里面,,这个文件定义了很多help函数,并且会通过composer自动加载到项目中。 所以,在参与http请求处理的任何代码位置都能够访问其中的函数,比如app()。
두 번째는
Route::get('/', function () { dd(App::basePath()); return ''; }); //这个其实是用到Facade,中文直译貌似叫门面,在config/app.php中, 有一节数组aliases专门用来配置一些类型的别名,第一个就是'App' => Illuminate\Support\Facades\App::class, 具体的Google一下laravel有关门面的具体实现方式
입니다. 세 번째 옵션은 서비스 제공업체에서 $this->app을 직접 사용하는 것입니다. 서비스 제공자는 나중에 소개할 예정인데 지금은 소개한 뿐입니다. 서비스 제공자 클래스는 laravel 컨테이너에 의해 인스턴스화되기 때문에 이러한 클래스는 $app 인스턴스 속성을 정의하는 IlluminateSupportServiceProvider에서 상속됩니다.
abstract class ServiceProvider { protected $app;
laravel이 서비스 제공자를 인스턴스화할 때 이 $app 위에 laravel 컨테이너 인스턴스를 주입합니다. 따라서 서비스 제공자에서는 app() 함수나 App Facade를 사용하지 않고도 $this->$app을 통해 항상 laravel 컨테이너 인스턴스에 액세스할 수 있습니다.
서비스 바인딩 및 파싱 이해 방법
간단히 말해서 컨테이너는 객체를 저장하는 용도이므로 객체 저장과 객체 제거 과정이 있어야 합니다. 이 객체를 저장하고 검색하는 과정을 Laravel에서는 서비스 바인딩 및 구문 분석이라고 합니다.
app()->bind('service', 'this is service1'); app()->bind('service2', [ 'hi' => function(){ //say hi } ]); class Service { } app()->bind('service3', function(){ return new Service(); });
바인딩의 특별한 경우인 싱글톤 바인딩 싱글톤도 있습니다(세 번째 매개변수는 true입니다). 컨테이너에 바인딩된 개체는 한 번만 구문 분석되며 후속 호출은 동일한 인스턴스를 반환합니다
public function singleton($abstract, $concrete = null) { $this->bind($abstract, $concrete, true); }
在绑定的时候,我们可以直接绑定已经初始化好的数据(基本类型、数组、对象实例),还可以用匿名函数来绑定。用匿名函数的好处在于,这个服务绑定到容器以后,并不会立即产生服务最终的对象,只有在这个服务解析的时候,匿名函数才会执行,此时才会产生这个服务对应的服务实例。
实际上,当我们使用singleton,bind方法以及数组形式,(这三个方法是后面要介绍的绑定的方法),进行服务绑定的时候,如果绑定的服务形式,不是一个匿名函数,也会在laravel内部用一个匿名函数包装起来,这样的话, 不轮绑定什么内容,都能做到前面介绍的懒初始化的功能,这对于容器的性能是有好处的。这个可以从bind的源码中看到一些细节:
if (! $concrete instanceof Closure) { $concrete = $this->getClosure($abstract, $concrete); }
看看bind的底层代码
public function bind($abstract, $concrete = null, $shared = false)
第一个参数服务绑定名称,第二个参数服务绑定的结果(也就是闭包,得到实例),第三个参数就表示这个服务是否在多次解析的时候,始终返回第一次解析出的实例(也就是单例绑定singleton)。
服务绑定还可以通过数组的方式:
app()['service'] = function(){ return new Service(); };
绑定大概就这些,接下来看解析,也就是取出来用
$service= app()->make('service');
这个方法接收两个参数,第一个是服务的绑定名称和服务绑定名称的别名,如果是别名,那么就会根据服务绑定名称的别名配置,找到最终的服务绑定名称,然后进行解析;第二个参数是一个数组,最终会传递给服务绑定产生的闭包。
看源码:
/** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed */ public function make($abstract, array $parameters = []) { return $this->resolve($abstract, $parameters); } /** * Resolve the given type from the container. * * @param string $abstract * @param array $parameters * @return mixed */ protected function resolve($abstract, $parameters = []) { $abstract = $this->getAlias($abstract); $needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($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]) && ! $needsContextualBuild) { return $this->instances[$abstract]; } $this->with[] = $parameters; $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); } else { $object = $this->make($concrete); } // 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) && ! $needsContextualBuild) { $this->instances[$abstract] = $object; } $this->fireResolvingCallbacks($abstract, $object); // Before returning, we will also set the resolved flag to "true" and pop off // the parameter overrides for this build. After those two things are done // we will be ready to return back the fully constructed class instance. $this->resolved[$abstract] = true; array_pop($this->with); return $object; }
第一步:
$needsContextualBuild = ! empty($parameters) || ! is_null( $this->getContextualConcrete($abstract) );
该方法主要是区分,解析的对象是否有参数,如果有参数,还需要对参数做进一步的分析,因为传入的参数,也可能是依赖注入的,所以还需要对传入的参数进行解析;这个后面再分析。
第二步:
if (isset($this->instances[$abstract]) && ! $needsContextualBuild) { return $this->instances[$abstract]; }
如果是绑定的单例,并且不需要上面的参数依赖。我们就可以直接返回 $this->instances[$abstract]。
第三步:
$concrete = $this->getConcrete($abstract); ... /** * Get the concrete type for a given abstract. * * @param string $abstract * @return mixed $concrete */ protected function getConcrete($abstract) { if (! is_null($concrete = $this->getContextualConcrete($abstract))) { return $concrete; } // If we don't have a registered resolver or concrete for the type, we'll just // assume each type is a concrete name and will attempt to resolve it as is // since the container should be able to resolve concretes automatically. if (isset($this->bindings[$abstract])) { return $this->bindings[$abstract]['concrete']; } return $abstract; }
这一步主要是先从绑定的上下文找,是不是可以找到绑定类;如果没有,则再从 $bindings[] 中找关联的实现类;最后还没有找到的话,就直接返回 $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); } else { $object = $this->make($concrete); } ... /** * Determine if the given concrete is buildable. * * @param mixed $concrete * @param string $abstract * @return bool */ protected function isBuildable($concrete, $abstract) { return $concrete === $abstract || $concrete instanceof Closure; }
如果之前找到的 $concrete 返回的是 $abstract 值,或者 $concrete 是个闭包,则执行 $this->build($concrete),否则,表示存在嵌套依赖的情况,则采用递归的方法执行 $this->make($concrete),直到所有的都解析完为止。
$this->build($concrete)
/** * Instantiate a concrete instance of the given type. * * @param string $concrete * @return mixed * * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function build($concrete) { // If the concrete type is actually a Closure, we will just execute it and // hand back the results of the functions, which allows functions to be // used as resolvers for more fine-tuned resolution of these objects. // 如果传入的是闭包,则直接执行闭包函数,返回结果 if ($concrete instanceof Closure) { return $concrete($this, $this->getLastParameterOverride()); } // 利用反射机制,解析该类。 $reflector = new ReflectionClass($concrete); // If the type is not instantiable, the developer is attempting to resolve // an abstract type such as an Interface of Abstract Class and there is // no binding registered for the abstractions so we need to bail out. if (! $reflector->isInstantiable()) { return $this->notInstantiable($concrete); } $this->buildStack[] = $concrete; // 获取构造函数 $constructor = $reflector->getConstructor(); // If there are no constructors, that means there are no dependencies then // we can just resolve the instances of the objects right away, without // resolving any other types or dependencies out of these containers. // 如果没有构造函数,则表明没有传入参数,也就意味着不需要做对应的上下文依赖解析。 if (is_null($constructor)) { // 将 build 过程的内容 pop,然后直接构造对象输出。 array_pop($this->buildStack); return new $concrete; } // 获取构造函数的参数 $dependencies = $constructor->getParameters(); // Once we have all the constructor's parameters we can create each of the // dependency instances and then use the reflection instances to make a // new instance of this class, injecting the created dependencies in. // 解析出所有上下文依赖对象,带入函数,构造对象输出 $instances = $this->resolveDependencies( $dependencies ); array_pop($this->buildStack); return $reflector->newInstanceArgs($instances); }
上面这一段有关解析make的介绍主要参考:
coding01:看 Laravel 源代码了解 Container(https://juejin.cn/post/6844903608052367368)
这一篇就主要学习laravel的服务容器以及它的绑定和解析,虽然目前能力无法对框架源码每一个地方都弄懂,但通过这几篇优秀的文章,我将其进行整理结合,这过程让我更加理解laravel的一些核心内容,起码别人问起来我多多少少能说出一些,这就是进步。
后面有关服务提供者,依赖注入,中间件等内容的学习将放在后续的博客文章中,欢迎看看我的其他博客文章:https://zgxxx.github.io/。
以上相关知识的引用已经注明出处,若有侵权,请联系我,感谢这些优秀文章的作者