How many steps does it take to move from a symfony framework to a complete project?

Keywords: PHP Linux Google Laravel

Preface

For PHP frameworks, whether it's yii, symfony or laravel, everyone is involved in their work. For the resource bundles vendor folders, entry files (index.php or app.php) stored in the framework, people also meet with them every day. But are you really familiar with these files/folders? How does a complete project evolve from a pure framework? What role does each part play in framing this building?

We talked about dependency injection in the last chapter, and I don't know if everyone understands it. It's no problem not to understand. Today's chapter has nothing to do with the previous one.

Composer

Now let's move on to the next topic, composer. Everyone is familiar with this tool. It is very convenient to install plug-ins with it. But is his principle clear? Originally a common class, how can it be loaded in? Composer said, we admire it, and it's done by autoload.

2.1 __autoload

This is a particularly important point of knowledge. We often see it in the entry file of the framework (_autoload and spl_autoload_register). Now, of course, you can only see spl_auto_register. But when asked about the roles and methods of these two methods, most people are still confused.

What are these two functions? What's the convenience of automatic loading?

Include and require are two basic methods of introducing files into PHP. Use include and require directly in small-scale development. But in large-scale projects, there will be a large accumulation of include and require. Think about it. I wrote hundreds of includes in a file. Are you tired? )

Such code is neither elegant nor efficient to execute, and it is quite difficult to maintain.

To solve this problem, some frameworks will provide a configuration list for introducing files, which will be introduced when the object is initialized. But it just makes the code a little more concise, and the effect of the introduction is still unsatisfactory. After PHP 5, with the improvement of PHP object-oriented support, _autoload function really makes automatic loading possible.

Here I add two points of knowledge that are not relevant to the current chapter:

  • Include and require functions are the same, but they differ in that only warnings are generated when an include error occurs, and require throws an error termination script.
  • The only difference between include_once and include is that include_once checks whether a file has been introduced or not, and if so, it does not reintroduce.

The simplest way to achieve automatic loading is to use the _autoload magic method. When you refer to a class that does not exist, _autoload is called, and your class name is passed as a parameter. As for the specific logic of the function, it needs the user to implement it himself. Using this property, an automatic loading mechanism is created.
First, create an autoload.php to do a simple test:

// When the class is undefined, the system calls automatically
function __autoload($class)
{
    /* Specific Processing Logic*/
    echo $class; // Simple output undefined class names
}

new HelloWorld();

/**
 * Output HelloWorld and Error Information
 * Fatal error: Class 'HelloWorld' not found
 */


Through this simple example, we can find that in the process of instantiating classes, the system does roughly the following work:

/* Simulated system instantiation process*/
function instance($class)
{
    // Returns an instance of a class if it exists
    if (class_exists($class, false)) {
        return new $class();
    }
    // Check whether the autoload function is user-defined
    if (function_exists('__autoload')) {
        _ autoload($class); // last chance to introduce
    }
    // Check again if the class exists
    if (class_exists($class, false)) {
        return new $class();
    } Other {// System: I'm at a loss.
        throw new Exception('Class Not Found');
    }
}

Having understood the working principle of _autoload function, let's use it to realize automatic loading.

First, create a class file (the recommended file name is the same as the class name). The code is as follows:

class [ClassName] 
{
    // Output the current class name when the object is instantiated
    function __construct()
    {
        echo '<h1>' . __CLASS__ . '</h1>';
    }
}

(I created a HelloWorld class here for demonstration.) Next, we will define the specific logic of _autoload to enable it to load automatically:

function __autoload($class)
{
    // Determining File Name Based on Class Name
    $file = $class . '.php';

    if (file_exists($file)) {
        include $file; // Introducing PHP files
    }
}

new HelloWorld();

/**
 * Output < H1 > HelloWorld</h1>
 */

It looks good, doesn't it? With this _autoload, you can write a mechanism for automatically loading classes. But have you ever tried to write two _autoloads in a file? No need to think, the result is wrong. In a large framework, can you guarantee that you have only one _autoload? Isn't that too much trouble?

Don't worry, spl_autoload_register() should be on the stage. But before we go any further, we have to say another important concept -- namespaces.

2.3 Namespaces

Namespaces aren't really new, and many languages (such as C++) have long supported this feature. But PHP started relatively late and did not support it until after PHP 5.3. Namespaces, in short, are identifiers whose main purpose is to resolve naming conflicts.
Just like in everyday life, there are many people with the same name. How to distinguish these people? That would require some additional identification. It seems good to treat the work unit as a logo, so that you don't have to worry about the embarrassment of "name collision".

Here we have a small task to introduce Baidu's CEO Robin Li:

namespace Baidu;

class Robin Li
{
    function __construct()
    {
        echo 'Baidu Founder';
    }
}

This is Robin Li's basic information. Namespace is his unit logo, and class is his name. Namespaces are declared by keyword namespace. If a file contains a namespace, it must declare the namespace before all other code.

new Baidu Robin Li (); / / restricted class name
 new \ Baidu / Robin Li (); / / fully qualified class name

In general, whether they introduce Baidu Robin Li or "Baidu Inc Robin Li" to others, they can understand. In the absence of a declaration in the current namespace, qualified class names are equivalent to fully qualified class names. Because if no space is specified, the default is global ().

namespace Google;

new Baidu / Robin Li (); / / Google \ "Baidu \" Robin Li (actual results)
new \ "Baidu \" Robin Li (); / / Baidu \ "Robin Li \" (actual results)

If you introduce Robin Li to their employees in the Google Corporation, you must point out that it is "Baidu Inc's Robin Li". Otherwise, he would think that Baidu is a department of Google, and Robin Li is just one of them. This example shows the difference between using a qualified class name and a fully qualified class name in a namespace. (Fully qualified class name = current namespace + qualified class name)

/* Import namespaces */
use Baidu\Robin Li;
new Robin Li(); // Baidu Robin Li (actual results)

/* Setting aliases */
use Baidu\Robin Li AS CEO;
new CEO(); // Baidu Robin Li (actual results)

/* Any circumstances */
new \Baidu\Robin Li();// Baidu Robin Li (actual results)

The first situation is that others have already known Robin Li. You only need to directly say the name, and he will know who you are referring to. The second thing is that Robin Li is their CEO. You can say CEO directly, he can react immediately. Namespaces are only used to prefix class names, which is not prone to conflicts, and the system still does not import automatically.
If no files are introduced, the system triggers the _autoload function before throwing a "Class Not Found" error, and passes in the qualified class name as a parameter.
So the above examples are based on the fact that you have manually introduced related files, otherwise the system will throw "Class' Baidu Robin Li 'not found".

2.4 spl_autoload_register

Next let's implement automatic loading with namespaces. Here we use the spl_autoload_register() function to implement, which requires your PHP version number to be greater than 5.12.
The function of the spl_autoload_register function is to register the incoming function (parameters can be in the form of callback function or function name) into the SPL_ autoload function queue and remove the default autoload () function of the system. Once the spl_autoload_register() function is called, when an undefined class is called, the system will call all functions registered with the spl_autoload_register() function in sequence, instead of automatically calling the _autoload() function.

Now let's create a Linux class that uses os as its namespace (it is recommended that the file name be consistent with the class name):

namespace os; // Namespace

class Linux // Class name
{
    function __construct()
    {
        echo '<h1>' . __CLASS__ . '</h1>';
    }
}

Next, create a new PHP file in the same directory, and use spl_autoload_register to load automatically in the form of function callbacks:

spl_autoload_register(function ($class) { // class = os\Linux

    /* Restricted Class Name Path Mapping */
    $class_map = array(
        // Qualified class name => file path
        'os\\Linux' => './Linux.php',
    );

    /* Determining File Name Based on Class Name */
    $file = $class_map[$class];

    /* Introduction of relevant documents */
    if (file_exists($file)) {
        include $file;
    }
});

new \os\Linux();

Here we use an array to store the relationship between the class name and the file path, so that when the class name is passed in, the autoloader knows which file to import to load the class.

But once there are more files, the mapping array becomes very long, which can be very troublesome to maintain. If naming follows a common convention, the automatic loader can automatically parse and determine the path of the class file. PSR-4 is a widely used convention.

2.4 PSR-4 Specification

PSR-4 is a specification for automatically loading corresponding classes by file paths. The specification specifies that a fully qualified class name should have the following structure:

\ Top Namespaces >(Subnamespaces>)*Class Names >

If we continue to use the example above, the top-level namespace corresponds to the company, the sub-namespace corresponds to the position, and the class name corresponds to the person name. Then the Robin Li standard is called "Baidu Inc CEO Robin Li".

The PSR-4 specification must have a top-level namespace, which means that it represents a particular directory (file base directory). The sub namespace represents the path (relative path) of the class file relative to the file base directory, and the class name is consistent with the file name (note the case difference).

For example: in fully qualified class name app view news Index, if app stands for C: Baidu, then the path of this class is C: Baidu view news Index.php

Let's take the example of parsing app view news Index and write a simple Demo:

$class = 'app\view\news\Index';

/* Top-level namespace path mapping */
$vendor_map = array(
    'app' => 'C:\Baidu',
);

/* Resolution class name is file path */
$vendor = substr($class, 0, strpos($class, '\\')); // Remove the top-level namespace [app]
$vendor_dir = $vendor_map[$vendor]; // File base directory [C: Baidu]
$rel_path = dirname(substr($class, strlen($vendor))); // Relative path [/view/news]
$file_name = basename($class) . '.php'; // File name [Index.php]

/* The path of the output file */
echo $vendor_dir . $rel_path . DIRECTORY_SEPARATOR . $file_name;

From this Demo, you can see the process of defining the class name to the path. Now let's implement the automatic loader in a standard object-oriented way.

First, we create a file Index.php, which is in the app mvc view home directory:

namespace app\mvc\view\home;

class Index
{
    function __construct()
    {
        echo '<h1> Welcome To Home </h1>';
    }
}

Then we are creating a loading class (no namespace is required) that is in the directory:

class Loader
{
    /* Path mapping */
    public static $vendorMap = array(
        'app' => __DIR__ . DIRECTORY_SEPARATOR . 'app',
    );

    /**
     * Automatic Loader
     */
    public static function autoload($class)
    {
        $file = self::findFile($class);
        if (file_exists($file)) {
            self::includeFile($file);
        }
    }

    /**
     * Parse file path
     */
    private static function findFile($class)
    {
        $vendor = substr($class, 0, strpos($class, '\\')); // Top-level namespaces
        $vendorDir = self::$vendorMap[$vendor]; // File base directory
        $filePath = substr($class, strlen($vendor)) . '.php'; // File relative path
        return strtr($vendorDir . $filePath, '\\', DIRECTORY_SEPARATOR); // File Standard Path
    }

    /**
     * Import files
     */
    private static function includeFile($file)
    {
        if (is_file($file)) {
            include $file;
        }
    }
}

Finally, autoload in the Loader class is registered in the spl_autoload_register function:

include 'Loader.php'; // Introducing loader
spl_autoload_register('Loader::autoload'); // Registration Autoloading

new \app\mvc\view\home\Index(); // Instantiate unreferenced classes

/**
 * Output: <h1> Welcome to Home</h1>
 */

2.4 composer

With all that said, it's time for composer to come on at last. I won't go into installation or anything like that here. Let's take a look at the details of the vendor/composer file

vendor
----autoload_classmap.php
----autoload_files.php
----autoload_namespace.php
----autoload_psr4.php
----autoload_real.php
----autoload_static.php
----ClassLoader.php
----install.json 
autoload.php

So let me first look at vendor/autoload.php:

<?php

// autoload.php @generated by Composer

require_once __DIR__ . '/composer' . '/autoload_real.php';

return ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326::getLoader();


//It executes an automatically generated getLoader method like Composer Autoloader Initff 1d77c911413097b07ee2acc23326.
//We followed up on autoload_real.php.

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return autoload_real.phpself::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326', 'loadClassLoader'));

        $map = require __DIR__ . '/autoload_namespaces.php';
        foreach ($map as $namespace => $path) {
            $loader->set($namespace, $path);
        }

        $map = require __DIR__ . '/autoload_psr4.php';
        foreach ($map as $namespace => $path) {
            $loader->setPsr4($namespace, $path);
        }

        $classMap = require __DIR__ . '/autoload_classmap.php';
        if ($classMap) {
            $loader->addClassMap($classMap);
        }

        $loader->register(true);

        $includeFiles = require __DIR__ . '/autoload_files.php';
        foreach ($includeFiles as $file) {
            composerRequireff1d77c91141523097b07ee2acc23326($file);
        }

        return $loader;
    }

It is obvious that he has included several configuration files, such as autoload_namespaces.php, autoload_psr4.php, autoload_classmap.php, autoload_files.php, etc., and processed them (setPsr4) and finally register ed them.
Then we follow up the register method:

    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

This function is one line, but it is simple and straightforward. It calls spl_autoload_register function of php directly, and registers the method of processing _autoload, that is, the loadClass method. Follow up the loadClass method:

    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

From the function name, you can probably know the process: if there is a $file corresponding to $class, include comes in.
Let's look at the findFile method.

    public function findFile($class)
    {
        // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
        if ('\\' == $class[0]) {
            $class = substr($class, 1);
        }

        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative) {
            return false;
        }

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if ($file === null && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if ($file === null) {
            // Remember that this class does not exist.
            return $this->classMap[$class] = false;
        }

        return $file;
    }

Find the file by the class name, and finally lock it in the findFileWithExtension method.
Or follow the findFileWithExtension method:

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }
    }

The final implementation converts the class name such as namespace class to a path such as directory name / class name. php, and returns the full path.

I found that composer's autoload is slightly different from php's spl_autoload when it contains files. That is, spl_autoload looks for file names of type. inc, but composer does not.

In addition, it can be found that although the name of the configuration file is autoload_psr4.php, in fact, automatic loading in psr0 format is also supported. The biggest difference between them is the use of "" in psr0 instead of "between directories".

So much has been said above, it's time to sum up. From _autoload to spl_autoload_register to composer and psr4 methods. What is the purpose of php's official and community design? They are designed to solve the inconvenience of include files. Say 10000, the original one by one inclusion is inconvenient, I now use spl_autoload_register to automatically include directly. But we can't write without rules, so we have psr4.

Posted by rurouni on Tue, 24 Sep 2019 01:12:01 -0700