Source code reading and analysis – PHP laravel

Time:2022-5-25

Source code reading and analysis – PHP laravel

How to read the source code? What’s the use of reading the source code?

This problem will begin to be contacted and will be paid attention to and understood intentionally for the program working for about two years; Most people think that reading the source code is to better cope with the interview and find a higher paying job; In addition to the principle and effect of program design, there are also better ideas and effects of program design; For development, you can also learn good code writing planning from reading the source code, improve your code quality, as well as the ability to analyze and repair bugs, function expansion and so on.

So I began to try to read the source code.

01. Problems encountered; Muddled, with a muddled face;

In reading the source code, novices are often confused and dizzy when they don’t master the skills. They feel that they go around (Jingle cats are dizzy)

Source code reading and analysis - PHP laravel

Novices generally look at the source code in this way; The following is a code [IOC registers and parses and starts the simplified process of obtaining data using DB through app loading]

<?php
class Config
{
  public function get($key)
  {
      Return "get configuration information."$ key;
  }
}
class Db
{
  protected $app

  function __construct(App $app)
  {
    $this->app = $app;
  }
  public function connection()
  {
    $this->configuration()
  }
  public function configuration()
  {
    $connections = $this->app->make("config").get("database.connections")
    // ...
  }
  public function select()
  {
    $this->connection()

    Return "query data";
  }
}
class Ioc
{
  protected $bindings = [];

  public function make($key)
  {
    return $this->bindings[$key];
  }
  public function bind($key, $object)
  {
    $this->bindings[$key] = $object;
  }
}
class App extends Ioc
{
  public function __construct()
  {
    $this->registerCoreIocBinding();
  }
  public function run()
  {
    // .. Skip resolution of controller / closure
    $db = $this->make(db).select();
  }
  protected function registerCoreIocBinding()
  {
    foreach ([
      "config" => Config::class,
      "db"  => Db::class,
    ] as $key => $value) {
      $this->bind($key, $value);
    }
  }
}

//Call
$app = new App();
$app->run()
?>

In the above code, an IOC object is defined to provide the core method of container registration and parsing. App or application in the framework inherits the IOC object and will register the core container object in the framework (using the bind method);

In the example code, two containers are provided: config and DB objects; When initializing in dB, it is required to pass the app, and when obtaining the configuration information in connection, it is necessary to parse the config object through the app to obtain the relevant configuration information;

The above code is relatively simple and has no particularly complex design; Let’s learn how to read the source code through the above code;

———————————–Glorious dividing line—————————————-

Many comrades don’t know what to analyze when analyzing the source code, and then start with the index PHP method in a little bit by bit, point to the end, find a circle, and then From start to give up (I’m not talking about you, if you’re also commenting, give me a 666)

For example, in the above code; Habitual comrades will start fromnew App()Start, rightnew App() -> app.__construct() -> app.registerCoreIocBinding -> ioc.bind()Click once for the method of the whole link;

When finished, start the second method$app->run()And then start clicking once on each method of its link; from$app - > run() to app make()Alas, at this time, I found myself in my make method again (there will be little doubt here). When I finished here, I finally understood it and entered itDb.select()Click to continueDb.select()->Db.connection()->Db.configuration()->app.make()Good guy, then he went back

Source code reading and analysis - PHP laravel

02. Skill summary

There are skills in reading the source code, and the skills are mainly;

  1. Determine the goal first
  2. Pay attention to the right amount of source code exploration, and remember not to go to the dead point
  3. You must see the method name and comments;
  4. See inheritance, initialization, special methods and related features from unknown origin
  5. Skillfully using the printing function provided by the program
  6. Record process and scheduling chain
  7. Skip what you won’t, don’t know, or try to guess

Among them, points 1, 2 and 3 are very important. Basically, they can be applied to the first reading of many relevant languages. They are also applicable, especially when the code cannot be run;

Among them, 4 and 5 need to have a certain understanding of the program. When you know how to run a language and the basic mechanism, you can also try to read it

Detail split specific ideas

  1. Determine the goal first

This is very critical!!! The first point, because many comrades know that I want to see the source code, click and return to the origin, and finally I was dizzy by myself;

The key problem is that you don’t know what you want to see. There is no goal in the function realization process. Therefore, it is very important to clarify the goal;

  1. Pay attention to the right amount of source code exploration, and remember not to go to the dead point

This step is mainly designed for the process of exploration. When looking at it for the first time, you often click on one method when you see one method, and then click on another method when you find another method;

I also forget who I am, where I am, and why I want to see the source code? (666 for comments if yes)

For the source code reading, we must pay attention to the appropriate amount, and the source code reading needs to be based on the first point as the main line; There are often many branches in the main line, and more branches will confuse you. Here, it is recommended to click three levels at most for each method, when analyzing the method for the first time;

Level 3 mainly refers to, for example, method a, which contains (B, C, d) and other methods; The review of method a is regarded as level 1, the second level is the click reading of methods B, C and D, and the third level is the review of methods called internally by methods B, C and D;

When you have a general understanding of methods B, C and D in method a, you can distinguish the main line according to the information brought by the reading of its source code, so as to avoid sticking to the dead point; Avoid the magic of love

  1. You must see the method name and comments;

For this, most comrades pay little attention to open source programs (I used to be the same);

First, we need to understand; The encapsulation of a method (not to mention the God of ox, horse and snake in the method) has its key functions and functions;

Excellent open source programs often convey the function and relevant description of the method to the reader in the form of annotation and method name;

Of course, it doesn’t rule out that there are people who don’t know English very well, and it’s not a big problem. Translation arrangements

Combined with the second step, when we click a method, we can first look at the annotation of the method and translate the other method name to understand what it does; For some methods, we only need to look at the name of the method. When we don’t understand it, we can further Click to look down. When we look down, we also need to focus on an appropriate amount;

  1. See inheritance, initialization, special methods and related features from unknown origin

In the source code, what hinders our understanding of the program is these special existence;

There are also variable calls in methods, such as global / local attributes. Note that methods are also included; The most troublesome problems are mainly those “methods and attributes of unknown origin”

For example, there are facade special settings in the PHP framework, but the methods in the facade objects are really empty. Where do the methods come from???

This depends on some initialization, inheritance, special methods and related features of the language; In the facade, the method is mainly through__call()The implementation of relevant magic methods is different in different languages, so we need to be vigilant against special existence to find itUnknown originSource of

  1. Skillfully using the printing function provided by the program

This is a good skill for debugging program code in my opinion; In the reading of program source code, it is mentioned in the fourth point that there will be attributes and methods of unknown origin;

Then we can try to determine the source of the corresponding attribute or the function of its unknown method with the help of printing method;

You can use the print function to print the parameter information of the relevant called variables, so as to observe and explore the source behind the attributes, but this also depends on the language, which is not particularly omnipotent;

But one thing is very useful, that is, we can use printing to understand the execution of the program and the change process of parameters

For example:

function A($a){
  var_ dump($a); //  Before treatment

  //Treatment of a

  var_ dump($a); //  After treatment
}
  1. Record process and scheduling chain

This is mainly to facilitate their own review;

In the open source program and open source framework, the overall scheduling chain is generally very complex. Therefore, for the function and scheduling process of each method, it is suggested to draw a flow chart and relevant instructions, so as to facilitate review and warmth in the future

For point 6, it is often necessary to cooperate with an overall scheduling process. This example The author is too lazy to draw;

  1. Skip what you won’t, don’t know or try to guess for a while

When reading the source code, you often encounter the situation of not understanding, and you can’t understand why it is like this or what to do;

At this time, my suggestion is that if you still don’t understand through the previous six steps, skip it directly;

It is not necessary to understand a certain point before learning the program. We may not learn it for the time being, but we will learn it later; “Use it, use it, use it”

In addition, you can also try to guess its function according to the method name or the relevant information you can see as a clue

03. Practical application

  • Practice object: laravel 5 7. * version
  • Reading principle: request to the controller for execution

Good start~~~

03.01 entrance

For PHP, the entry files of the framework are basically from public / index PHP start

<?php

define('LARAVEL_START', microtime(true));

require __DIR__.'/../vendor/autoload.php';
//Initialize framework application object application
$app = require_once __DIR__.'/../bootstrap/app.php';
//Gets the core instance of HTTP request processing
$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
//Execute request processing
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
//Response output
$response->send();
//The program ends and the related middleware ends
$kernel->terminate($request, $response);
?>

At the beginning, we can Reading PHP can understand the flow and process of the whole framework program;

  1. Initialize framework application object application
  2. Get the core instance of HTTP request processing
  3. Execute request processing
  4. Response output
  5. The program ends and the related middleware ends

Next, let’s go to bootstrap / APP PHP to understand the application initialization process

03.02 application initialization

<?php
//Initialize application
$app = new Illuminate\Foundation\Application(
    $_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
//Bind http / debug / console core processor
$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
);
//Return
return $app;
?>

Our goal here is to understand the initialization process of application, so we need to click to see the construction process of application;

Analysis of reading ideas: for the above code, based on our goal, we can be sure that the constructor of the application object is what we need to know

In the subsequent calls to singleton in the application, I believe I didn’t understand it for the first time. Based on the seven point principle, we can skip the initialization process of the application first

OK, enter the application method

blog\vendor\laravel\framework\src\Illuminate\Foundation\Application.php

<?php
class Application
{
  // ..
  public function __construct($basePath = null)
  {
      if ($basePath) {
          $this->setBasePath($basePath);
      }
      $this->registerBaseBindings();
      $this->registerBaseServiceProviders();
      $this->registerCoreContainerAliases();
  }
  // ...
}
?>

When seeing the method, according to the principle “3. Be sure to look at the method name and comments; 6. Record the process and scheduling chain”

Don’t you know “yes or no” at once? You: “yes, yes, yes”

<?php
// Create a new Illuminate application instance.
//Create a new instance of the lighting application.
public function __construct($basePath = null)
{
    //Determine whether the application path is set
    if ($basePath) {
        //Set application path
        $this->setBasePath($basePath);
    }
    //Register app binding
    $this->registerBaseBindings();
    //Register application service provider
    $this->registerBaseServiceProviders();
    //Register core container alias
    $this->registerCoreContainerAliases();
}
?>

In the follow-up, 3 and 6 points will be directly applied to the code; The author doesn’t want to lead to it every time. It’s good to be tacit

OK, let’s further understand the general function of each method based on “2. Pay attention to the right amount of source code exploration, and remember not to go to the dead point”

First look at setbasepath

<?php
public function setBasePath($basePath)
{
    //Processing path is not empty
    $this->basePath = rtrim($basePath, '\/');
    //Bind path to container
    $this->bindPathsInContainer();
    return $this;
}
//Bind the path of all applications in the container
protected function bindPathsInContainer()
{
    $this->instance('path', $this->path());
    $this->instance('path.base', $this->basePath());
    $this->instance('path.lang', $this->langPath());
    //..
}

?>

After reading about setbasepath, we can understand from the overall idea that it is to set the core path of the application, and stop it in an appropriate amount according to the principle 2. Therefore, we stopbindPathsInContainerFunction reading

Look at registerbase bindings

<?php
//Register basic binding to container
protected function registerBaseBindings()
{
    static::setInstance($this);

    $this->instance('app', $this);

    $this->instance(Container::class, $this);

    $this->instance(PackageManifest::class, new PackageManifest(
        new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
    ));
}
?>

At this point, we found that instance is a relatively core existence. It basically exists everywhere. Do we need to analyze it?????

Yes, but not now; Because the key is to understand the application initialization process;

Source code reading and analysis - PHP laravel

According to 7, we can guess that its function is to register instances, that is, register objects; It can be understood by combining the method name. This method is to register the core instance application into the container;

$this->instance(PackageManifest::class, new PackageManifest(
    new Filesystem, $this->basePath(), $this->getCachedPackagesPath()
));

What did you do in this paragraph???? You know what?? You: “what the hell is pulling me? I just read the article. How can I know”;

“Of course I know you don’t know.” I don’t know what to do. Of course, it’s skipping… According to the principle of 7

Continue to look at registerbaseserviceproviders

<?php
//Register all basic service providers
protected function registerBaseServiceProviders()
{
    $this->register(new \Illuminate\Events\EventServiceProvider($this));
    $this->register(new \Illuminate\Log\LogServiceProvider($this));
    $this->register(new \Illuminate\Routing\RoutingServiceProvider($this));
}
?>

Enter this method according to the meaning of “register all basic service providers”, and read this methodEvent translation event,log,routingAccording to the information provided by these three keywords, I don’t know what this method does;

It is the service provider that registers framework events, framework logs and framework routes; Then it’s OK. You need to see register. This is something that will happen later. It doesn’t have much to do with the current topic;

Finally, look at registercore containeraliases

<?php
public function registerCoreContainerAliases()
{
    foreach ([
        'app'                  => [self::class, \Illuminate\Contracts\Container\Container::class,
        // ...  Universal three points
         \Illuminate\Contracts\View\Factory::class],
    ] as $key => $aliases) {
        foreach ($aliases as $alias) {
            $this->alias($key, $alias);
        }
    }
}
?>

You can’t tell from this. The current method is to register the core container into the container, including view / URL / db / redis…

Then the application initialization is OK

Application introduction summary

It’s time to summarize. According to the just process and based on the 6-point principle, make a summary

Summary: in application initialization, the system directory address of the whole application will be set first. After setting, the core application will be registered and bound to the container, and service providers such as event / log / routing will be registered. Finally, the registration of the core container will be completed

03.04 back to index PHP see HTTP kernel

After understanding the application, let’s take a look at the process of HTTP

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);
$response->send();
$kernel->terminate($request, $response);

Some comrades may say that this $kernel is the object???? At this time, we need to print with Principle 5

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
var_dump($kernel);
$response = $kernel->handle(
    $request = Illuminate\Http\Request::capture()
);

If you look at this, you’ll know it’s the object;

Source code reading and analysis - PHP laravel

What is called isApp/Http/Kernel.php

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
  // ..
}

However, it is found that there is no kernel method. According to principle 4, it is found that the final method is inIlluminate\Foundation\Http\Kernelin

Maybe you want to tell meIlluminate\Foundation\Http\Kernel“Where is this?”

Now that you have asked the question sincerely, I will tell you mercifully that when your editor can’t directly click the query method, you can see itvendor/composer/autoload_classmap.phpIt will tell you the answer

Enter Foundation / HTTP / kernel PHP

blog\vendor\laravel\framework\src\Illuminate\Foundation\Http\Kernel.php

Then find the kernel method

//Process incoming HTTP requests
public function handle($request)
{
    try {
        $request->enableHttpMethodParameterOverride();
        $response = $this->sendRequestThroughRouter($request);
    } catch (Exception $e) {
        //It may be a processing error
    } catch (Throwable $e) {
        //It may be a processing error
    }
    //Event request
    $this->app['events']->dispatch(
        new Events\RequestHandled($request, $response)
    );
    return $response;
}

According to the overall structure of the method, we can basically infer that the core request processing method must beEnablehttpmethodparameteroverride or sendrequestthroughrouterin

Because it’s normal here, others are processing errors. Therefore, based on principle 2, check these two methods respectively, and the results are as follows:enableHttpMethodParameterOverrideMaybe not

There is no way but to translate the method name “HTTP method parameter override”. According to this meaning, it obviously handles the request object, which has nothing to do with request processing; There is only one truth

It’s up to you, sendrequestthroughrouter

protected function sendRequestThroughRouter($request)
{
    //You don't need to look at this. You know it is to set the request to bind to the container
    $this->app->instance('request', $request);
    //The facade defines the instance and skips if you don't understand it
    Facade::clearResolvedInstance('request');

    $this->bootstrap();

    return (new Pipeline($this->app))
                ->send($request)
                ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
                ->then($this->dispatchToRouter());
}

Next, look at bootstrap

Finally, we have only the following two left. Let’s look at bootstrap ();

protected $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,
];
public function bootstrap()
{
    if (! $this->app->hasBeenBootstrapped()) {
        $this->app->bootstrapWith($this->bootstrappers());
    }
}
protected function bootstrappers()
{
    return $this->bootstrappers;
}

Here, in order to reduce the space, the relevant schedules are directly arranged to be put together. According to the translation of “bootstrap” is “drive / start” and combined with the keywords in the bootstrappers attribute, it can be inferred that it is; Load and start the system configuration, error handling, facade and service provider in the framework;

adopt$this->app->bootstrapWith($this->bootstrappers())Completion;

Wait$ this->app?? Where did you come from? Who is it? According to principle 4, it is found that it is passed by the constructor

public function __construct(Application $app, Router $router)
{
    $this->app = $app;
}

At this time, you have a choice to see again$this->app->bootstrapWithOr skip, here we choose to see

public 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]);
    }
}

Well, it can be found that events are carried out before and after, only middleware$this->make($bootstrapper)->bootstrap($this)

This is the real business. Here you can further check it in combination with the parameters passed in ‘http:: kernel:: bootstrap’; But, uh, it can be enough, because that’s enough; If you look at it again, it will be off the topic

Return to sendrequestthroughrouter

return (new Pipeline($this->app))
            ->send($request)
            ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
            ->then($this->dispatchToRouter());

At present, if we continue to analyze, it will be more difficult to analyze here; Because it looks a little complicated; Who does the general closure method look at??

Look at the scheduling method passed internally, so there is only one left for filtering$this->app->shouldSkipMiddleware()and$this->dispatchToRouter()

Shouldskipmiddleware belongs to the application level. We can look at dispatchtorouter without looking at it first; Because it is a method in the kernel, and the main function of the kernel is to process requests, it depends on him, it is him, it is him;

Let’s look at dispatchtorouter

//Line dispatcher callback
protected function dispatchToRouter()
{
    return function ($request) {
        $this->app->instance('request', $request);

        return $this->router->dispatch($request);
    };
}

OK, according to our experience, is that right$this->router->dispatch($request)

Careful discovery$this->routerIt is passed during initializationIlluminate\Routing\RouterObject;

Show our principles and skills to router

Next, I will omit the previous tips; Because the final thing is to learn by yourself, so later I will put forward the key methods and try to practice according to the skill principles I said; In fact, I’m too lazy to write. I’m too tired. Alas, I can’t give a praise

Let’s look at how to find a route first

blog\vendor\laravel\framework\src\Illuminate\Routing\Router.php

public 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 findRoute($request)
{
    $this->current = $route = $this->routes->match($request);

    $this->container->instance(Route::class, $route);

    return $route;
}
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)
    );
}

On the whole, route matching is completed through findroute, while runroute is run; Combined with the reading of findroute, it can be determined that routecollection:: match is called for parsing and searching

public function match(Request $request)
{
    //Try printing
    $routes = $this->get($request->getMethod());
    //Find the route. Here is the key route finding method
    $route = $this->matchAgainstRoutes($routes, $request);

    if (! is_null($route)) {
        return $route->bind($request);
    }

    $others = $this->checkForAlternateVerbs($request);

    if (count($others) > 0) {
        return $this->getRouteForMethods($request, $others);
    }

    throw new NotFoundHttpException;
}

protected function matchAgainstRoutes(array $routes, $request, $includingMethod = true)
{
    //If you don't understand it here, print $fallbacks and $routes. Use var_ dump
    [$fallbacks, $routes] = collect($routes)->partition(function ($route) {
        return $route->isFallback;
    });
    //Here is the closure. When you encounter a closure, you can directly look at the method called internally
    return $routes->merge($fallbacks)->first(function ($value) use ($request, $includingMethod) {
        //Use VaR if you don't know $value_ Dump printing
        return $value->matches($request, $includingMethod);
    });
}

Through the above steps, we can basically find that the method called will eventually be assigned to the $route variable and returned

Finally, let’s see how to check and call

go back toblog\vendor\laravel\framework\src\Illuminate\Routing\Router.phpin

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,
        //The same principle, look at it first
        $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) {
                        //The same principle is to look at it
                        return $this->prepareResponse(
                            //$route passed parameters on it
                            $request, $route->run()
                        );
                    });
}

Then we can see where to callRoute::runmethod


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();
    }
}

OK, that’s it. We’ll finish it

04. Continued

I hope this process of interpreting the source code can help you

.