📜 ⬆️ ⬇️

Startup in PHP: started for health, and finished for the rest

Translator's Preface


This article is a free translation-retelling of the post The End of Autoloading
. The original article is not the first freshness, so the code shown in the examples may not be relevant. In the topic that the article touches on, the most important thing is the general view, and not concrete examples.

Foreword


Startup in PHP is a great time saver. This allows you to write scripts without thinking about the paths to the libraries that you use. But with the advent of neymspeysov and influenced by the Java-style of modern frameworks, the situation has changed. In the near future, autoloading will be everywhere, but without a single benefit of its old style.


Before autoload. Paths to files.


Before the advent of autoload, in each script it was necessary to specify the paths to the used libraries. The source code looked like this:
')
<?php require_once 'PEAR.php'; require_once 'PEAR/DependencyDB.php'; class PEAR_Registry extends PEAR { //... } 

Dependencies were clearly written at the beginning of each script. In this example, the dependency is obvious, even if the PEAR_DependencyDB call is on line 328.

The arrival of SPL autoload.


Later, the spl_autoload_register () function appeared, which allowed us to remove the require / require_once calls. Remarkably, it was now possible to use classes without having to know where they are located. For example:

 <?php class postActions extends sfActions { public function executeList() { $this->posts = PostPeer::doSelect(new Criteria()); } } 

See, there is not a single call to require / require_once, despite the fact that this class depends on the sfActions, PostPeer, and Criteria classes. The developers were able to engage in business logic, without wasting time searching for dependency paths. It was really comfortable.

Implements autoload.


Implementations of autoloading vary. Some libraries use a list of all the classes that need to be connected. For example:

 <?php class Propel { // .. protected static $autoloadMap = array( 'DBAdapter' => 'adapter/DBAdapter.php', 'DBMSSQL' => 'adapter/DBMSSQL.php', 'MssqlPropelPDO' => 'adapter/MSSQL/MssqlPropelPDO.php', 'MssqlDebugPDO' => 'adapter/MSSQL/MssqlDebugPDO.php', // etc. } 

This technique allowed us to hide class paths, but made the library developer update the autoload “map” every time a new class was added. Another technique uses a walk through the project folder structure in the search for the desired class. This approach allowed us to replace the framework classes with our own, since Passing through folders took place in a specific order: user folders, project, plugins, and framework. Thus, the developer could create the class ClassName, which replaces the ClassName provided by the plugin, since will load earlier.

Startup with namespaces


Neymspeys arrival changed startup techniques. The framework framework authors have united to unify autoload techniques for the possibility of technical interaction between different libraries. It was decided that the explicit is better than the implicit and the full class name would be a relative path to the file.

 \Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php \Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php \Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php \Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php 

Were developed principles of naming and file structure, as well as the implementation of the class SplClassLoader . This approach is now used in almost all modern frameworks. Code example:

 <?php namespace Application\HelloBundle\Controller; use Symfony\Framework\WebBundle\Controller; class HelloController extends Controller { public function indexAction($name) { $author = new \Application\HelloBundle\Model\Author(); $author->setFirstName($name); $author->save(); return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author)); } } 

Here everything is also not require due to autoload. The autoloader looks for the Symfony \ Framework \ WebBundle \ Controller class in the Symfony / Framework / WebBundle / Controller.php file.
Everything is good, everyone is happy. Explicit dependencies and class substitution are easy:

 <?php namespace Application\HelloBundle\Controller; // use a custom Controller class instead of the framework's Controller use Application\HelloBundle\Tools\Controller; class HelloController extends Controller { // same code as before } 


End all convenience.


Does the use of the previous example remind you anything? This is very similar to the good old require / require_once, isn't it?

 <?php // old style require_once 'Application/HelloBundle/Tools/Controller.php'; // new style use 'Application\HelloBundle\Tools\Controller'; 

The verbosity introduced by namespaces in the first place reduces the ease of using autoloading. But the problem is not only that you need to write more code, the IDE can help you with autocompletion, but that you need to know the full names of the classes you need. You need to know the framework classes very well in order to use them. This is a step back from the “first generation” autoload, where it was enough to know the name of the class.

There is no other way.


Wouldn't it be great to use modern libraries without knowing their file structure? What if you could write a controller like this:

 <?php class HelloController extends Controller { public function indexAction($name) { $author = new Author(); $author->setFirstName($name); $author->save(); return $this->render('HelloBundle:Hello:index', array('name' => $name, 'author' => $author)); } } 

A smart autoloader would intercept a call to the Controller class, load the Symfony / Framework / WebBundle / Controller.php file, and dynamically create an alias from Symfony \ Framework \ WebBundle \ Controller to Controller. Unfortunately, in PHP, use creates an alias at compile time, so this move will not work. Of course, it is possible to do this using eval, but this is probably even worse than manually connecting files. Also, the creation of such aliases when working with the framework is not possible due to a conflict with lazy loading and conflict of class names, for example:
Symfony \ Framework \ WebBundle \ Command
and
Symfony \ Components \ Console \ Command \ Command
As long as the authors of the frameworks do not change their view on autoloading, the future of PHP seems to me to be verbose.

Solution to the problem.


Personally, I think that wordiness greatly slows down the development. For example, take microframes - they make it possible to process a request quickly, with a minimum of MVC separation. Let's compare the code of an example application written using Slim (autoload without neimspaces) and Silex (autoload with neymspaces):

 <?php // Hello world with Slim require_once 'slim/Slim.php'; Slim::init(); Slim::get('/hello/:name', function($name) { Slim::render('hello.php', array('name' => $name)); }); Slim::run(); // Hello world with Silex require_once 'silex.phar'; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Templating\Engine; use Symfony\Component\Templating\Loader\FilesystemLoader; use Silex\Framework; $framework = new Framework(array( 'GET /hello/:name' => function($name) { $loader = new FilesystemLoader('views/%name%.php'); $view = new Engine($loader); return new Response($view->render( 'hello', array('name' => $name) )); } )); $framework->handle()->send(); 

In the second example, autoloading makes everything just harder.

Developers of modern frameworks explain that verbosity is the price we pay for the quality of the code. I'm not sure I want to pay this price. I do not want to see how PHP turns into Java, where the code is excellent in terms of Computer Science, but very expensive to write. This encourages the desire to use other languages ​​where the issue of autoloading with namespaces is not worth it and at the same time, rapid development is possible.

Take for example Ruby. There is a framework like Sinatra, using which the HelloWorld application becomes very concise:

 require 'sinatra' require 'erb' get '/hello/:name' do |name| @name = name erb :hello end 

Oh, look, require is used here! And at the same time, everything is written very quickly and easy to use.

Source: https://habr.com/ru/post/138920/


All Articles