📜 ⬆️ ⬇️

We create our own framework based on symfony2. (Part 4)



Before we move on to today's topic, we’ll change our framework a bit, so as to make templating more convenient:

<?php // example.com/web/front.php require_once __DIR__.'/../src/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; $request = Request::createFromGlobals(); $map = array( '/hello' => 'hello', '/bye' => 'bye', ); $path = $request->getPathInfo(); if (isset($map[$path])) { ob_start(); extract($request->query->all(), EXTR_SKIP); include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]); $response = new Response(ob_get_clean()); } else { $response = new Response('Not Found', 404); } $response->send(); 


Since we imported variables from the query array into the current symbol table, let's simplify the hello.php template as follows:
')
 <!-- example.com/src/pages/hello.php --> Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?> 


Now add new functionality will be much more convenient.
An important aspect of any site is the look of its links. Thanks to the reference map, we have separated them from the code that generates the associative answer, but it is still not flexible enough. For example, that if we want to process data from dynamically generated paths, instead of a parameterized string:

 # Before /hello?name=Fabien # After /hello/Fabien 


To support this feature, we will use the Symfony2 routing component (the Symfony2 "Routing" component). As always, add the component to composer.json and run the php composer.phar update command to install it:

 { "require": { "symfony/class-loader": "2.1.*", "symfony/http-foundation": "2.1.*", "symfony/routing": "2.1.*" } } 


From now on, we will use the autoloader generated by Composer instead of our autoload.php . Delete the autoload.php file and change the link to it in front.php :

 <?php // example.com/web/front.php require_once __DIR__.'/../vendor/.composer/autoload.php'; // ... 


The routing component uses an instance of the class “RouteCollection” , instead of an array of matches (reference map).

 use Symfony\Component\Routing\RouteCollection; $routes = new RouteCollection(); 


Let's add a route for the link of the form / hello / SOMETHING and one more for simple / bye :

 use Symfony\Component\Routing\Route; $routes->add('hello', new Route('/hello/{name}', array('name' => 'World'))); $routes->add('bye', new Route('/bye')); 


Each element of the collection is defined by a name (hello) and an instance of the class “Route” , which in turn is determined by the pattern of the route (/hello/{name}) and the array of default attributes (array('name' => 'World')) .
In the official documentation for this component, you will find information about its other functions, such as URL generation, attribute requirements, compliance with HTTP methods, a YAML or XML file uploader, exporting rewrite rules to PHP or Apache to improve performance, and much more.
Based on the information in the instance of the “RouteCollection” class, an instance of the “UrlMatcher” class will link the paths:

 use Symfony\Component\Routing\RequestContext; use Symfony\Component\Routing\Matcher\UrlMatcher; $context = new RequestContext(); $context->fromRequest($request); $matcher = new UrlMatcher($routes, $context); $attributes = $matcher->match($request->getPathInfo()); 


The match () method splits the requested path into an array (note that matching paths are automatically saved under the special _route key):

 print_r($matcher->match('/bye')); array ( '_route' => 'bye', ); print_r($matcher->match('/hello/Fabien')); array ( 'name' => 'Fabien', '_route' => 'hello', ); print_r($matcher->match('/hello')); array ( 'name' => 'World', '_route' => 'hello', ); 


Even if in our example the query parameters are not needed, they are used on real projects to verify the requirements of the methods and so on.
If a match cannot be found with a single path, an exception is thrown:

 $matcher->match('/not-found'); // throws a Symfony\Component\Routing\Exception\ResourceNotFoundException 


Now, using acquired knowledge, it's time to write the following version of our framework:

 <?php // example.com/web/front.php require_once __DIR__.'/../vendor/.composer/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing; $request = Request::createFromGlobals(); $routes = include __DIR__.'/../src/app.php'; $context = new Routing\RequestContext(); $context->fromRequest($request); $matcher = new Routing\Matcher\UrlMatcher($routes, $context); try { extract($matcher->match($request->getPathInfo()), EXTR_SKIP); ob_start(); include sprintf(__DIR__.'/../src/pages/%s.php', $_route); $response = new Response(ob_get_clean()); } catch (Routing\Exception\ResourceNotFoundException $e) { $response = new Response('Not Found', 404); } catch (Exception $e) { $response = new Response('An error occurred', 500); } $response->send(); 


New code changes:
• Path names are used for template names.
• Error 500 is processed correctly.
• Query parameters are separated to keep patterns simple.
 <!-- example.com/src/pages/hello.php --> Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?> 

• The route configuration has been moved to a separate file:

 <?php // example.com/src/app.php use Symfony\Component\Routing; $routes = new Routing\RouteCollection(); $routes->add('hello', new Routing\Route('/hello/{name}', array('name' => 'World'))); $routes->add('bye', new Routing\Route('/bye')); return $routes; 


We completely separated the configuration (everything, the characteristics of our application - in app.php ) and the framework (the rest of the code on which our application is based - in front.php ).
Having added about 30 lines of code, we got a new framework - more productive, more flexible than the previous one. Enjoy!
Using the routing component has another great advantage: the ability to generate URLs based on Route definitions. If you simultaneously use URL matching and URL generation in your code, there should be no consequences when changing the URL type. Want to know how to use a generator? Insanely simple:

 use Symfony\Component\Routing; $generator = new Routing\Generator\UrlGenerator($routes, $context); echo $generator->generate('hello', array('name' => 'Fabien')); // outputs /hello/Fabien 


The code is absolutely clear; we can also generate absolute paths by adding the third parameter true:

 echo $generator->generate('hello', array('name' => 'Fabien'), true); // outputs something like http://example.com/somewhere/hello/Fabien 


Concerned about performance? Using your route definitions, create an optimized URL match class that will replace the default UrlMatcher :

 $dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes); echo $dumper->dump(); 


Want even more performance? Export your routes as mod_rewrite rules for Apache:

 $dumper = new Routing\Matcher\Dumper\ApacheMatcherDumper($routes); echo $dumper->dump(); 


We agreed with the user fozzy to translate this cycle together.

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


All Articles