まえがき
##追記
輪読会にてバシバシに間違っている点指摘もらったので、5/7訂正、追記を行いました。
前提
https://qiita.com/carrotRakko/items/3e6d19a7bd9427ab6479
上記の記事を読んであることを前提に話を進めます
また、筆者はプログラミング初心者であり、前回、前々回ではsingletonやfacade,kernel等が出てきて概念というよりは用語自体に**???**となったレベルです
##背景
上記の記事にもありますがLarevel輪読会のレジュメです
前回Illuminate\Container\Container
のresolve()
メソッドが呼び出されている所までたどり着きました。
#今回の目標
今回はその後を追っていき、実際にweb.php
で
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
実際にこの処理で結果的に何が行われるかまで追えれば最高です~~(全く自信ありませんが)~~
##resolve()は何をやっているのか
resolve()
メソッドは以下です
/**
* Resolve the given type from the container.
*
* @param string $abstract
* @param array $parameters
* @param bool $raiseEvents
* @return mixed
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
protected function resolve($abstract, $parameters = [], $raiseEvents = true)
{
$abstract = $this->getAlias($abstract);
$concrete = $this->getContextualConcrete($abstract);
$needsContextualBuild = ! empty($parameters) || ! is_null($concrete);
// 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;
if (is_null($concrete)) {
$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;
}
if ($raiseEvents) {
$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;
}
よくわからないので上から追っていくと
$abstract = $this->getAlias($abstract);
は
/**
* Get the alias for an abstract if available.
*
* @param string $abstract
* @return string
*/
public function getAlias($abstract)
{
if (! isset($this->aliases[$abstract])) {
return $abstract;
}
return $this->getAlias($this->aliases[$abstract]);
}
よりaliases
は空配列なのでaliases
のキーにrouter
はないのでそのまま$abstract
を返します
/**
* Get the contextual concrete binding for the given abstract.
*
* @param string $abstract
* @return \Closure|string|array|null
*/
protected function getContextualConcrete($abstract)
{
if (! is_null($binding = $this->findInContextualBindings($abstract))) {
return $binding;
}
// Next we need to see if a contextual binding might be bound under an alias of the
// given abstract type. So, we will need to check if any aliases exist with this
// type and then spin through them and check for contextual bindings on these.
if (empty($this->abstractAliases[$abstract])) {
return;
}
foreach ($this->abstractAliases[$abstract] as $alias) {
if (! is_null($binding = $this->findInContextualBindings($alias))) {
return $binding;
}
}
}
今回はどこにも該当しないのでnull
が帰ってきます
ここでresolve()
に戻ると$needsContextualBuild
はfalse
となります
次のif文はすっ飛ばして$this->with[] = $parameters;
に行きますがこれは
$parameters
が空配列なので特に関係ないです
よって次の
if (is_null($concrete)) {
$concrete = $this->getConcrete($abstract);
}
が何をしてるのかを追います。getConcrete
は
/**
* Get the concrete type for a given abstract.
*
* @param string $abstract
* @return mixed
*/
protected function getConcrete($abstract)
{
// 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
???何それってなります
なのでbindings
に何かを入れているとこを探します
##bindings
とはなんぞや
前回の内容を踏まえて最初から追っていくと
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
ここっぽいなと思い追ってみることにしました。new
してるので__construct
を追います
/**
* Create a new Illuminate application instance.
*
* @param string|null $basePath
* @return void
*/
public function __construct($basePath = null)
{
if ($basePath) {
$this->setBasePath($basePath);
}
$this->registerBaseBindings();
$this->registerBaseServiceProviders();
$this->registerCoreContainerAliases();
}
ここから全部をみるべきなんですが、今回router
登録しているのはこれです
$this->registerBaseServiceProviders();
そして同クラス内にあります
/**
* Register all of the base service providers.
*
* @return void
*/
protected function registerBaseServiceProviders()
{
$this->register(new EventServiceProvider($this));
$this->register(new LogServiceProvider($this));
$this->register(new RoutingServiceProvider($this));
}
というわけでまず、new
されている方を追いましょうか。router
関係のものがコンストラクトされているのはこれですnew RoutingServiceProvider($this)
use Illuminate\Routing\RoutingServiceProvider;
とあるので該当クラスに飛びます
register
を追いましょう。これも同クラス内にあって
/**
* Register the service provider.
*
* @return void
*/
public function register()
{
$this->registerRouter();
$this->registerUrlGenerator();
$this->registerRedirector();
$this->registerPsrRequest();
$this->registerPsrResponse();
$this->registerResponseFactory();
$this->registerControllerDispatcher();
}
今回router
を登録しているのは
$this->registerRouter();
これです。同クラス内にあって
/**
* Register the router instance.
*
* @return void
*/
protected function registerRouter()
{
$this->app->singleton('router', function ($app) {
return new Router($app['events'], $app);
});
}
(一部ですが)bindings
の中身がわかったとこで
getConcrete
に戻ると
if (isset($this->bindings[$abstract])) {
return $this->bindings[$abstract]['concrete'];
}
とあるので先ほど作成した連想配列の $absteact
に['concrete']
を追加したものを返します次は
// 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;
}
以上のようにsingleton
は
ここで$concrete
)はClosure
のインスタンスなので
$object = $this->build($concrete);
となります
よってbuild
を追います
これまた、同クラス内にあって一部抜粋すると
// 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());
}
とあるので
$object = $concrete($this, $this->getLastParameterOverride())
となります
よって今回の$concrete
にはrouter
という文字列が入っているので
先ほど登録したクロージャが呼ばれて
$object
にはnew Router
が返されます。
以上よりweb.php
にてRoute
が呼ばれたときは
Router
のインスタンスが返されることが無事追い切れました。
次回以降は
・Illuminate\Routing\RouteServiceProvider クラスの registerRouter() メソッドで $this->app->singleton() の第2引数に渡している Closure が実行されたときの処理
・routes/web.php で Route::get(‘/’, function () { … }) が実行されたとき、すなわち Illuminate\Routing\Router クラスのシングルトンの get(‘/’, function () { … }) メソッドが実行されたときの処理
を追っていきます。
##singleton
って何してるの?
少し深掘りになりますがsingleton
について追ってみました
Illuminate\Foundation\Application
を追ってみてもsingleton
は無かったので
継承元のIlluminate\Container\Container
に戻ってみました。
/**
* Register a shared binding in the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @return void
*/
public function singleton($abstract, $concrete = null)
{
$this->bind($abstract, $concrete, true);
}
ありました。
というわけでbind
を追います
/**
* Register a binding with the container.
*
* @param string $abstract
* @param \Closure|string|null $concrete
* @param bool $shared
* @return void
*/
public function bind($abstract, $concrete = null, $shared = false)
{
$this->dropStaleInstances($abstract);
// If no concrete type was given, we will simply set the concrete type to the
// abstract type. After that, the concrete type to be registered as shared
// without being forced to state their classes in both of the parameters.
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);
}
}
と、同クラス上にありました。とりあえずここです
// 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);
}
これまた同クラス上にありました
/**
* Get the Closure to be used when building a type.
*
* @param string $abstract
* @param string $concrete
* @return \Closure
*/
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
);
};
}
$this->bindings[$abstract] = compact('concrete', 'shared');
です。
これにより $this->bindings[$abstract]
は'concrete'
と'shared'
をキーと変数に持った連想配列が入っています。
#後書き
PHPの知見が浅い筆者の当日の発表はボロボロでしたが、エンジニアの先輩方に教えてもらい
結果的には一通り追い切ることができました。感謝感謝です。
ここが輪読会のメリットであり面白いところなのではと感じました。
精力的にやっていきたいですね。