Laravelのcontrollerのactionが呼ばれるまでを覗きたい
トレースを見ながらやると良いかも
controllerのメソッドが呼ばれるまで
public/index.php
からみてく。
kernelをコンテナに登録。
bootstrap/app.php
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
/*
|--------------------------------------------------------------------------
| Bind Important Interfaces
|--------------------------------------------------------------------------
|
| Next, we need to bind some important interfaces into the container so
| we will be able to resolve them when needed. The kernels serve the
| incoming requests to this application from both the web and CLI.
|
*/
$app->singleton(
Illuminate\Contracts\Http\Kernel::class,
App\Http\Kernel::class
);
public/index.php
$app = require_once __DIR__.'/../bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
$request = Illuminate\Http\Request::capture()
);
$kernel->handleを実行してる
vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
/**
* Handle an incoming HTTP request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function handle($request)
{
try {
$request->enableHttpMethodParameterOverride();
$response = $this->sendRequestThroughRouter($request);
} catch (Exception $e) {
$this->reportException($e);
$response = $this->renderException($request, $e);
} catch (Throwable $e) {
$this->reportException($e = new FatalThrowableError($e));
$response = $this->renderException($request, $e);
}
$this->app['events']->dispatch(
new Events\RequestHandled($request, $response)
);
return $response;
}
ここのthenをみる。
/**
* Send the given request through the middleware / router.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
protected function sendRequestThroughRouter($request)
{
$this->app->instance('request', $request);
Facade::clearResolvedInstance('request');
$this->bootstrap();
return (new Pipeline($this->app))
->send($request)
->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
->then($this->dispatchToRouter());
}
thenが何やってるのか、わからん。けど引数はdestinationだっって。
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);
return $pipeline($this->passable);
}
dispatchToRouterこっちをおうぞ
/**
* Get the route dispatcher callback.
*
* @return \Closure
*/
protected function dispatchToRouter()
{
return function ($request) {
$this->app->instance('request', $request);
return $this->router->dispatch($request);
};
}
vendor/laravel/framework/src/Illuminate/Routing/Router.php
/**
* Dispatch the request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
public function dispatch(Request $request)
{
$this->currentRequest = $request;
return $this->dispatchToRoute($request);
}
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
/**
* Return the response for the given route.
*
* @param \Illuminate\Http\Request $request
* @param \Illuminate\Routing\Route $route
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
*/
protected function runRoute(Request $request, Route $route)
{
$request->setRouteResolver(function () use ($route) {
return $route;
});
$this->events->dispatch(new Events\RouteMatched($route, $request));
return $this->prepareResponse($request,
$this->runRouteWithinStack($route, $request)
);
}
protected function runRouteWithinStack(Route $route, Request $request)
{
$shouldSkipMiddleware = $this->container->bound('middleware.disable') &&
$this->container->make('middleware.disable') === true;
$middleware = $shouldSkipMiddleware ? [] : $this->gatherRouteMiddleware($route);
return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $this->prepareResponse(
$request, $route->run()
);
});
}
vendor/laravel/framework/src/Illuminate/Routing/Route.php
/**
* Run the route action and return the response.
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
controllerDispatcher()でcontrollerDispatcherクラスが呼ばれて、その中のdispatchを実行してる。
controllerとmethodの名を引数。
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
引数のcontrollerはコンテナ経由で実行してる。
/**
* Get the controller instance for the route.
*
* @return mixed
*/
public function getController()
{
if (! $this->controller) {
$class = $this->parseControllerCallback()[0];
$this->controller = $this->container->make(ltrim($class, '\\'));
}
return $this->controller;
}
/**
* Get the dispatcher for the route's controller.
*
* @return \Illuminate\Routing\Contracts\ControllerDispatcher
*/
public function controllerDispatcher()
{
if ($this->container->bound(ControllerDispatcherContract::class)) {
return $this->container->make(ControllerDispatcherContract::class);
}
return new ControllerDispatcher($this->container);
}
vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php
/**
* Dispatch a request to a given controller and method.
*
* @param \Illuminate\Routing\Route $route
* @param mixed $controller
* @param string $method
* @return mixed
*/
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}
ここで実行された!!!!!
controllerには、callActionが実装されてる。
vendor/laravel/framework/src/Illuminate/Routing/Controller.php
/**
* Execute an action on the controller.
*
* @param string $method
* @param array $parameters
* @return \Symfony\Component\HttpFoundation\Response
*/
public function callAction($method, $parameters)
{
return call_user_func_array([$this, $method], $parameters);
}
ここの$parametersって何がはいってるんだろうっていうのを、次の段落でみていく
parameterのタイプヒントはどうやってるのかな?を探る
parameterを探してく、。
さっきも出した、
public function dispatch(Route $route, $controller, $method)
{
$parameters = $this->resolveClassMethodDependencies(
$route->parametersWithoutNulls(), $controller, $method
);
if (method_exists($controller, 'callAction')) {
return $controller->callAction($method, $parameters);
}
return $controller->{$method}(...array_values($parameters));
}
vendor/laravel/framework/src/Illuminate/Routing/RouteDependencyResolverTrait.php
{
/**
* Resolve the object method's type-hinted dependencies.
*
* @param array $parameters
* @param object $instance
* @param string $method
* @return array
*/
protected function resolveClassMethodDependencies(array $parameters, $instance, $method)
{
if (! method_exists($instance, $method)) {
return $parameters;
}
return $this->resolveMethodDependencies(
$parameters, new ReflectionMethod($instance, $method)
);
}
controllerにmethodが存在しなかったら、そのまま返す。
もし存在してたら、resolveMethodDependencies
が依存を解決してparametersをまた返す。
この関数が何やってるかは追うのキツそうなのでシカトする。
ちょっとみた感じ、parameterのデフォルト値が存在しなかったら、コンテナでmakeされて、それをparameterに戻してあげる??みたいなことをやっそう。array_sliceとかしてるねえ。
じゃあその元の値は$route->parametersWithoutNulls()
null値を排除。
public function parametersWithoutNulls()
{
return array_filter($this->parameters(), function ($p) {
return ! is_null($p);
});
}
ここで取得してる。
public function parameters()
{
if (isset($this->parameters)) {
return $this->parameters;
}
throw new LogicException('Route is not bound.');
}
$this->parametersは、どこで登録してるねん。
次はどこで登録してるのかみてく
Route.php
vendor/laravel/framework/src/Illuminate/Routing/Route.php
のparameters
っていうプロパティのセットをおう。
最終的には、以下の関数が呼ばれてるみたい。なのでこれがどこで呼ばれてるかってのを見る。
/**
* Set a parameter to the given value.
*
* @param string $name
* @param mixed $value
* @return void
*/
public function setParameter($name, $value)
{
$this->parameters();
$this->parameters[$name] = $value;
}
んで、見つけたので順を追ってみていく。
これの登録はmiddlewareでやってるくさい
app/Http/Kernel.php
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php
<?php
namespace Illuminate\Routing\Middleware;
use Closure;
use Illuminate\Contracts\Routing\Registrar;
class SubstituteBindings
{
/**
* The router instance.
*
* @var \Illuminate\Contracts\Routing\Registrar
*/
protected $router;
/**
* Create a new bindings substitutor.
*
* @param \Illuminate\Contracts\Routing\Registrar $router
* @return void
*/
public function __construct(Registrar $router)
{
$this->router = $router;
}
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
$this->router->substituteBindings($route = $request->route());
$this->router->substituteImplicitBindings($route);
return $next($request);
}
}
substituteBindingsの実態
vendor/laravel/framework/src/Illuminate/Routing/Router.php
こっちは素通り?
/**
* Substitute the route bindings onto the route.
*
* @param \Illuminate\Routing\Route $route
* @return \Illuminate\Routing\Route
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function substituteBindings($route)
{
foreach ($route->parameters() as $key => $value) {
if (isset($this->binders[$key])) {
$route->setParameter($key, $this->performBinding($key, $value, $route));
}
}
return $route;
}
こっち!!!!!!!!!!
/**
* Substitute the implicit Eloquent model bindings for the route.
*
* @param \Illuminate\Routing\Route $route
* @return void
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public function substituteImplicitBindings($route)
{
ImplicitRouteBinding::resolveForRoute($this->container, $route);
}
こっからは行けそう。
$route->signatureParameters
で引数を配列で取得。それをforeachで回して、タイプヒントからコンテナ経由で実態を取得。
それを$route->setParameter
でセットしてる。
vendor/laravel/framework/src/Illuminate/Routing/ImplicitRouteBinding.php
/**
* Resolve the implicit route bindings for the given route.
*
* @param \Illuminate\Container\Container $container
* @param \Illuminate\Routing\Route $route
* @return void
*
* @throws \Illuminate\Database\Eloquent\ModelNotFoundException
*/
public static function resolveForRoute($container, $route)
{
$parameters = $route->parameters();
foreach ($route->signatureParameters(UrlRoutable::class) as $parameter) {
if (! $parameterName = static::getParameterName($parameter->name, $parameters)) {
continue;
}
$parameterValue = $parameters[$parameterName];
if ($parameterValue instanceof UrlRoutable) {
continue;
}
$instance = $container->make($parameter->getClass()->name);
if (! $model = $instance->resolveRouteBinding($parameterValue)) {
throw (new ModelNotFoundException)->setModel(get_class($instance), [$parameterValue]);
}
$route->setParameter($parameterName, $model);
}
}
一番したにsetParameterいましたね。
foreachしてる配列はここで取得。
vendor/laravel/framework/src/Illuminate/Routing/Route.php
/**
* Get the parameters that are listed in the route / controller signature.
*
* @param string|null $subClass
* @return array
*/
public function signatureParameters($subClass = null)
{
return RouteSignatureParameters::fromAction($this->action, $subClass);
}
vendor/laravel/framework/src/Illuminate/Routing/RouteSignatureParameters.php
/**
* Extract the route action's signature parameters.
*
* @param array $action
* @param string|null $subClass
* @return array
*/
public static function fromAction(array $action, $subClass = null)
{
$parameters = is_string($action['uses'])
? static::fromClassMethodString($action['uses'])
: (new ReflectionFunction($action['uses']))->getParameters();
return is_null($subClass) ? $parameters : array_filter($parameters, function ($p) use ($subClass) {
return $p->getClass() && $p->getClass()->isSubclassOf($subClass);
});
}
/**
* Get the parameters for the given class / method by string.
*
* @param string $uses
* @return array
*/
protected static function fromClassMethodString($uses)
{
[$class, $method] = Str::parseCallback($uses);
if (! method_exists($class, $method) && is_callable($class, $method)) {
return [];
}
return (new ReflectionMethod($class, $method))->getParameters();
}
public static function parseCallback($callback, $default = null)
{
return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
}
routeでusesを指定してたら簡単ですね。
Route::get('/', [
'as' => 'top.index',
'uses' => 'TopController@index',
]);
ただ、下みたいな、上に当てはまらない場合はどうしてるんだろうね。
Route::get('/', 'TopController@index')->name('top.index');
ちゃんちゃん。
おまけ
俺はてっきり、こいつから呼ばれてるのかと思ってた
vendor/laravel/framework/src/Illuminate/Container/Container.php
/**
* Call the given Closure / class@method and inject its dependencies.
*
* @param callable|string $callback
* @param array $parameters
* @param string|null $defaultMethod
* @return mixed
*/
public function call($callback, array $parameters = [], $defaultMethod = null)
{
return BoundMethod::call($this, $callback, $parameters, $defaultMethod);
}
匂わせなメソッドも通っていたので。
vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php
/**
* Determine if the given string is in Class@method syntax.
*
* @param mixed $callback
* @return bool
*/
protected static function isCallableWithAtSign($callback)
{
return is_string($callback) && strpos($callback, '@') !== false;
}
「'@'」で検索するとcontrollerのactionで通ってそうな関数が見つかる、、。なんやねんこいつら。
ついでのリンク