From 0ef3f8efd7bddc0001c41d540a96a57b52b790f1 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 3 Jan 2012 18:46:06 +0100 Subject: [PATCH 01/74] added part 1 --- book/part1.rst | 202 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 book/part1.rst diff --git a/book/part1.rst b/book/part1.rst new file mode 100644 index 00000000000..ab135d41549 --- /dev/null +++ b/book/part1.rst @@ -0,0 +1,202 @@ +Create your own framework... on top of the Symfony2 Components (part 1) +======================================================================= + +Symfony2 is a reusable set of standalone, decoupled, and cohesive PHP +components that solve common web development problems. + +Instead of using these low-level components, you can use the ready-to-be-used +Symfony2 full-stack web framework, which is based on these components... or +you can create your very own framework. This series is about the latter. + +.. note:: + + If you just want to use the Symfony2 full-stack framework, you'd better + read its official `documentation`_ instead. + +Why would you like to create your own framework? +------------------------------------------------ + +Why would you like to create your own framework in the first place? If you +look around, everybody will tell you that it's a bad thing to reinvent the +wheel and that you'd better choose an existing framework and forget about +creating your own altogether. Most of the time, they are right but I can think +of a few good reasons to start creating your own framework: + +* To learn more about the low level architecture of modern web frameworks in + general and about the Symfony2 full-stack framework internals in particular; + +* To create a framework tailored to your very specific needs (just be sure + first that your needs are really specific); + +* To experiment creating a framework for fun (in a learn-and-throw-away + approach); + +* To refactor an old/existing application that needs a good dose of recent web + development best practices; + +* To prove the world that you can actually create a framework on your own (... + but with little effort). + +I will gently guide you through the creation of a web framework, one step at a +time. At each step, you will have a fully-working framework that you can use +as is or as a start for your very own. We will start with simple frameworks +and more features will be added with time. Eventually, you will have a +fully-featured full-stack web framework. + +And of course, each step will be the occasion to learn more about some of the +Symfony2 Components. + +.. tip:: + + If you don't have time to read the whole series, or if you want to get + started fast, you can also have a look at `Silex`_, a micro-framework + based on the Symfony2 Components. The code is rather slim and it leverages + many aspects of the Symfony2 Components. + +Many modern web frameworks call themselves MVC frameworks. We won't talk about +MVC here as the Symfony2 Components are able to create any type of frameworks, +not just the ones that follow the MVC architecture. Anyway, if you have a look +at the MVC semantics, this series is about how to create the Controller part +of a framework. For the Model and the View, it really depends on your personal +taste and I will let you use any existing third-party libraries (Doctrine, +Propel, or plain-old PDO for the Model; PHP or Twig for the View). + +When creating a framework, following the MVC pattern is not the right goal. +The main goal should be the Separation of Concerns; I actually think that this +is the only design pattern that you should really care about. The fundamental +principles of the Symfony2 Components are centered around the HTTP +specification. As such, the frameworks that we are going to create should be +more accurately labelled as HTTP frameworks or Request/Response frameworks. + +Before we start +--------------- + +Reading about how to create a framework is not enough. You will have to follow +along and actually type all the examples we will work on. For that, you need a +recent version of PHP (5.3.8 or later is good enough), a web server (like +Apache or NGinx), a good knowledge of PHP and an understanding of Object +Oriented programming. + +Ready to go? Let's start. + +Bootstrapping +------------- + +Before we can even think of creating our first framework, we need to talk +about some conventions: where we will store our code, how we will name our +classes, how we will reference external dependencies, etc. + +To store our framework, create a directory somewhere on your machine: + +.. code-block: sh + + $ mkdir framework + $ cd framework + +Coding Standards +~~~~~~~~~~~~~~~~ + +Before anyone starts a flame war about coding standards and why the one used +here suck hard, let's all admit that this does not matter that much as long as +you are consistent. For this book, we are going to use the `Symfony2 Coding +Standards`_. + +Components Installation +~~~~~~~~~~~~~~~~~~~~~~~ + +To install the Symfony2 Components that we need for our framework, we are +going to use `Composer`_, a project dependency manager for PHP. First, list +your dependencies in a ``composer.json`` file: + +.. code-block:: json + + # framework/composer.json + { + "require": { + "symfony/class-loader": "2.1.*" + } + } + +Here, we tell Composer that our project depends on the Symfony2 ClassLoader +component, version 2.1.0 or later. To actually install the project +dependencies, download the composer binary and run it: + +.. code-block:: sh + + $ wget http://getcomposer.org/composer.phar + $ # or + $ curl -O http://getcomposer.org/composer.phar + + $ php composer.phar install + +After running the ``install`` command, you must see a new ``vendor/`` +directory that must contain the Symfony2 ClassLoader code. + +.. note:: + + Even if we highly recommend you the use of Composer, you can also download + the archives of the components directly or use Git submodules. That's + really up to you. + +Naming Conventions and Autoloading +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We are going to `autoload`_ all our classes. Without autoloading, you need to +require the file where a class is defined before being able to use it. But +with some conventions, we can just let PHP do the hard work for us. + +Symfony2 follows the de-facto PHP standard, `PSR-0`_, for class names and +autoloading. The Symfony2 ClassLoader Component provides an autoloader that +implements this PSR-0 standard and most of the time, the Symfony2 ClassLoader +is all you need to autoload all your project classes. + +Create and empty autoloader in a new ``autoload.php`` file: + +.. code-block:: php + + register(); + +You can now run the ``autoload.php`` on the CLI, it should not do anything and +should not throw any error: + +.. code-block:: sh + + $ php autoload.php + +.. tip:: + + The Symfony website has more information about the `ClassLoader`_ + component. + +Our Project +----------- + +Instead of creating our framework from scratch, we are going to write the same +"application" over and over again, adding one abstraction at a time. Let's +start with the simplest web application we can think of in PHP:: + + Date: Tue, 3 Jan 2012 23:13:08 +0100 Subject: [PATCH 02/74] added a LICENSE file --- LICENSE.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 LICENSE.md diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000000..176160d1345 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,4 @@ +This work is licensed under a Creative Commons Attribution-Share Alike 3.0 +Unported License. + +http://creativecommons.org/licenses/by-sa/3.0/ From 8ed4076c0e236aa97233c329ecf034fc0fbc9830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Wed, 4 Jan 2012 00:06:13 +0100 Subject: [PATCH 03/74] Fixed typo --- book/part1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part1.rst b/book/part1.rst index ab135d41549..fdbb2e8787a 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -188,7 +188,7 @@ start with the simplest web application we can think of in PHP:: $input = $_GET['name']; - printf('Hello %s', $_GET['name']); + printf('Hello %s', $input); That's all for the first part of this series. Next time, we will introduce the HttpFoundation Component and see what it will brings us. From b239305a9689fd78812ea4dd18fca57b923f74cb Mon Sep 17 00:00:00 2001 From: Jacob Dreesen Date: Wed, 4 Jan 2012 02:30:08 +0100 Subject: [PATCH 04/74] Fixed typo --- book/part1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part1.rst b/book/part1.rst index ab135d41549..fb18aa3c5bc 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -150,7 +150,7 @@ autoloading. The Symfony2 ClassLoader Component provides an autoloader that implements this PSR-0 standard and most of the time, the Symfony2 ClassLoader is all you need to autoload all your project classes. -Create and empty autoloader in a new ``autoload.php`` file: +Create an empty autoloader in a new ``autoload.php`` file: .. code-block:: php From 13ba87c8b8de8a9ec8ea159e231ba8612958fc64 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 4 Jan 2012 06:54:20 +0100 Subject: [PATCH 05/74] added a tip about the Composer autoloader --- book/part1.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/book/part1.rst b/book/part1.rst index b937418b3df..7d0b5017bbf 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -177,6 +177,12 @@ should not throw any error: The Symfony website has more information about the `ClassLoader`_ component. +.. note:: + + Composer automatically creates an autoloader for all your installed + dependencies; instead of using the ClassLoader component, you can also + just require ``vendor/.composer/autoload.php``. + Our Project ----------- From 2c79d420e2768cda70a4bdaf35c51f7b4936d982 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 4 Jan 2012 21:15:15 +0100 Subject: [PATCH 06/74] added part 2 --- book/part2.rst | 332 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 332 insertions(+) create mode 100644 book/part2.rst diff --git a/book/part2.rst b/book/part2.rst new file mode 100644 index 00000000000..94057b2880d --- /dev/null +++ b/book/part2.rst @@ -0,0 +1,332 @@ +Create your own framework... on top of the Symfony2 Components (part 2) +======================================================================= + +Before we dive into the code refactoring, I first want to step back and take a +look at why you would like to use a framework instead of keeping your +plain-old PHP applications as is. Why using a framework is actually a good +idea, even for the simplest snippet of code and why creating your framework on +top of the Symfony2 components is better than creating a framework from +scratch. + +.. note:: + + I won't talk about the obvious and traditional benefits of using a + framework when working on big applications with more than a few + developers; the Internet has already plenty of good resources on that + topic. + +Even if the "application" we wrote yesterday was simple enough, it suffers +from a few problems:: + + assertEquals('Hello Fabien', $content); + } + } + +.. note:: + + If our application were just slightly bigger, we would have been able to + find even more problems. If you are curious about them, read the `Symfony2 + versus Flat PHP`_ chapter of the Symfony2 documentation. + +At this point, if you are not convinced that security and testing are indeed +two very good reasons to stop writing code the old way and adopt a framework +instead (whatever adopting a framework means in this context), you can stop +reading this series now and go back to whatever code you were working on +before. + +.. note:: + + Of course, using a framework should give you more than just security and + testability, but the more important thing to keep in mind is that the + framework you choose must allow you to write better code faster. + +Going OOP with the HttpFoundation Component +------------------------------------------- + +Writing web code is about interacting with HTTP. So, the fundamental +principles of our framework should be centered around the `HTTP +specification`_. + +The HTTP specification describes how a client (a browser for instance) +interacts with a server (our application via a web server). The dialog between +the client and the server is specified by well-defined *messages*, requests +and responses: *the client sends a request to the server and based on this +request, the server returns a response*. + +In PHP, the request is represented by global variables (``$_GET``, ``$_POST``, +``$_FILE``, ``$_COOKIE``, ``$_SESSION``...) and the response is generated by +functions (``echo``, ``header``, ``setcookie``, ...). + +The first step towards better code is probably to use an Object-Oriented +approach; that's the main goal of the Symfony2 HttpFoundation component: +replacing the default PHP global variables and functions by an Object-Oriented +layer. + +To use this component, open the ``composer.json`` file and add it as a +dependency for the project: + +.. code-block:: json + + # framework/composer.json + { + "require": { + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*" + } + } + +Then, run the composer ``update`` command: + +.. code-block:: sh + + $ php composer.phar update + +Finally, at the bottom of the ``autoload.php`` file, add the code needed to +autoload the component:: + + registerNamespace('Symfony\\Component\\HttpFoundation', __DIR__.'/vendor/symfony/http-foundation'); + +Now, let's rewrite our application by using the ``Request`` and the +``Response`` classes:: + + get('name', 'World'); + + $response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'))); + + $response->send(); + +The ``createFromGlobals()`` method creates a ``Request`` object based on the +current PHP global variables. + +The ``send()`` method sends the ``Response`` object back to the client (it +first outputs the HTTP headers followed by the content). + +.. tip:: + + Before the ``send()`` call, we should have added a call to the + ``prepare()`` method (``$response->prepare($request);``) to ensure that + our Response were compliant with the HTTP specification. For instance, if + we were to call the page with the ``HEAD`` method, it would have removed + the content of the Response. + +The main difference with the previous code is that you have total control of +the HTTP messages. You can create whatever request you want and you are in +charge of sending the response whenever you see fit. + +.. note:: + + We haven't explicitly set the ``Content-Type`` header in the rewritten + code as the Response object defaults to ``UTF-8`` by default. + +With the ``Request`` class, you have all the request information at your +fingertips thanks to a nice and simple API:: + + getPathInfo(); + + // retrieve GET and POST variables respectively + $request->query->get('foo'); + $request->request->get('bar', 'default value if bar does not exist'); + + // retrieve SERVER variables + $request->server->get('HTTP_HOST'); + + // retrieves an instance of UploadedFile identified by foo + $request->files->get('foo'); + + // retrieve a COOKIE value + $request->cookies->get('PHPSESSID'); + + // retrieve an HTTP request header, with normalized, lowercase keys + $request->headers->get('host'); + $request->headers->get('content_type'); + + $request->getMethod(); // GET, POST, PUT, DELETE, HEAD + $request->getLanguages(); // an array of languages the client accepts + +You can also simulate a request:: + + $request = Request::create('/index.php?name=Fabien'); + +With the ``Response`` class, you can easily tweak the response:: + + setContent('Hello world!'); + $response->setStatusCode(200); + $response->headers->set('Content-Type', 'text/html'); + + // configure the HTTP cache headers + $response->setMaxAge(10); + +.. tip:: + + To debug a Response, cast it to a string; it will return the HTTP + representation of the response (headers and content). + +Last but not the least, these classes, like every other class in the Symfony +code, have been `audited`_ for security issues by an independent company. And +being an Open-Source project also means that many other developers around the +world have read the code and have already fixed potential security problems. +When was the last you ordered a professional security audit for your home-made +framework? + +Even something as simple as getting the client IP address can be insecure:: + + getClientIp()) { + // the client is a known one, so give it some more privilege + } + +And there is an added benefit: it is *secure* by default. What do I mean by +secure? The ``$_SERVER['HTTP_X_FORWARDED_FOR']`` value cannot be trusted as it +can be manipulated by the end user when there is no proxy. So, if you are +using this code in production without a proxy, it becomes trivially easy to +abuse your system. That's not the case with the ``getClientIp()`` method as +you must explicitly trust this header by calling ``trustProxyData()``:: + + getClientIp(true)) { + // the client is a known one, so give it some more privilege + } + +So, the ``getClientIp()`` method works securely in all circumstances. You can +use it in all your projects, whatever the configuration is, it will behave +correctly and safely. That's one of the goal of using a framework. If you were +to write a framework from scratch, you would have to think about all these +cases by yourself. Why not using a technology that already works? + +.. note:: + + If you want to learn more about the HttpFoundation component, you can have + a look at the `API`_ or read its dedicated `documentation`_ on the Symfony + website. + +Believe or not but we have our first framework. You can stop now if you want. +Using just the Symfony2 HttpFoundation component already allows you to write +better and more testable code. It also allows you to write code faster as many +day-to-day problems have already been solved for you. + +As a matter of fact, projects like Drupal have adopted (for the upcoming +version 8) the HttpFoundation component; if it works for them, it will +probably work for you. Don't reinvent the wheel. + +I've almost forgot to talk about one added benefit: using the HttpFoundation +component is the start of better interoperability between all frameworks and +applications using it (as of today Symfony2, Drupal 8, phpBB 4, Silex, +Midguard CMS, ...). + +.. _`Twig`: http://twig.sensiolabs.com/ +.. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html +.. _`HTTP specification`: http://tools.ietf.org/wg/httpbis/ +.. _`API`: http://api.symfony.com/2.0/Symfony/Component/HttpFoundation.html +.. _`documentation`: http://symfony.com/doc/current/components/http_foundation.html +.. _`audited`: http://symfony.com/blog/symfony2-security-audit From fcaf268ec2f05fdb4e733854c6764e42454f0917 Mon Sep 17 00:00:00 2001 From: Chris Sedlmayr Date: Thu, 5 Jan 2012 12:01:01 +0000 Subject: [PATCH 07/74] Fixes grammatical error --- book/part1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part1.rst b/book/part1.rst index 7d0b5017bbf..d20f073015b 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -197,7 +197,7 @@ start with the simplest web application we can think of in PHP:: printf('Hello %s', $input); That's all for the first part of this series. Next time, we will introduce the -HttpFoundation Component and see what it will brings us. +HttpFoundation Component and see what it brings us. .. _`documentation`: http://symfony.com/doc .. _`Silex`: http://silex.sensiolabs.org/ From a4f52d95f477b27ee3310ed6bedc32c9f92555cb Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 5 Jan 2012 14:21:20 +0100 Subject: [PATCH 08/74] added links to projects --- book/part2.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/book/part2.rst b/book/part2.rst index 94057b2880d..34446b749f4 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -321,8 +321,8 @@ probably work for you. Don't reinvent the wheel. I've almost forgot to talk about one added benefit: using the HttpFoundation component is the start of better interoperability between all frameworks and -applications using it (as of today Symfony2, Drupal 8, phpBB 4, Silex, -Midguard CMS, ...). +applications using it (as of today `Symfony2`_, `Drupal 8`_, phpBB 4, Silex, +Midgard CMS, `Zikula`_ ...). .. _`Twig`: http://twig.sensiolabs.com/ .. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html @@ -330,3 +330,9 @@ Midguard CMS, ...). .. _`API`: http://api.symfony.com/2.0/Symfony/Component/HttpFoundation.html .. _`documentation`: http://symfony.com/doc/current/components/http_foundation.html .. _`audited`: http://symfony.com/blog/symfony2-security-audit +.. _`Symfony2`: http://symfony.com/ +.. _`Drupal 8`: http://drupal.org/ +.. _`phpBB 4`: http://www.phpbb.com/ +.. _`Silex`: http://silex.sensiolabs.org/ +.. _`Midgard CMS`: http://www.midgard-project.org/ +.. _`Zikula`: http://zikula.org/ From ee67eee6f343459992cd3847929a2df859b492bc Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 5 Jan 2012 14:26:20 +0100 Subject: [PATCH 09/74] fixed markup --- book/part2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/part2.rst b/book/part2.rst index 34446b749f4..f5a23dddd42 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -321,8 +321,8 @@ probably work for you. Don't reinvent the wheel. I've almost forgot to talk about one added benefit: using the HttpFoundation component is the start of better interoperability between all frameworks and -applications using it (as of today `Symfony2`_, `Drupal 8`_, phpBB 4, Silex, -Midgard CMS, `Zikula`_ ...). +applications using it (as of today `Symfony2`_, `Drupal 8`_, `phpBB 4`_, +`Silex`_, `Midgard CMS`_, `Zikula`_ ...). .. _`Twig`: http://twig.sensiolabs.com/ .. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html From 4c2e4f9f5c5efde7d33ee16f63d9d8fe40468c08 Mon Sep 17 00:00:00 2001 From: gnugat Date: Thu, 5 Jan 2012 13:48:30 +0000 Subject: [PATCH 10/74] [part2] Fixinng typo: removing one of the double where --- book/part2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part2.rst b/book/part2.rst index f5a23dddd42..acac7fd9666 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -272,7 +272,7 @@ your servers:: } Using the ``Request::getClientIp()`` method would have given you the right -behavior from day one (and it would have covered the case where where you have +behavior from day one (and it would have covered the case where you have chained proxies):: Date: Fri, 6 Jan 2012 17:42:39 -0200 Subject: [PATCH 11/74] Add missing colon to code-block --- book/part1.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part1.rst b/book/part1.rst index d20f073015b..d8d3d66b3b6 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -88,7 +88,7 @@ classes, how we will reference external dependencies, etc. To store our framework, create a directory somewhere on your machine: -.. code-block: sh +.. code-block:: sh $ mkdir framework $ cd framework From 00e524eb50f2693725b6ff0580a93df561b07a74 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 6 Jan 2012 21:23:22 +0100 Subject: [PATCH 12/74] fixed ambiguity (closes #6) --- book/part2.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part2.rst b/book/part2.rst index acac7fd9666..69a98204ebc 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -193,7 +193,7 @@ charge of sending the response whenever you see fit. .. note:: We haven't explicitly set the ``Content-Type`` header in the rewritten - code as the Response object defaults to ``UTF-8`` by default. + code as the charset of the Response object defaults to ``UTF-8``. With the ``Request`` class, you have all the request information at your fingertips thanks to a nice and simple API:: From 4b39fc03f3dc23230fd5c98e1024413b7fd6c843 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 7 Jan 2012 10:11:34 +0100 Subject: [PATCH 13/74] removed the path to composer.json as JSON doe snot support comments but some people copy/paste this line too --- book/part1.rst | 1 - book/part2.rst | 1 - 2 files changed, 2 deletions(-) diff --git a/book/part1.rst b/book/part1.rst index d8d3d66b3b6..8188a0e9afe 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -110,7 +110,6 @@ your dependencies in a ``composer.json`` file: .. code-block:: json - # framework/composer.json { "require": { "symfony/class-loader": "2.1.*" diff --git a/book/part2.rst b/book/part2.rst index 69a98204ebc..43632a17b84 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -129,7 +129,6 @@ dependency for the project: .. code-block:: json - # framework/composer.json { "require": { "symfony/class-loader": "2.1.*", From 26be81cf539f6e1b76cee65d2f3cabb0f0e1570f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 7 Jan 2012 18:31:20 +0100 Subject: [PATCH 14/74] added part 3 --- book/part3.rst | 248 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 book/part3.rst diff --git a/book/part3.rst b/book/part3.rst new file mode 100644 index 00000000000..bdd627d4430 --- /dev/null +++ b/book/part3.rst @@ -0,0 +1,248 @@ +Create your own framework... on top of the Symfony2 Components (part 3) +======================================================================= + +Up until now, our application is simplistic as there is only one page. To +spice things up a little bit, let's go crazy and add another page that says +goodbye:: + + send(); + +As you can see for yourself, much of the code is exactly the same as the one +we have written for the first page. Let's extract the common code that we can +share between all our pages. Code sharing sounds like a good plan to create +our first "real" framework! + +The PHP way of doing the refactoring would probably be the creation of an +include file:: + + get('name', 'World'); + + $response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'))); + $response->send(); + +And for the "Goodbye" page:: + + setContent('Goodbye!'); + $response->send(); + +We have indeed moved most of the shared code into a central place, but it does +not feel like a good abstraction, doesn't it? First, we still have the +``send()`` method in all pages, then our pages does not look like templates, +and we are still not able to test this code properly. + +Moreover, adding a new page means that we need to create a new PHP script, +which name is exposed to the end user via the URL +(``http://example.com/goodbye.php``): there is a direct mapping between the PHP +script name and the client URL. This is because the dispatching of the request +is done by the web server directly. It might be a good idea to move this +dispatching to our code for better flexibility. This can be easily achieved by +routing all client requests to a single PHP script. + +.. tip:: + + Exposing a single PHP script to the end user is a design pattern called + the "`front controller`_". + +Such a script might look like the following:: + + __DIR__.'/hello.php', + '/bye' => __DIR__.'/bye.php', + ); + + $path = $request->getPathInfo(); + if (isset($map[$path])) { + require $map[$path]; + } else { + $response->setStatusCode(404); + $response->setContent('Not Found'); + } + + $response->send(); + +And here is for instance the new ``hello.php`` script:: + + get('name', 'World'); + $response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'))); + +In the ``front.php`` script, ``$map`` associates URL paths with their +corresponding PHP script paths. + +As a bonus, if the client ask for a path that is not defined in the URL map, +we return a custom 404 page; you are now in control of your website. + +To access a page, you must now use the ``front.php`` script: + +* ``http://example.com/front.php/hello?name=Fabien`` + +* ``http://example.com/front.php/bye`` + +``/hello`` and ``/bye`` are the page *path*s. + +.. tip:: + + Most web servers like Apache or nginx are able to rewrite the incoming + URLs and remove the front controller script so that your users will be + able to type ``http://example.com/hello?name=Fabien``, which looks much + better. + +So, the trick is the usage of the ``Request::getPathInfo()`` method which +returns the path of the Request by removing the front controller script name +including its sub-directories (only if needed -- see above tip). + +.. tip:: + + You don't even need to setup a web server to test the code. Instead, + replace the ``$request = Request::createFromGlobals();`` call to something + like ``$request = Request::create('/hello?name=Fabien');`` where the + argument is the URL path you want to simulate. + +Now that the web server always access the same script (``front.php``) for all +our pages, we can secure our code further by moving all other PHP files +outside the web root directory: + + example.com + ├── composer.json + │ src + │ ├── autoload.php + │ └── pages + │ ├── hello.php + │ └── bye.php + ├── vendor + └── web + └── front.php + +Now, configure your web server root directory to point to ``web/`` and all +other files won't be accessible from the client anymore. + +.. note:: + + For this new structure to work, you will have to adjust some paths in + various PHP files; the changes are left as an exercise for the reader. + +The last thing that is repeated in each page is the call to ``setContent()``. +We can convert all pages to "templates" by just echoing the content and +calling the ``setContent()`` directly from the front controller script:: + + getPathInfo(); + if (isset($map[$path])) { + ob_start(); + include $map[$path]; + $response->setContent(ob_get_clean()); + } else { + $response->setStatusCode(404); + $response->setContent('Not Found'); + } + + // ... + +And the ``hello.php`` script can now be converted to a template:: + + + + get('name', 'World') ?> + + Hello + +We have our framework for today:: + + __DIR__.'/../src/pages/hello.php', + '/bye' => __DIR__.'/../src/pages/bye.php', + ); + + $path = $request->getPathInfo(); + if (isset($map[$path])) { + ob_start(); + include $map[$path]; + $response->setContent(ob_get_clean()); + } else { + $response->setStatusCode(404); + $response->setContent('Not Found'); + } + + $response->send(); + +Adding a new page is a two step process: add an entry in the map and create a +PHP template in ``src/pages/``. From a template, get the Request data via the +``$request`` variable and tweak the Response headers via the ``$response`` +variable. + +.. note:: + + If you decide to stop here, you can probably enhance your framework by + extracting the URL map to a configuration file. + +.. _`front controller`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html#a-front-controller-to-the-rescue From 18d376eb1b42f55f216a6e36cd33c624bb806954 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 7 Jan 2012 20:01:15 +0100 Subject: [PATCH 15/74] updated composer.json --- book/part1.rst | 2 +- book/part2.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/book/part1.rst b/book/part1.rst index 8188a0e9afe..89670f56904 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -112,7 +112,7 @@ your dependencies in a ``composer.json`` file: { "require": { - "symfony/class-loader": "2.1.*" + "symfony/class-loader": "master-dev" } } diff --git a/book/part2.rst b/book/part2.rst index 43632a17b84..fce684c3803 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -131,8 +131,8 @@ dependency for the project: { "require": { - "symfony/class-loader": "2.1.*", - "symfony/http-foundation": "2.1.*" + "symfony/class-loader": "master-dev", + "symfony/http-foundation": "master-dev" } } From a18f8274ea81d5ce7e6076ef987b7e0ba1796bd4 Mon Sep 17 00:00:00 2001 From: jdreesen Date: Sun, 8 Jan 2012 03:19:20 +0100 Subject: [PATCH 16/74] fixed typos --- book/part3.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/part3.rst b/book/part3.rst index bdd627d4430..5ae29f79aeb 100644 --- a/book/part3.rst +++ b/book/part3.rst @@ -70,7 +70,7 @@ and we are still not able to test this code properly. Moreover, adding a new page means that we need to create a new PHP script, which name is exposed to the end user via the URL -(``http://example.com/goodbye.php``): there is a direct mapping between the PHP +(``http://example.com/bye.php``): there is a direct mapping between the PHP script name and the client URL. This is because the dispatching of the request is done by the web server directly. It might be a good idea to move this dispatching to our code for better flexibility. This can be easily achieved by @@ -122,7 +122,7 @@ And here is for instance the new ``hello.php`` script:: In the ``front.php`` script, ``$map`` associates URL paths with their corresponding PHP script paths. -As a bonus, if the client ask for a path that is not defined in the URL map, +As a bonus, if the client asks for a path that is not defined in the URL map, we return a custom 404 page; you are now in control of your website. To access a page, you must now use the ``front.php`` script: From 255577f0154ba86190f64ac25fed7331a5ecf7c2 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 8 Jan 2012 21:44:32 +0100 Subject: [PATCH 17/74] went back to 2.1.* in composer.json files --- book/part1.rst | 2 +- book/part2.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/book/part1.rst b/book/part1.rst index 89670f56904..8188a0e9afe 100644 --- a/book/part1.rst +++ b/book/part1.rst @@ -112,7 +112,7 @@ your dependencies in a ``composer.json`` file: { "require": { - "symfony/class-loader": "master-dev" + "symfony/class-loader": "2.1.*" } } diff --git a/book/part2.rst b/book/part2.rst index fce684c3803..43632a17b84 100644 --- a/book/part2.rst +++ b/book/part2.rst @@ -131,8 +131,8 @@ dependency for the project: { "require": { - "symfony/class-loader": "master-dev", - "symfony/http-foundation": "master-dev" + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*" } } From 358b4c801b333a23c656caaaa926cb0b456ff6bf Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 9 Jan 2012 09:24:32 +0100 Subject: [PATCH 18/74] added part 4 --- book/part4.rst | 253 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 book/part4.rst diff --git a/book/part4.rst b/book/part4.rst new file mode 100644 index 00000000000..72d86506336 --- /dev/null +++ b/book/part4.rst @@ -0,0 +1,253 @@ +Create your own framework... on top of the Symfony2 Components (part 4) +======================================================================= + +Before we start with today's topic, let's refactor our current framework just +a little to make templates even more readable:: + + 'hello', + '/bye' => 'bye', + ); + + $path = $request->getPathInfo(); + if (isset($map[$path])) { + ob_start(); + extract($request->query->all()); + include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]); + $response = new Response(ob_get_clean()); + } else { + $response = new Response('Not Found', 404); + } + + $response->send(); + +As we now extract the request query parameters, simplify the ``hello.php`` +template as follows:: + + + + Hello + +Now, we are in good shape to add new features. + +One very important aspect of any website is the form of its URLs. Thanks to +the URL map, we have decoupled the URL from the code that generates the +associated response, but it is not yet flexible enough. For instance, we might +want to support dynamic paths to allow embedding data directly into the URL +instead of relying on a query string: + + # Before + /hello?name=Fabien + + # After + /hello/Fabien + +To support this feature, we are going to use the Symfony2 Routing component. +As always, add it to ``composer.json`` and run the ``php composer.phar +update`` command to install it:: + +.. code-block:: json + + { + "require": { + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*", + "symfony/routing": "2.1.*" + } + } + +From now on, we are going to use the generated Composer autoloader instead of +our own ``autoload.php``. Remove the ``autoload.php`` file and replace its +reference in ``front.php``:: + + add('hello', new Route('/hello/{name}', array('name' => 'World'))); + $routes->add('bye', new Route('/bye')); + +Each entry in the collection is defined by a name (``hello``) and a ``Route`` +instance, which is defined by a route pattern (``/hello/{name}``) and an array +of default values for route attributes (``array('name' => 'World')``). + +.. note:: + + Read the official `documentation`_ for the Routing component to learn more + about its many features like URL generation, attribute requirements, HTTP + method enforcements, loaders for YAML or XML files, dumpers to PHP or + Apache rewrite rules for enhanced performance, and much more. + +Based on the information stored in the ``RouteCollection`` instance, a +``UrlMatcher`` instance can match URL 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 takes a request path and returns an array of attributes +(notice that the matched route is automatically stored under the special +``_route`` attribute):: + + 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', + ); + +.. note:: + + Even if we don't strictly need the request context in our examples, it is + used in real-world applications to enforce method requirements and more. + +The URL matcher throws an exception when none of the routes match:: + + $matcher->match('/not-found'); + + // throws a Symfony\Component\Routing\Exception\ResourceNotFoundException + +With this knowledge in mind, let's write the new version of our framework:: + + fromRequest($request); + $matcher = new Routing\Matcher\UrlMatcher($routes, $context); + + try { + extract($matcher->match($request->getPathInfo())); + 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(); + +There are a few new things in the code:: + +* Route names are used for template names; + +* ``500`` errors are now managed correctly; + +* Request attributes are extracted to keep our templates simple:: + + + + Hello + +* Routes configuration has been moved to its own file: + + .. code-block:: php + + add('hello', new Routing\Route('/hello/{name}', array('name' => 'World'))); + $routes->add('bye', new Routing\Route('/bye')); + + We now have a clear separation between the configuration (everything + specific to our application in ``app.php``) and the framework (the generic + code that powers our application in ``front.php``). + +With less than 30 lines of code, we have a new framework, more powerful and +more flexible than the previous one. Enjoy! + +Using the Routing component has one big additional benefit: the ability to +generate URLs based on Route definitions. When using both URL matching and URL +generation in your code, changing the URL patterns should have no other +impact. Want to know how to use the generator? Insanely easy:: + + use Symfony\Component\Routing; + + $generator = new Routing\Generator\UrlGenerator($routes, $context); + + echo $generator->generate('hello', array('name' => 'Fabien)); + // outputs /hello/Fabien + +The code should be self-explanatory; and thanks to the context, you can even +generate absolute URLs:: + + echo $generator->generate('hello', array('name' => 'Fabien), true); + // outputs something like http://example.com/somewhere/hello/Fabien + +.. tip:: + + Concerned about performance? Based on your route definitions, create a + highly optimized URL matcher class that can replace the default + ``UrlMatcher``:: + + $dumper = new Routing\Matcher\Dumper\PhpMatcherDumper($routes); + + echo $dumper->dump(); + + Want even more performance? Dump your routes as a set of Apache rewrite + rules:: + + $dumper = new Routing\Matcher\Dumper\ApacheMatcherDumper($routes); + + echo $dumper->dump(); + +.. _`documentation`: http://symfony.com/doc/current/components/routing.html From 1473eec0c2b98c43ef5fa1d27fb507dbdf7030b2 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 9 Jan 2012 10:01:53 +0100 Subject: [PATCH 19/74] fixed typo --- book/part4.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/part4.rst b/book/part4.rst index 72d86506336..8069c75b4df 100644 --- a/book/part4.rst +++ b/book/part4.rst @@ -224,13 +224,13 @@ impact. Want to know how to use the generator? Insanely easy:: $generator = new Routing\Generator\UrlGenerator($routes, $context); - echo $generator->generate('hello', array('name' => 'Fabien)); + echo $generator->generate('hello', array('name' => 'Fabien')); // outputs /hello/Fabien The code should be self-explanatory; and thanks to the context, you can even generate absolute URLs:: - echo $generator->generate('hello', array('name' => 'Fabien), true); + echo $generator->generate('hello', array('name' => 'Fabien'), true); // outputs something like http://example.com/somewhere/hello/Fabien .. tip:: From 7e8da09c2a5a988d859cd6983a33e521eb8fc6bf Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 9 Jan 2012 11:12:48 +0100 Subject: [PATCH 20/74] fixed typo (closes #8) --- book/part4.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/book/part4.rst b/book/part4.rst index 8069c75b4df..a7022699ee1 100644 --- a/book/part4.rst +++ b/book/part4.rst @@ -208,6 +208,8 @@ There are a few new things in the code:: $routes->add('hello', new Routing\Route('/hello/{name}', array('name' => 'World'))); $routes->add('bye', new Routing\Route('/bye')); + return $routes; + We now have a clear separation between the configuration (everything specific to our application in ``app.php``) and the framework (the generic code that powers our application in ``front.php``). From bd3ca8e93907ab4649a848696e0146a75df5345f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 9 Jan 2012 11:42:14 +0100 Subject: [PATCH 21/74] fixed extract() calls --- book/part4.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/part4.rst b/book/part4.rst index a7022699ee1..056934204ce 100644 --- a/book/part4.rst +++ b/book/part4.rst @@ -23,7 +23,7 @@ a little to make templates even more readable:: $path = $request->getPathInfo(); if (isset($map[$path])) { ob_start(); - extract($request->query->all()); + extract($request->query->all(), EXTR_SKIP); include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]); $response = new Response(ob_get_clean()); } else { @@ -169,7 +169,7 @@ With this knowledge in mind, let's write the new version of our framework:: $matcher = new Routing\Matcher\UrlMatcher($routes, $context); try { - extract($matcher->match($request->getPathInfo())); + extract($matcher->match($request->getPathInfo()), EXTR_SKIP); ob_start(); include sprintf(__DIR__.'/../src/pages/%s.php', $_route); From db0ad14d469e2cfcfd27c44cefea9b9ca38c4d77 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 11 Jan 2012 08:39:01 +0100 Subject: [PATCH 22/74] added part 5 --- book/part5.rst | 190 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 book/part5.rst diff --git a/book/part5.rst b/book/part5.rst new file mode 100644 index 00000000000..088f4402171 --- /dev/null +++ b/book/part5.rst @@ -0,0 +1,190 @@ +Create your own framework... on top of the Symfony2 Components (part 5) +======================================================================= + +The astute reader has noticed that our framework hardcodes the way specific +"code" (the templates) is run. For simple pages like the ones we have created +so far, that's not a problem, but if you want to add more logic, you would be +forced to put the logic into the template itself, which is probably not a good +idea, especially if you still have the separation of concerns principle in +mind. + +Let's separate the template code from the logic by adding a new layer: the +controller: *The controller mission is to generate a Response based on the +information conveyed by the client Request.* + +Change the template rendering part of the framework to read as follows:: + + attributes->add($matcher->match($request->getPathInfo())); + $response = call_user_func('render_template', $request); + } catch (Routing\Exception\ResourceNotFoundException $e) { + $response = new Response('Not Found', 404); + } catch (Exception $e) { + $response = new Response('An error occurred', 500); + } + +As the rendering is now done by an external function (``render_template()`` +here), we need to pass to it the attributes extracted from the URL. We could +have passed them as an additional argument to ``render_template()``, but +instead, let's use another feature of the ``Request`` class called +*attributes*: Request attributes lets you attach additional information about +the Request that is not directly related to the HTTP Request data. + +You can now create the ``render_template()`` function, a generic controller +that renders a template when there is no specific logic. To keep the same +template as before, request attributes are extracted before the template is +rendered:: + + function render_template($request) + { + extract($request->attributes->all(), EXTR_SKIP); + ob_start(); + include sprintf(__DIR__.'/../src/pages/%s.php', $_route); + + return new Response(ob_get_clean()); + } + +As ``render_template`` is used as an argument to the PHP ``call_user_func()`` +function, we can replace it with any valid PHP `callbacks`_. This allows us to +use a function, an anonymous function, or a method of a class as a +controller... your choice. + +As a convention, for each route, the associated controller is configured via +the ``_controller`` route attribute:: + + $routes->add('hello', new Routing\Route('/hello/{name}', array( + 'name' => 'World', + '_controller' => 'render_template', + ))); + + try { + $request->attributes->add($matcher->match($request->getPathInfo())); + $response = call_user_func($request->attributes->get('_controller'), $request); + } catch (Routing\Exception\ResourceNotFoundException $e) { + $response = new Response('Not Found', 404); + } catch (Exception $e) { + $response = new Response('An error occurred', 500); + } + +A route can now be associated with any controller and of course, within a +controller, you can still use the ``render_template()`` to render a template:: + + $routes->add('hello', new Routing\Route('/hello/{name}', array( + 'name' => 'World', + '_controller' => function ($request) { + return render_template($request); + } + ))); + +This is rather flexible as you can change the Response object afterwards and +you can even pass additional arguments to the template:: + + $routes->add('hello', new Routing\Route('/hello/{name}', array( + 'name' => 'World', + '_controller' => function ($request) { + // $foo will be available in the template + $request->attributes->set('foo', 'bar'); + + $response = render_template($request); + + // change some header + $response->headers->set('Content-Type', 'text/plain'); + + return $response; + } + ))); + +Here is the updated and improved version of our framework:: + + attributes->all(), EXTR_SKIP); + ob_start(); + include sprintf(__DIR__.'/../src/pages/%s.php', $_route); + + return new Response(ob_get_clean()); + } + + $request = Request::createFromGlobals(); + $routes = include __DIR__.'/../src/app.php'; + + $context = new Routing\RequestContext(); + $context->fromRequest($request); + $matcher = new Routing\Matcher\UrlMatcher($routes, $context); + + try { + $request->attributes->add($matcher->match($request->getPathInfo())); + $response = call_user_func($request->attributes->get('_controller'), $request); + } catch (Routing\Exception\ResourceNotFoundException $e) { + $response = new Response('Not Found', 404); + } catch (Exception $e) { + $response = new Response('An error occurred', 500); + } + + $response->send(); + +To celebrate the birth of our new framework, let's create a brand new +application that needs some simple logic. Our application has one page that +says whether a given year is a leap year or not. When calling +``/is_leap_year``, you get the answer for the current year, but the you can +also specify a year like in ``/is_leap_year/2009``. Being generic, the +framework does not need to be modified in any way, just create a new +``app.php`` file:: + + add('leap_year', new Routing\Route('/is_leap_year/{year}', array( + 'year' => null, + '_controller' => function ($request) { + if (is_leap_year($request->attributes->get('year'))) { + return new Response('Yep, this is a leap year!'); + } + + return new Response('Nope, this is not a leap year.'); + } + ))); + + return $routes; + +The ``is_leap_year()`` function returns ``true`` when the given year is a leap +year, ``false`` otherwise. If the year is null, the current year is tested. +The controller is simple: it gets the year from the request attributes, pass +it to the `is_leap_year()`` function, and according to the return value it +creates a new Response object. + +As always, you can decide to stop here and use the framework as is; it's +probably all you need to create simple websites like those fancy one-page +`websites`_ and hopefully a few others. + +.. _`callbacks`: http://php.net/callback#language.types.callback +.. _`websites`: http://kottke.org/08/02/single-serving-sites From 0ce2a839aa6298dff7d573aec578d6b40df1e61d Mon Sep 17 00:00:00 2001 From: jdreesen Date: Wed, 11 Jan 2012 16:19:58 +0100 Subject: [PATCH 23/74] fixed some typos --- book/part5.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/book/part5.rst b/book/part5.rst index 088f4402171..f657ceb5901 100644 --- a/book/part5.rst +++ b/book/part5.rst @@ -9,8 +9,8 @@ idea, especially if you still have the separation of concerns principle in mind. Let's separate the template code from the logic by adding a new layer: the -controller: *The controller mission is to generate a Response based on the -information conveyed by the client Request.* +controller: *The controller's mission is to generate a Response based on the +information conveyed by the client's Request.* Change the template rendering part of the framework to read as follows:: @@ -142,7 +142,7 @@ Here is the updated and improved version of our framework:: To celebrate the birth of our new framework, let's create a brand new application that needs some simple logic. Our application has one page that says whether a given year is a leap year or not. When calling -``/is_leap_year``, you get the answer for the current year, but the you can +``/is_leap_year``, you get the answer for the current year, but you can also specify a year like in ``/is_leap_year/2009``. Being generic, the framework does not need to be modified in any way, just create a new ``app.php`` file:: From fdb195c18ba1f8d209ab9e1f35bb1667ce406d48 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 13 Jan 2012 12:08:22 +0100 Subject: [PATCH 24/74] added part 6 --- book/part6.rst | 205 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 book/part6.rst diff --git a/book/part6.rst b/book/part6.rst new file mode 100644 index 00000000000..d70c31ddc20 --- /dev/null +++ b/book/part6.rst @@ -0,0 +1,205 @@ +Create your own framework... on top of the Symfony2 Components (part 6) +======================================================================= + +You might think that our framework is already pretty solid and you are +probably right. But let's see how we can improve it nonetheless. + +Right now, all our examples use procedural code, but remember that controllers +can be any valid PHP callbacks. Let's convert our controller to a proper +class:: + + class LeapYearController + { + public function indexAction($request) + { + if (is_leap_year($request->attributes->get('year'))) { + return new Response('Yep, this is a leap year!'); + } + + return new Response('Nope, this is not a leap year.'); + } + } + +Update the route definition accordingly:: + + $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array( + 'year' => null, + '_controller' => array(new LeapYearController(), 'indexAction'), + ))); + +The move is pretty straightforward and makes a lot of sense as soon as you +create more pages but you might have noticed a non-desirable side-effect... +The ``LeapYearController`` class is *always* instantiated, even if the +requested URL does not match the ``leap_year`` route. This is bad for one main +reason: performance wise, all controllers for all routes must now be +instantiated for every request. It would be better if controllers were +lazy-loaded so that only the controller associated with the matched route is +instantiated. + +To solve this issue, and a bunch more, let's install and use the HttpKernel +component:: + + { + "require": { + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*", + "symfony/routing": "2.1.*", + "symfony/http-kernel": "2.1.*" + } + } + +The HttpKernel component has many interesting features, but the one we need +right now is the *controller resolver*. A controller resolver knows how to +determine the controller to execute and the arguments to pass to it, based on +a Request object. All controller resolvers implement the following interface:: + + namespace Symfony\Component\HttpKernel\Controller; + + interface ControllerResolverInterface + { + function getController(Request $request); + + function getArguments(Request $request, $controller); + } + +The ``getController()`` method relies on the same convention as the one we +have defined earlier: the ``_controller`` request attribute must contain the +controller associated with the Request. Besides the built-in PHP callbacks, +``getController()`` also supports strings composed of a class name followed by +two colons and a method name as a valid callback, like 'class::method':: + + $routes->add('leap_year', new Routing\Route('/is_leap_year/{year}', array( + 'year' => null, + '_controller' => 'LeapYearController::indexAction', + ))); + +To make this code work, modify the framework code to use the controller +resolver from HttpKernel:: + + use Symfony\Component\HttpKernel; + + $resolver = new HttpKernel\Controller\ControllerResolver(); + + $controller = $resolver->getController($request); + $arguments = $resolver->getArguments($request, $controller); + + $response = call_user_func_array($controller, $arguments); + +.. note:: + + As an added bonus, the controller resolver properly handles the error + management for you: when you forget to define a ``_controller`` attribute + for a Route for instance. + +Now, let's see how the controller arguments are guessed. ``getArguments()`` +introspects the controller signature to determine which arguments to pass to +it by using the native PHP `reflection`_. + +The ``indexAction()`` method needs the Request object as an argument. +```getArguments()`` knows when to inject it properly if it is type-hinted +correctly:: + + public function indexAction(Request $request) + + // won't work + public function indexAction($request) + +More interesting, ``getArguments()`` is also able to inject any Request +attribute; the argument just needs to have the same name as the corresponding +attribute:: + + public function indexAction($year) + +You can also inject the Request and some attributes at the same time (as the +matching is done on the argument name or a type hint, the arguments order does +not matter):: + + public function indexAction(Request $request, $year) + + public function indexAction($year, Request $request) + +Finally, you can also define default values for any argument that matches an +optional attribute of the Request:: + + public function indexAction($year = 2012) + +Let's just inject the ``$year`` request attribute for our controller:: + + class LeapYearController + { + public function indexAction($year) + { + if (is_leap_year($year)) { + return new Response('Yep, this is a leap year!'); + } + + return new Response('Nope, this is not a leap year.'); + } + } + +The controller resolver also takes care of validating the controller callable +and its arguments. In case of a problem, it throws an exception with a nice +message explaining the problem (the controller class does not exist, the +method is not defined, an argument has no matching attribute, ...). + +.. note:: + + With the great flexibility of the default controller resolver, you might + wonder why someone would want to create another one (why would there be an + interface if not). Two examples: in Symfony2, ``getController()`` is + enhanced to support `controllers as services`_; and in + `FrameworkExtraBundle`_, ``getArguments()`` is enhanced to support + parameter converters, where request attributes are converted to objects + automatically. + +Let's conclude with the new version of our framework:: + + attributes->all()); + ob_start(); + include sprintf(__DIR__.'/../src/pages/%s.php', $_route); + + return new Response(ob_get_clean()); + } + + $request = Request::createFromGlobals(); + $routes = include __DIR__.'/../src/app.php'; + + $context = new Routing\RequestContext(); + $context->fromRequest($request); + $matcher = new Routing\Matcher\UrlMatcher($routes, $context); + $resolver = new HttpKernel\Controller\ControllerResolver(); + + try { + $request->attributes->add($matcher->match($request->getPathInfo())); + + $controller = $resolver->getController($request); + $arguments = $resolver->getArguments($request, $controller); + + $response = call_user_func_array($controller, $arguments); + } catch (Routing\Exception\ResourceNotFoundException $e) { + $response = new Response('Not Found', 404); + } catch (Exception $e) { + $response = new Response('An error occurred', 500); + } + + $response->send(); + +Think about it once more: our framework is more robust and more flexible than +ever and it still has less than 40 lines of code. + +.. _`reflection`: http://php.net/reflection +.. _`FrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html +.. _`controllers as services`: http://symfony.com/doc/current/cookbook/controller/service.html From 97743fb879f1868965228cd3823a48175e5a5fec Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 15 Jan 2012 10:50:03 +0100 Subject: [PATCH 25/74] added part 7 --- book/part7.rst | 188 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 book/part7.rst diff --git a/book/part7.rst b/book/part7.rst new file mode 100644 index 00000000000..7679675e9eb --- /dev/null +++ b/book/part7.rst @@ -0,0 +1,188 @@ +Create your own framework... on top of the Symfony2 Components (part 7) +======================================================================= + +One down-side of our framework right now is that we need to copy and paste the +code in ``front.php`` each time we create a new website. 40 lines of code is +not that much, but it would be nice if we could wrap this code into a proper +class. It would bring us better *reusability* and easier testing to name just +a few benefits. + +If you have a closer look at the code, ``front.php`` has one input, the +Request, and one output, the Response. Our framework class will follow this +simple principle: the logic is about creating the Response associated with a +Request. + +As the Symfony2 components requires PHP 5.3, let's create our very own +namespace for our framework: ``Simplex``. + +Move the request handling logic into its own ``Simple\\Framework`` class:: + + matcher = $matcher; + $this->resolver = $resolver; + } + + public function handle(Request $request) + { + try { + $request->attributes->add($this->matcher->match($request->getPathInfo())); + + $controller = $this->resolver->getController($request); + $arguments = $this->resolver->getArguments($request, $controller); + + return call_user_func_array($controller, $arguments); + } catch (Routing\Exception\ResourceNotFoundException $e) { + return new Response('Not Found', 404); + } catch (Exception $e) { + return new Response('An error occurred', 500); + } + } + } + +And update ``example.com/web/front.php`` accordingly:: + + fromRequest($request); + $matcher = new Routing\Matcher\UrlMatcher($routes, $context); + $resolver = new HttpKernel\Controller\ControllerResolver(); + + $framework = new Simplex\Framework($matcher, $resolver); + $response = $framework->handle($request); + + $response->send(); + +To wrap up the refactoring, let's move everything but routes definition from +``example.com/src/app.php`` into yet another namespace: ``Calendar``. + +For the classes defined under the ``Simplex`` and ``Calendar`` namespaces to +be autoloaded, update the ``composer.json`` file: + +.. code-block:: json + + { + "require": { + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*", + "symfony/routing": "2.1.*", + "symfony/http-kernel": "2.1.*" + }, + "autoload": { + "psr-0": { "Simplex": "src/", "Calendar": "src/" } + } + } + +.. note:: + + For the autoloader to be updated, run ``php composer.phar update``. + +Move the controller to ``Calendar\\Controller\\LeapYearController``:: + + isLeapYear($year)) { + return new Response('Yep, this is a leap year!'); + } + + return new Response('Nope, this is not a leap year.'); + } + } + +And move the ``is_leap_year()`` function to its own class too:: + + add('leap_year', new Routing\Route('/is_leap_year/{year}', array( + 'year' => null, + '_controller' => 'Calendar\\Controller\\LeapYearController::indexAction', + ))); + +To sum up, here is the new file layout: + + example.com + ├── composer.json + │ src + │ ├── app.php + │ └── Simplex + │ └── Framework.php + │ └── Calendar + │ └── Controller + │ │ └── LeapYearController.php + │ └── Model + │ └── LeapYear.php + ├── vendor + └── web + └── front.php + +That's it! Our application has now four different layers and each of them has +a well defined goal: + +* ``web/front.php``: The front controller; the only exposed PHP code that + makes the interface with the client (it gets the Request and sends the + Response) and provides the boiler-plate code to initialize the framework and + our application; + +* ``src/Simplex``: The reusable framework code that abstracts the handling of + incoming Requests (by the way, it makes your controllers/templates easily + testable -- more about that later on); + +* ``src/Calendar``: Our application specific code (the controllers and the + model); + +* ``src/app.php``: The application configuration/framework customization. From 9ffd1863c18d89af957a6b2adec9050021a7dbed Mon Sep 17 00:00:00 2001 From: gnugat Date: Sun, 15 Jan 2012 10:41:07 +0000 Subject: [PATCH 26/74] [part7] Fixing typo: adding 'x' to Simple/Framework class --- book/part7.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part7.rst b/book/part7.rst index 7679675e9eb..1c13da63884 100644 --- a/book/part7.rst +++ b/book/part7.rst @@ -15,7 +15,7 @@ Request. As the Symfony2 components requires PHP 5.3, let's create our very own namespace for our framework: ``Simplex``. -Move the request handling logic into its own ``Simple\\Framework`` class:: +Move the request handling logic into its own ``Simplex\\Framework`` class:: Date: Sun, 15 Jan 2012 17:26:32 +0100 Subject: [PATCH 27/74] fixed typos --- book/part7.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/book/part7.rst b/book/part7.rst index 1c13da63884..979b2fa4fe9 100644 --- a/book/part7.rst +++ b/book/part7.rst @@ -26,6 +26,7 @@ Move the request handling logic into its own ``Simplex\\Framework`` class:: use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Matcher\UrlMatcher; + use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\HttpKernel\Controller\ControllerResolver; class Framework @@ -48,9 +49,9 @@ Move the request handling logic into its own ``Simplex\\Framework`` class:: $arguments = $this->resolver->getArguments($request, $controller); return call_user_func_array($controller, $arguments); - } catch (Routing\Exception\ResourceNotFoundException $e) { + } catch (ResourceNotFoundException $e) { return new Response('Not Found', 404); - } catch (Exception $e) { + } catch (\Exception $e) { return new Response('An error occurred', 500); } } From 5674395eb95d3b1d180b2580ed27585822413211 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 15 Jan 2012 20:05:10 +0100 Subject: [PATCH 28/74] renamed part as we now know that we are going to have more than 10 parts --- book/{part1.rst => part01.rst} | 0 book/{part2.rst => part02.rst} | 0 book/{part3.rst => part03.rst} | 0 book/{part4.rst => part04.rst} | 0 book/{part5.rst => part05.rst} | 0 book/{part6.rst => part06.rst} | 0 book/{part7.rst => part07.rst} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename book/{part1.rst => part01.rst} (100%) rename book/{part2.rst => part02.rst} (100%) rename book/{part3.rst => part03.rst} (100%) rename book/{part4.rst => part04.rst} (100%) rename book/{part5.rst => part05.rst} (100%) rename book/{part6.rst => part06.rst} (100%) rename book/{part7.rst => part07.rst} (100%) diff --git a/book/part1.rst b/book/part01.rst similarity index 100% rename from book/part1.rst rename to book/part01.rst diff --git a/book/part2.rst b/book/part02.rst similarity index 100% rename from book/part2.rst rename to book/part02.rst diff --git a/book/part3.rst b/book/part03.rst similarity index 100% rename from book/part3.rst rename to book/part03.rst diff --git a/book/part4.rst b/book/part04.rst similarity index 100% rename from book/part4.rst rename to book/part04.rst diff --git a/book/part5.rst b/book/part05.rst similarity index 100% rename from book/part5.rst rename to book/part05.rst diff --git a/book/part6.rst b/book/part06.rst similarity index 100% rename from book/part6.rst rename to book/part06.rst diff --git a/book/part7.rst b/book/part07.rst similarity index 100% rename from book/part7.rst rename to book/part07.rst From f00401d48e4f657d842052f6adbf00e7c037691d Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 17 Jan 2012 10:42:31 +0100 Subject: [PATCH 29/74] added part 8 --- book/part08.rst | 189 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 book/part08.rst diff --git a/book/part08.rst b/book/part08.rst new file mode 100644 index 00000000000..d47b9217b3c --- /dev/null +++ b/book/part08.rst @@ -0,0 +1,189 @@ +Create your own framework... on top of the Symfony2 Components (part 8) +======================================================================= + +Some watchful readers pointed out some subtle but nonetheless important bugs +in the framework we have built yesterday. When creating a framework, you must +be sure that it behaves as advertised. If not, all the applications based on +it will exhibit the same bugs. The good news is that whenever you fix a bug, +you are fixing a bunch of applications too. + +Today's mission is to write unit tests for the framework we have created by +using `PHPUnit`_. Create a PHPUnit configuration file in +``example.com/phpunit.xml.dist``: + +.. code-block:: xml + + + + + + + ./tests + + + + +This configuration defines sensible defaults for most PHPUnit settings; more +interesting, the autoloader is used to bootstrap the tests, and tests will be +stored under the ``example.com/tests/`` directory. + +Now, let's write a test for "not found" resources. To avoid the creation of +all dependencies when writing tests and to really just unit-test what we want, +we are going to use `test doubles`_. Test doubles are easier to create when we +rely on interfaces instead of concrete classes. Fortunately, Symfony2 provides +such interfaces for core objects like the URL matcher and the controller +resolver. Modify the framework to make use of them:: + + matcher = $matcher; + $this->resolver = $resolver; + } + + // ... + } + +We are now ready to write our first test:: + + getFrameworkForException(new ResourceNotFoundException()); + + $response = $framework->handle(new Request()); + + $this->assertEquals(404, $response->getStatusCode()); + } + + protected function getFrameworkForException($exception) + { + $matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + $matcher + ->expects($this->once()) + ->method('match') + ->will($this->throwException($exception)) + ; + $resolver = $this->getMock('Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'); + + return new Framework($matcher, $resolver); + } + } + +This test simulates a request that does not match any route. As such, the +``match()`` method returns a ``ResourceNotFoundException`` exception and we +are testing that our framework converts this exception to a 404 response. + +Executing this test is as simple as running ``phpunit`` from the +``example.com`` directory: + +.. code-block:: bash + + $ phpunit + +After the test ran, you should see a green bar. If not, you have a bug +either in the test or in the framework code! + +Adding a unit test for any exception thrown in a controller is just as easy:: + + public function testErrorHandling() + { + $framework = $this->getFrameworkForException(new \RuntimeException()); + + $response = $framework->handle(new Request()); + + $this->assertEquals(500, $response->getStatusCode()); + } + +Last, but not the least, let's write a test for when we actually have a proper +Response:: + + use Symfony\Component\HttpFoundation\Response; + use Symfony\Component\HttpKernel\Controller\ControllerResolver; + + public function testControllerResponse() + { + $matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface'); + $matcher + ->expects($this->once()) + ->method('match') + ->will($this->returnValue(array( + '_route' => 'foo', + 'name' => 'Fabien', + '_controller' => function ($name) { + return new Response('Hello '.$name); + } + ))) + ; + $resolver = new ControllerResolver(); + + $framework = new Framework($matcher, $resolver); + + $response = $framework->handle(new Request()); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertContains('Hello Fabien', $response->getContent()); + } + +In this test, we simulate a route that matches and returns a simple +controller. We check that the response status is 200 and that its content is +the one we have set in the controller. + +To check that we have covered all possible use cases, run the PHPUnit test +coverage feature (you need to enable `XDebug`_ first): + +.. code-block:: bash + + phpunit --coverage-html=cov/ + +Open ``example.com/cov/src_Simplex_Framework.php.html`` in a browser and check +that all the lines for the Framework class are green (it means that they have +been visited when the tests were executed). + +Thanks to the simple object-oriented code that we have written so far, we have +been able to write unit-tests to cover all possible use cases of our +framework; test doubles ensured that we were actually testing our code and not +Symfony2 code. + +Now that we are confident (again) about the code we have written, we can +safely think about the next batch of features we want to add to our framework. + +.. _`PHPUnit`: http://www.phpunit.de/manual/current/en/index.html +.. _`test doubles`: http://www.phpunit.de/manual/current/en/test-doubles.html +.. _`XDebug`: http://xdebug.org/ From 54e1b0829c0f669134b05a7ba7a4586f112c6cef Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 17 Jan 2012 10:47:17 +0100 Subject: [PATCH 30/74] added a note in part 8 --- book/part08.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/book/part08.rst b/book/part08.rst index d47b9217b3c..6e50b557ca7 100644 --- a/book/part08.rst +++ b/book/part08.rst @@ -117,6 +117,12 @@ Executing this test is as simple as running ``phpunit`` from the $ phpunit +.. note:: + + I do not explain how the code works in details as this is not the goal of + this series, but if you don't understand what the hell is going on, I + highly recommend you to read PHPUnit documentation on `test doubles`_. + After the test ran, you should see a green bar. If not, you have a bug either in the test or in the framework code! @@ -170,7 +176,7 @@ coverage feature (you need to enable `XDebug`_ first): .. code-block:: bash - phpunit --coverage-html=cov/ + $ phpunit --coverage-html=cov/ Open ``example.com/cov/src_Simplex_Framework.php.html`` in a browser and check that all the lines for the Framework class are green (it means that they have From bde64c898b3c627c3ac9bde02a14ce2f7e0fb5fd Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 19 Jan 2012 17:25:16 +0100 Subject: [PATCH 31/74] made small tweaks --- book/part09.rst | 329 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100644 book/part09.rst diff --git a/book/part09.rst b/book/part09.rst new file mode 100644 index 00000000000..12652f1498f --- /dev/null +++ b/book/part09.rst @@ -0,0 +1,329 @@ +Create your own framework... on top of the Symfony2 Components (part 9) +======================================================================= + +Our framework is still missing a major characteristic of any good framework: +*extensibility*. Being extensible means that the developer should be able to +easily hook into the framework life cycle to modify the way the request is +handled. + +What kind of hooks are we talking about? Authentication or caching for +instance. To be flexible, hooks must be plug-and-play; the ones you "register" +for an application are different from the next one depending on your specific +needs. Many software have a similar concept like Drupal or Wordpress. In some +languages, there is even a standard like `WSGI`_ in Python or `Rack`_ in Ruby. + +As there is no standard for PHP, we are going to use a well-known design +pattern, the *Observer*, to allow any kind of behaviors to be attached to our +framework; the Symfony2 EventDispatcher Component implements a lightweight +version of this pattern: + +.. code-block:: json + + { + "require": { + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*", + "symfony/routing": "2.1.*", + "symfony/http-kernel": "2.1.*", + "symfony/event-dispatcher": "2.1.*" + }, + "autoload": { + "psr-0": { "Simplex": "src/", "Calendar": "src/" } + } + } + +How does it work? The *dispatcher*, the central object of the event dispatcher +system, notifies *listeners* of an *event* dispatched to it. Put another way: +your code dispatches an event to the dispatcher, the dispatcher notifies all +registered listeners for the event, and each listener do whatever it wants +with the event. + +As an example, let's create a listener that transparently adds the Google +Analytics code to all responses. + +To make it work, the framework must dispatch an event just before returning +the Response instance:: + + matcher = $matcher; + $this->resolver = $resolver; + $this->dispatcher = $dispatcher; + } + + public function handle(Request $request) + { + try { + $request->attributes->add($this->matcher->match($request->getPathInfo())); + + $controller = $this->resolver->getController($request); + $arguments = $this->resolver->getArguments($request, $controller); + + $response = call_user_func_array($controller, $arguments); + } catch (ResourceNotFoundException $e) { + $response = new Response('Not Found', 404); + } catch (\Exception $e) { + $response = new Response('An error occurred', 500); + } + + // dispatch a response event + $this->dispatcher->dispatch('response', new ResponseEvent($response, $request)); + + return $response; + } + } + +Each time the framework handles a Request, a ``ResponseEvent`` event is +now dispatched:: + + response = $response; + $this->request = $request; + } + + public function getResponse() + { + return $this->response; + } + + public function getRequest() + { + return $this->request; + } + } + +The last step is the creation of the dispatcher in the front controller and +the registration of a listener for the ``response`` event:: + + addListener('response', function (Simplex\ResponseEvent $event) { + $response = $event->getResponse(); + + if ($response->isRedirection() + || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || 'html' !== $event->getRequest()->getRequestFormat() + ) { + return; + } + + $response->setContent($response->getContent().'GA CODE'); + }); + + $framework = new Simplex\Framework($dispatcher, $matcher, $resolver); + $response = $framework->handle($request); + + $response->send(); + +.. note:: + + The listener is just a proof of concept and you should add the Google + Analytics code just before the body tag. + +As you can see, ``addListener()`` associates a valid PHP callback to a named +event (``response``); the event name must be the same as the one used in the +``dispatch()`` call. + +In the listener, we add the Google Analytics code only if the response is not +a redirection, if the requested format is HTML, and if the response content +type is HTML (these conditions demonstrate the ease of manipulating the +Request and Response data from your code). + +So far so good, but let's add another listener on the same event. Let's say +that I want to set the ``Content-Length`` of the Response if it is not already +set:: + + $dispatcher->addListener('response', function (Simplex\ResponseEvent $event) { + $response = $event->getResponse(); + $headers = $response->headers; + + if (!$headers->has('Content-Length') && !$headers->has('Transfer-Encoding')) { + $headers->set('Content-Length', strlen($response->getContent())); + } + }); + +Depending on whether you have added this piece of code before the previous +listener registration or after it, you will have the wrong or the right value +for the ``Content-Length`` header. Sometimes, the order of the listeners +matter but by default, all listeners are registered with the same priority, +``0``. To tell the dispatcher to run a listener early, change the priority to +a positive number; negative numbers can be used for low priority listeners. +Here, we want the ``Content-Length`` listener to be executed last, so change +the priority to ``-255``:: + + $dispatcher->addListener('response', function (Simplex\ResponseEvent $event) { + $response = $event->getResponse(); + $headers = $response->headers; + + if (!$headers->has('Content-Length') && !$headers->has('Transfer-Encoding')) { + $headers->set('Content-Length', strlen($response->getContent())); + } + }, -255); + +.. tip:: + + When creating your framework, think about priorities (reserve some numbers + for internal listeners for instance) and document them thoroughly. + +Let's refactor the code a bit by moving the Google listener to its own class:: + + getResponse(); + + if ($response->isRedirection() + || ($response->headers->has('Content-Type') && false === strpos($response->headers->get('Content-Type'), 'html')) + || 'html' !== $event->getRequest()->getRequestFormat() + ) { + return; + } + + $response->setContent($response->getContent().'GA CODE'); + } + } + +And do the same with the other listener:: + + getResponse(); + $headers = $response->headers; + + if (!$headers->has('Content-Length') && !$headers->has('Transfer-Encoding')) { + $headers->set('Content-Length', strlen($response->getContent())); + } + } + } + +Our front controller should now look like the following:: + + $dispatcher = new EventDispatcher(); + $dispatcher->addListener('response', array(new Simplex\ContentLengthListener(), 'onResponse'), -255); + $dispatcher->addListener('response', array(new Simplex\GoogleListener(), 'onResponse')); + +Even if the code is now nicely wrapped in classes, there is still a slight +issue: the knowledge of the priorities is "hardcoded" in the front controller, +instead of being in the listeners themselves. For each application, you have +to remember to set the appropriate priorities. Moreover, the listener method +names are also exposed here, which means that refactoring our listeners would +mean changing all the applications that rely on those listeners. Of course, +there is a solution: use subscribers instead of listeners:: + + $dispatcher = new EventDispatcher(); + $dispatcher->addSubscriber(new Simplex\ContentLengthListener()); + $dispatcher->addSubscriber(new Simplex\GoogleListener()); + +A subscriber knowns about all the events it is interested in and pass this +information to the dispatcher via the ``getSubscribedEvents()`` method. Have a +look at the new version of the ``GoogleListener``:: + + 'onResponse'); + } + } + +And here is the new version of ``ContentLengthListener``:: + + array('onResponse', -255)); + } + } + +.. tip:: + + A single subscriber can host as many listeners as you want on as many + events as needed. + +To make your framework truly flexible, don't hesitate to add more events; and +to make it more awesome out of the box, add more listeners. Again, this series +is not about creating a generic framework, but one that is tailored to your +needs. Stop whenever you see fit, and further evolve the code from there. + +.. _`WSGI`: http://www.python.org/dev/peps/pep-0333/#middleware-components-that-play-both-sides +.. _`Rack`: http://rack.rubyforge.org/ From a635d89d285588204550d02059260bba906e20e8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 21 Jan 2012 08:43:01 +0100 Subject: [PATCH 32/74] added part 10 --- book/part10.rst | 193 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 book/part10.rst diff --git a/book/part10.rst b/book/part10.rst new file mode 100644 index 00000000000..3f3cc14e37f --- /dev/null +++ b/book/part10.rst @@ -0,0 +1,193 @@ +Create your own framework... on top of the Symfony2 Components (part 10) +======================================================================== + +In the conclusion of the second part of this series, I've talked about one +great benefit of using the Symfony2 components: the *interoperability* between +all frameworks and applications using them. Let's do a big step towards this +goal by making our framework implement ``HttpKernelInterface``:: + + namespace Symfony\Component\HttpKernel; + + interface HttpKernelInterface + { + /** + * @return Response A Response instance + */ + function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true); + } + +``HttpKernelInterface`` is probably the most important piece of code in the +HttpKernel component, no kidding. Frameworks and applications that implement +this interface are fully interoperable. Moreover, a lot of great features will +come with it for free. + +Update your framework so that it implements this interface:: + + handle($request)->send(); + +That's all it takes to add HTTP caching support to our framework. Isn't it +amazing? + +Configuring the cache needs to be done via HTTP cache headers. For instance, +to cache a response for 10 seconds, use the ``Response::setTtl()`` method:: + + // example.com/src/Calendar/Controller/LeapYearController.php + + public function indexAction(Request $request, $year) + { + $leapyear = new LeapYear(); + if ($leapyear->isLeapYear($year)) { + $response = new Response('Yep, this is a leap year!'); + } else { + $response = new Response('Nope, this is not a leap year.'); + } + + $response->setTtl(10); + + return $response; + } + +.. tip:: + + If, like me, you are running your framework from the command line by + simulating requests (``Request::create('/is_leap_year/2012')``), you can + easily debug Response instances by dumping their string representation + (``echo $response;``) as it displays all headers as well as the response + content. + +To validate that it works correctly, add a random number to the response +content and check that the number only changes every 10 seconds:: + + $response = new Response('Yep, this is a leap year! '.rand()); + +.. note:: + + When deploying to your production environment, keep using the Symfony2 + reverse proxy (great for shared hosting) or even better, switch to a more + efficient reverse proxy like `Varnish`_. + +Using HTTP cache headers to manage your application cache is very powerful and +allows you to finely tuned your caching strategy as you can use both the +expiration and the validation models of the HTTP specification. If you are not +comfortable with these concepts, I highly recommend you to read the `HTTP +caching`_ chapter of the Symfony2 documentation. + +The Response class contains many other methods that let's you configure the +HTTP cache very easily. One of the most powerful is ``setCache()`` as it +abstracts the most frequently used caching strategies into one simple array:: + + $date = date_create_from_format('Y-m-d H:i:s', '2005-10-15 10:00:00'); + + $response->setCache(array( + 'public' => true, + 'etag' => 'abcde', + 'last_modified' => $date, + 'max_age' => 10, + 's_maxage' => 10, + )); + + // it is equivalent to the following code + $response->setPublic(); + $response->setEtag('abcde'); + $response->setLastModified($date); + $response->setMaxAge(10); + $response->setSharedMaxAge(10); + +When using the validation model, the ``isNotModified()`` method allows you to +easily cut on the response time by short-circuiting the response generation as +early as possible:: + + $response->setETag('whatever_you_compute_as_an_etag'); + + if ($response->isNotModified($request)) { + return $response; + } + $response->setContent('The computed content of the response'); + + return $response; + +Using HTTP caching is great, but what if you cannot cache the whole page? What +if you can cache everything but some sidebar that is more dynamic that the +rest of the content? Edge Side Includes (`ESI`_) to the rescue! Instead of +generating the whole content in one go, ESI allows you to mark a region of a +page as being the content of a sub-request call:: + + This is the content of your page + + Is 2012 a leap year? + + Some other content + +For ESI tags to be supported by HttpCache, you need to pass it an instance of +the ``ESI`` class. The ``ESI`` class automatically parses ESI tags and makes +sub-requests to convert them to their proper content:: + + use Symfony\Component\HttpKernel\HttpCache\ESI; + + $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI()); + +.. note:: + + For ESI to work, you need to use a reverse proxy that supports it like the + Symfony2 implementation. `Varnish`_ is the best alternative and it is + Open-Source. + +When using complex HTTP caching strategies and/or many ESI include tags, it +can be hard to understand why and when a resource should be cached or not. To +ease debugging, you can enable the debug mode:: + + $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI(), array('debug' => true)); + +The debug mode adds a ``X-Symfony-Cache`` header to each response that +describes what the cache layer did: + +.. code-block:: text + + X-Symfony-Cache: GET /is_leap_year/2012: stale, invalid, store + + X-Symfony-Cache: GET /is_leap_year/2012: fresh + +HttpCache has many some features like support for the +``stale-while-revalidate`` and ``stale-if-error`` HTTP Cache-Control +extensions as defined in RFC 5861. + +With the addition of a single interface, our framework can now benefit from +the many features built into the HttpKernel component; HTTP caching being just +one of them but an important one as it can make your applications fly! + +.. _`HTTP caching`: http://symfony.com/doc/current/book/http_cache.html +.. _`ESI`: http://en.wikipedia.org/wiki/Edge_Side_Includes +.. _`Varnish`: https://www.varnish-cache.org/ From 111cac05d43e9e9dc09de682b6cebaaff51f09b9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 21 Jan 2012 10:02:33 +0100 Subject: [PATCH 33/74] removed some use statement to be more consistent with previous parts --- book/part10.rst | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/book/part10.rst b/book/part10.rst index 3f3cc14e37f..f6f9ff6d25e 100644 --- a/book/part10.rst +++ b/book/part10.rst @@ -50,11 +50,8 @@ PHP; it implements ``HttpKernelInterface`` and wraps another // example.com/web/front.php - use Symfony\Component\HttpKernel\HttpCache\HttpCache; - use Symfony\Component\HttpKernel\HttpCache\Store; - $framework = new Simplex\Framework($dispatcher, $matcher, $resolver); - $framework = new HttpCache($framework, new Store(__DIR__.'/../cache')); + $framework = new HttpKernel\HttpCache\HttpCache($framework, new HttpKernel\HttpCache\Store(__DIR__.'/../cache')); $framework->handle($request)->send(); @@ -155,9 +152,11 @@ For ESI tags to be supported by HttpCache, you need to pass it an instance of the ``ESI`` class. The ``ESI`` class automatically parses ESI tags and makes sub-requests to convert them to their proper content:: - use Symfony\Component\HttpKernel\HttpCache\ESI; - - $framework = new HttpCache($framework, new Store(__DIR__.'/../cache'), new ESI()); + $framework = new HttpKernel\HttpCache\HttpCache( + $framework, + new HttpKernel\HttpCache\Store(__DIR__.'/../cache'), + new HttpKernel\HttpCache\ESI() + ); .. note:: From 8399581800e9fb85a387e7da1ea3d04e5367cc18 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 21 Jan 2012 15:24:55 +0100 Subject: [PATCH 34/74] moved the Context::fromRequest() code to the Framework class --- book/part07.rst | 3 ++- book/part09.rst | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/book/part07.rst b/book/part07.rst index 979b2fa4fe9..605d84e6f8a 100644 --- a/book/part07.rst +++ b/book/part07.rst @@ -42,6 +42,8 @@ Move the request handling logic into its own ``Simplex\\Framework`` class:: public function handle(Request $request) { + $this->matcher->getContext()->fromRequest($request); + try { $request->attributes->add($this->matcher->match($request->getPathInfo())); @@ -69,7 +71,6 @@ And update ``example.com/web/front.php`` accordingly:: $routes = include __DIR__.'/../src/app.php'; $context = new Routing\RequestContext(); - $context->fromRequest($request); $matcher = new Routing\Matcher\UrlMatcher($routes, $context); $resolver = new HttpKernel\Controller\ControllerResolver(); diff --git a/book/part09.rst b/book/part09.rst index 12652f1498f..55674f2b204 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -72,6 +72,8 @@ the Response instance:: public function handle(Request $request) { + $this->matcher->getContext()->fromRequest($request); + try { $request->attributes->add($this->matcher->match($request->getPathInfo())); From 02aab54e7c26d84eef2c2020fb29940de39b9f92 Mon Sep 17 00:00:00 2001 From: Arnaud Kleinpeter Date: Sat, 21 Jan 2012 16:31:40 +0100 Subject: [PATCH 35/74] Corrected some english errors --- book/part10.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/book/part10.rst b/book/part10.rst index f6f9ff6d25e..7e32d77970a 100644 --- a/book/part10.rst +++ b/book/part10.rst @@ -97,12 +97,12 @@ content and check that the number only changes every 10 seconds:: efficient reverse proxy like `Varnish`_. Using HTTP cache headers to manage your application cache is very powerful and -allows you to finely tuned your caching strategy as you can use both the +allows you to tune finely your caching strategy as you can use both the expiration and the validation models of the HTTP specification. If you are not comfortable with these concepts, I highly recommend you to read the `HTTP caching`_ chapter of the Symfony2 documentation. -The Response class contains many other methods that let's you configure the +The Response class contains many other methods that let you configure the HTTP cache very easily. One of the most powerful is ``setCache()`` as it abstracts the most frequently used caching strategies into one simple array:: @@ -179,7 +179,7 @@ describes what the cache layer did: X-Symfony-Cache: GET /is_leap_year/2012: fresh -HttpCache has many some features like support for the +HttpCache has many features like support for the ``stale-while-revalidate`` and ``stale-if-error`` HTTP Cache-Control extensions as defined in RFC 5861. From fda9900e03620550a8eb5ec65cff94fe8d26dba3 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 22 Jan 2012 08:08:37 +0100 Subject: [PATCH 36/74] fixed pygments code name for json --- book/part01.rst | 2 +- book/part02.rst | 2 +- book/part07.rst | 2 +- book/part09.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index 8188a0e9afe..cebc1b98503 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -108,7 +108,7 @@ To install the Symfony2 Components that we need for our framework, we are going to use `Composer`_, a project dependency manager for PHP. First, list your dependencies in a ``composer.json`` file: -.. code-block:: json +.. code-block:: javascript { "require": { diff --git a/book/part02.rst b/book/part02.rst index 43632a17b84..f2bc1502025 100644 --- a/book/part02.rst +++ b/book/part02.rst @@ -127,7 +127,7 @@ layer. To use this component, open the ``composer.json`` file and add it as a dependency for the project: -.. code-block:: json +.. code-block:: javascript { "require": { diff --git a/book/part07.rst b/book/part07.rst index 605d84e6f8a..384cd12ed5c 100644 --- a/book/part07.rst +++ b/book/part07.rst @@ -85,7 +85,7 @@ To wrap up the refactoring, let's move everything but routes definition from For the classes defined under the ``Simplex`` and ``Calendar`` namespaces to be autoloaded, update the ``composer.json`` file: -.. code-block:: json +.. code-block:: javascript { "require": { diff --git a/book/part09.rst b/book/part09.rst index 55674f2b204..c35b871565a 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -17,7 +17,7 @@ pattern, the *Observer*, to allow any kind of behaviors to be attached to our framework; the Symfony2 EventDispatcher Component implements a lightweight version of this pattern: -.. code-block:: json +.. code-block:: javascript { "require": { From 76e45f99af29f7b8ef3149de9d88ba7a8a6825fe Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sun, 22 Jan 2012 08:11:38 +0100 Subject: [PATCH 37/74] fixed markup --- book/part03.rst | 2 ++ book/part04.rst | 2 +- book/part07.rst | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/book/part03.rst b/book/part03.rst index 5ae29f79aeb..be9e5124fee 100644 --- a/book/part03.rst +++ b/book/part03.rst @@ -155,6 +155,8 @@ Now that the web server always access the same script (``front.php``) for all our pages, we can secure our code further by moving all other PHP files outside the web root directory: +.. code-block:: text + example.com ├── composer.json │ src diff --git a/book/part04.rst b/book/part04.rst index 056934204ce..6a168aeac89 100644 --- a/book/part04.rst +++ b/book/part04.rst @@ -55,7 +55,7 @@ instead of relying on a query string: To support this feature, we are going to use the Symfony2 Routing component. As always, add it to ``composer.json`` and run the ``php composer.phar -update`` command to install it:: +update`` command to install it: .. code-block:: json diff --git a/book/part07.rst b/book/part07.rst index 384cd12ed5c..964c897b403 100644 --- a/book/part07.rst +++ b/book/part07.rst @@ -157,6 +157,8 @@ Don't forget to update the ``example.com/src/app.php`` file accordingly:: To sum up, here is the new file layout: +.. code-block:: text + example.com ├── composer.json │ src From 09969d900f3a73de62678c47b69fcc0f95eb1abc Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 23 Jan 2012 14:31:16 +0100 Subject: [PATCH 38/74] added part 11 --- book/part11.rst | 212 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 book/part11.rst diff --git a/book/part11.rst b/book/part11.rst new file mode 100644 index 00000000000..4e581fc063e --- /dev/null +++ b/book/part11.rst @@ -0,0 +1,212 @@ +Create your own framework... on top of the Symfony2 Components (part 11) +======================================================================== + +If you were to use our framework right now, you would probably have to add +support for custom error messages. Right now, we have 404 and 500 error +support but the responses are hardcoded in the framework itself. Making them +customizable is easy enough though: dispatch a new event and listen to it. +Doing it right means that the listener have to call a regular controller. But +what if the error controller throws an exception? You will end up in an +infinite loop. There should be an easier way, right? + +Enter the ``HttpKernel`` class. Instead of solving the same problem over and +over again and instead of reinventing the wheel each time, the ``HttpKernel`` +class is a generic, extensible, and flexible implementation of +``HttpKernelInterface``. + +This class is very similar to the framework class we have written so far: it +dispatches events at some strategic points during the handling of the request, +it uses a controller resolver to choose the controller to dispatch the request +to, and as an added bonus, it takes care of edge cases and provides great +feedback when a problem arises. + +Here is the new framework code:: + + addSubscriber(new HttpKernel\EventListener\RouterListener($matcher)); + + $framework = new Simplex\Framework($dispatcher, $resolver); + + $response = $framework->handle($request); + $response->send(); + +``RouterListener`` is an implementation of the same logic we had in our +framework: it matches the incoming request and populates the request +attributes with route parameters. + +Our code is now much more concise and surprisingly more robust and more +powerful than ever. For instance, use the built-in ``ExceptionListener`` to +make your error management configurable:: + + $errorHandler = function (HttpKernel\Exception\FlattenException $exception) { + $msg = 'Something went wrong! ('.$exception->getMessage().')'; + + return new Response($msg, $exception->getStatusCode()); + }); + $dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler); + +``ExceptionListener`` gives you a ``FlattenException`` instance instead of the +thrown ``Exception`` instance to ease exception manipulation and display. It +can take any valid controller as an exception handler, so you can create an +ErrorController class instead of using a Closure:: + + $listener = new HttpKernel\EventListener\ExceptionListener('Calendar\\Controller\\ErrorController::exceptionAction'); + $dispatcher->addSubscriber($listener); + +The error controller reads as follows:: + + getMessage().')'; + + return new Response($msg, $exception->getStatusCode()); + } + } + +Voilà! Clean and customizable error management without efforts. And of course, +of your controller throws an exception, HttpKernel will handle it nicely. + +In part 2, we have talked about the ``Response::prepare()`` method, which +ensures that a Response is compliant with the HTTP specification. It is +probably a good idea to always call it just before sending the Response to the +client; that's what the ``ResponseListener`` does:: + + $dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8')); + +This one was easy too! Let's take another one: do you want out of the box +support for streamed responses? Just subscribe to +``StreamedResponseListener``:: + + $dispatcher->addSubscriber(new HttpKernel\EventListener\StreamedResponseListener()); + +And in your controller, return a ``StreamedResponse`` instance instead of a +``Response`` instance. + +.. tip:: + + Read the `Internals`_ chapter of the Symfony2 documentation to learn more + about the events dispatched by HttpKernel and how they allow you to change + the flow of a request. + +Now, let's create a listener, one that allows a controller to return a string +instead of a full Response object:: + + class LeapYearController + { + public function indexAction(Request $request, $year) + { + $leapyear = new LeapYear(); + if ($leapyear->isLeapYear($year)) { + return 'Yep, this is a leap year! '; + } + + return 'Nope, this is not a leap year.'; + } + } + +To implement this feature, we are going to listen to the ``kernel.view`` +event, which is triggered just after the controller has been called. Its goal +is to convert the controller return value to a proper Response instance, but +only if needed:: + + getControllerResult(); + + if (is_string($response)) { + $event->setResponse(new Response($response)); + } + } + + public static function getSubscribedEvents() + { + return array('kernel.view' => 'onView'); + } + } + +The code is simple because the ``kernel.view`` event is only triggered when +the controller return value is not a Response and because setting the response +on the event stops the event propagation (our listener cannot interfere with +other view listeners). + +Don't forget to register it in the front controller:: + + $dispatcher->addSubscriber(new Simplex\StringResponseListener()); + +.. note:: + + If you forget to register the subscriber, HttpKernel will throws an + exception with a nice message: ``The controller must return a response + (Nope, this is not a leap year. given).``. + +At this point, our whole framework code is as compact as possible and it is +mainly composed of an assembling of existing libraries. Extending is a matter +of registering event listeners/subscribers. + +Hopefully, you now have a better understanding of why the simple looking +``HttpKernelInterface`` is so powerful. Its default implementation, +``HttpKernel``, gives you access to a lot of cool features, ready to be used +out of the box, with no efforts. And because HttpKernel is actually the code +that powers the Symfony2 and Silex frameworks, you have the best of both +worlds: a custom framework, tailored to your needs, but based on a rock-solid +and well maintained low-level architecture that has been proven to work for +many websites; a code that has been audited for security issues and that has +proven to scale well. + +.. _`Internals`: http://symfony.com/doc/current/book/internals.html#events From a3f0b3129c594d51c6a14dadfe2bb8fec0050ed4 Mon Sep 17 00:00:00 2001 From: Arnaud Kleinpeter Date: Mon, 23 Jan 2012 15:48:59 +0100 Subject: [PATCH 39/74] Corrected few typos --- book/part11.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/book/part11.rst b/book/part11.rst index 4e581fc063e..debd0fca5bc 100644 --- a/book/part11.rst +++ b/book/part11.rst @@ -5,7 +5,7 @@ If you were to use our framework right now, you would probably have to add support for custom error messages. Right now, we have 404 and 500 error support but the responses are hardcoded in the framework itself. Making them customizable is easy enough though: dispatch a new event and listen to it. -Doing it right means that the listener have to call a regular controller. But +Doing it right means that the listener has to call a regular controller. But what if the error controller throws an exception? You will end up in an infinite loop. There should be an easier way, right? @@ -108,7 +108,7 @@ The error controller reads as follows:: } Voilà! Clean and customizable error management without efforts. And of course, -of your controller throws an exception, HttpKernel will handle it nicely. +if your controller throws an exception, HttpKernel will handle it nicely. In part 2, we have talked about the ``Response::prepare()`` method, which ensures that a Response is compliant with the HTTP specification. It is @@ -191,12 +191,12 @@ Don't forget to register it in the front controller:: .. note:: - If you forget to register the subscriber, HttpKernel will throws an + If you forget to register the subscriber, HttpKernel will throw an exception with a nice message: ``The controller must return a response (Nope, this is not a leap year. given).``. At this point, our whole framework code is as compact as possible and it is -mainly composed of an assembling of existing libraries. Extending is a matter +mainly composed of an assembly of existing libraries. Extending is a matter of registering event listeners/subscribers. Hopefully, you now have a better understanding of why the simple looking From 16b5b09a27ac3da9746b89ee8e97de40e28172c0 Mon Sep 17 00:00:00 2001 From: William DURAND Date: Tue, 24 Jan 2012 15:25:37 +0100 Subject: [PATCH 40/74] Fixed typo --- book/part11.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part11.rst b/book/part11.rst index debd0fca5bc..1d55c653aac 100644 --- a/book/part11.rst +++ b/book/part11.rst @@ -76,7 +76,7 @@ make your error management configurable:: return new Response($msg, $exception->getStatusCode()); }); - $dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler); + $dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler)); ``ExceptionListener`` gives you a ``FlattenException`` instance instead of the thrown ``Exception`` instance to ease exception manipulation and display. It From 9bc692f1046a7b11decb2aca6c4fc1e31baffaf9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Jan 2012 07:44:02 +0100 Subject: [PATCH 41/74] added part 12 --- book/part12.rst | 256 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 book/part12.rst diff --git a/book/part12.rst b/book/part12.rst new file mode 100644 index 00000000000..10a9b34f35a --- /dev/null +++ b/book/part12.rst @@ -0,0 +1,256 @@ +Create your own framework... on top of the Symfony2 Components (part 12) +======================================================================== + +In the last installment of this series, we have emptied the +``Simplex\\Framework`` class by extending the ``HttpKernel`` class from +Symfony. Seeing this empty class, you might be tempted to move some code from +the front controller to it:: + + addSubscriber(new HttpKernel\EventListener\RouterListener($matcher)); + $dispatcher->addSubscriber(new HttpKernel\EventListener\ResponseListener('UTF-8')); + + parent::__construct($dispatcher, $resolver); + } + } + +The front controller code would become more concise:: + + handle($request)->send(); + +Having a more concise front controller means that you can have more than one +for a single application. Why would it be useful? To allow having different +configuration for the development environment and the production one for +instance. In the development environment, you might want to have error +reporting turned on and errors displayed in the browser to ease debugging:: + + ini_set('display_errors', 1); + error_reporting(-1); + +... but you certainly won't want that same configuration on the production +environment. Having two different front controllers gives you the opportunity +to have a slightly different configuration for each of them. + +So, moving code from the front controller to the framework class makes our +framework more configurable, but at the same time, it introduces a lot of +issues: + +* We are not able to register custom listeners anymore as the dispatcher is + not available outside the Framework class (an easy workaround could be the + adding of a ``Framework::getEventDispatcher()`` method); + +* We have lost the flexibility we had before; you cannot change the + implementation of the ``UrlMatcher`` or of the ``ControllerResolver`` + anymore; + +* Related to the previous point, we cannot test our framework easily anymore + as it's impossible to mock internal objects; + +* We cannot change the charset passed to ResponseListener anymore (a + workaround could be to pass it as a constructor argument). + +The previous code did not exhibit the same issues because we used dependency +injection; all dependencies of our objects were injected into their +constructors (for instance, the event dispatcher were injected into the +framework so that we had total control of its creation and configuration). + +Does it means that we have to make a choice between flexibility, +customization, ease of testing and not having to copy and paste the same code +into each application front controller? As you might expect, there is a +solution. We can solve all these issues and some more by using the Symfony2 +dependency injection container: + +.. code-block:: json + + { + "require": { + "symfony/class-loader": "2.1.*", + "symfony/http-foundation": "2.1.*", + "symfony/routing": "2.1.*", + "symfony/http-kernel": "2.1.*", + "symfony/event-dispatcher": "2.1.*", + "symfony/dependency-injection": "2.1.*" + }, + "autoload": { + "psr-0": { "Simplex": "src/", "Calendar": "src/" } + } + } + +Create a new file to host the dependency injection container configuration:: + + register('context', 'Symfony\Component\Routing\RequestContext'); + $sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher') + ->setArguments(array($routes, new Reference('context'))) + ; + $sc->register('resolver', 'Symfony\Component\HttpKernel\Controller\ControllerResolver'); + + $sc->register('listener.router', 'Symfony\Component\HttpKernel\EventListener\RouterListener') + ->setArguments(array(new Reference('matcher'))) + ; + $sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener') + ->setArguments(array('UTF-8')) + ; + $sc->register('listener.exception', 'Symfony\Component\HttpKernel\EventListener\ExceptionListener') + ->setArguments(array('Calendar\\Controller\\ErrorController::exceptionAction')) + ; + $sc->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher') + ->addMethodCall('addSubscriber', array(new Reference('listener.router'))) + ->addMethodCall('addSubscriber', array(new Reference('listener.response'))) + ->addMethodCall('addSubscriber', array(new Reference('listener.exception'))) + ; + $sc->register('framework', 'Simplex\Framework') + ->setArguments(array(new Reference('dispatcher'), new Reference('resolver'))) + ; + + return $sc; + +The goal of this file is to configure your objects and their dependencies. +Nothing is instantiated during this configuration step. This is purely a +static description of the objects you need to manipulate and how to create +them. Objects will be created on-demand when you access them from the +container or when the container needs them to create other objects. + +For instance, to create the router listener, we tell Symfony that its class +name is ``Symfony\Component\HttpKernel\EventListener\RouterListeners``, and +that its constructor takes a matcher object (``new Reference('matcher')``). As +you can see, each object is referenced by a name, a string that uniquely +identifies each object. The name allows us to get an object and to reference +it in other object definitions. + +.. note:: + + By default, every time you get an object from the container, it returns + the exact same instance. That's because a container manages your "global" + objects. + +The front controller is now only about wiring everything together:: + + get('framework')->handle($request); + + $response->send(); + +.. note:: + + If you want a light alternative for your container, consider `Pimple`_, a + simple dependency injection container in about 60 lines of PHP code. + +Now, here is how you can register a custom listener in the front controller:: + + $sc->register('listener.string_response', 'Simplex\StringResponseListener'); + $sc->getDefinition('dispatcher') + ->addMethodCall('addSubscriber', array(new Reference('listener.string_response'))) + ; + +Beside describing your objects, the dependency injection container can also be +configured via parameters. Let's create one that defines if we are in debug +mode or not:: + + $sc->setParameter('debug', true); + + echo $sc->getParameter('debug'); + +These parameters can be used when defining object definitions. Let's make the +charset configurable:: + + $sc->register('listener.response', 'Symfony\Component\HttpKernel\EventListener\ResponseListener') + ->setArguments(array('%charset%')) + ; + +After this change, you must set the charset before using the response listener +object:: + + $sc->setParameter('charset', 'UTF-8'); + +Instead of relying on the convention that the routes are defined by the +``$routes`` variables, let's use a parameter again:: + + $sc->register('matcher', 'Symfony\Component\Routing\Matcher\UrlMatcher') + ->setArguments(array('%routes%', new Reference('context'))) + ; + +And the related change in the front controller:: + + $sc->setParameter('routes', include __DIR__.'/../src/app.php'); + +We have obviously barely scratched the surface of what you can do with the +container: from class names as parameters, to overriding existing object +definitions, from scope support to dumping a container to a plain PHP class, +and much more. The Symfony dependency injection container is really powerful +and is able to manage any kind of PHP classes. + +Don't yell at me if you don't want to have a dependency injection container in +your framework. If you don't like it, don't use it. It's your framework, not +mine. + +This is (already) the last part of my series on creating a framework on top of +the Symfony2 components. I'm aware that many topics have not been covered in +great details, but hopefully it gives you enough information to get started on +your own and to better understand how the Symfony2 framework works internally. + +If you want to learn more, I highly recommend you to read the source code of +the Silex micro-framework, and especially its `Application`_ class. + +Have fun! + +~~ FIN ~~ + +*P.S.:* If there is enough interest (leave a comment on this post), I might +write some more articles on specific topics (using a configuration file for +routing, using HttpKernel debugging tools, using the build-in client to +simulate a browser are some of the topics that come to my mind for instance). + +.. _`Pimple`: https://github.com/fabpot/Pimple +.. _`Application`: https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php From 26bef8d4d799513e995718fba6dbf38fa7b94ca1 Mon Sep 17 00:00:00 2001 From: Amitay Horwitz Date: Wed, 25 Jan 2012 09:44:00 +0200 Subject: [PATCH 42/74] Fixed typos --- book/part12.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/book/part12.rst b/book/part12.rst index 10a9b34f35a..b3eadcce4a3 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -229,7 +229,7 @@ We have obviously barely scratched the surface of what you can do with the container: from class names as parameters, to overriding existing object definitions, from scope support to dumping a container to a plain PHP class, and much more. The Symfony dependency injection container is really powerful -and is able to manage any kind of PHP classes. +and is able to manage any kind of PHP class. Don't yell at me if you don't want to have a dependency injection container in your framework. If you don't like it, don't use it. It's your framework, not @@ -249,7 +249,7 @@ Have fun! *P.S.:* If there is enough interest (leave a comment on this post), I might write some more articles on specific topics (using a configuration file for -routing, using HttpKernel debugging tools, using the build-in client to +routing, using HttpKernel debugging tools, using the built-in client to simulate a browser are some of the topics that come to my mind for instance). .. _`Pimple`: https://github.com/fabpot/Pimple From a8a2da0639b1d59dd500fac0f99a4cb85736ca1e Mon Sep 17 00:00:00 2001 From: Amitay Horwitz Date: Wed, 25 Jan 2012 09:44:00 +0200 Subject: [PATCH 43/74] Fixed part 12 typos --- book/part12.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/book/part12.rst b/book/part12.rst index 10a9b34f35a..97db3371a2d 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -2,7 +2,7 @@ Create your own framework... on top of the Symfony2 Components (part 12) ======================================================================== In the last installment of this series, we have emptied the -``Simplex\\Framework`` class by extending the ``HttpKernel`` class from +``Simplex\Framework`` class by extending the ``HttpKernel`` class from Symfony. Seeing this empty class, you might be tempted to move some code from the front controller to it:: @@ -229,7 +229,7 @@ We have obviously barely scratched the surface of what you can do with the container: from class names as parameters, to overriding existing object definitions, from scope support to dumping a container to a plain PHP class, and much more. The Symfony dependency injection container is really powerful -and is able to manage any kind of PHP classes. +and is able to manage any kind of PHP class. Don't yell at me if you don't want to have a dependency injection container in your framework. If you don't like it, don't use it. It's your framework, not @@ -249,7 +249,7 @@ Have fun! *P.S.:* If there is enough interest (leave a comment on this post), I might write some more articles on specific topics (using a configuration file for -routing, using HttpKernel debugging tools, using the build-in client to +routing, using HttpKernel debugging tools, using the built-in client to simulate a browser are some of the topics that come to my mind for instance). .. _`Pimple`: https://github.com/fabpot/Pimple From db5937455d4c77b79923b03eea5debbcdb4fc22e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Jan 2012 09:53:45 +0100 Subject: [PATCH 44/74] fixed typo --- book/part12.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part12.rst b/book/part12.rst index 10a9b34f35a..2ff56c15bea 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -151,7 +151,7 @@ them. Objects will be created on-demand when you access them from the container or when the container needs them to create other objects. For instance, to create the router listener, we tell Symfony that its class -name is ``Symfony\Component\HttpKernel\EventListener\RouterListeners``, and +name is ``Symfony\Component\HttpKernel\EventListener\RouterListener``, and that its constructor takes a matcher object (``new Reference('matcher')``). As you can see, each object is referenced by a name, a string that uniquely identifies each object. The name allows us to get an object and to reference From d907d46c3c672f01f7ce3d55fd8c5dde0540f140 Mon Sep 17 00:00:00 2001 From: Arnaud Kleinpeter Date: Wed, 25 Jan 2012 14:44:04 +0100 Subject: [PATCH 45/74] Fixed typos --- book/part12.rst | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/book/part12.rst b/book/part12.rst index 10a9b34f35a..ac921fc0c89 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -3,8 +3,8 @@ Create your own framework... on top of the Symfony2 Components (part 12) In the last installment of this series, we have emptied the ``Simplex\\Framework`` class by extending the ``HttpKernel`` class from -Symfony. Seeing this empty class, you might be tempted to move some code from -the front controller to it:: +the eponymous component. Seeing this empty class, you might be tempted to move +some code from the front controller to it:: handle($request)->send(); -Having a more concise front controller means that you can have more than one +Having a concise front controller allows you to have several front controllers for a single application. Why would it be useful? To allow having different configuration for the development environment and the production one for instance. In the development environment, you might want to have error @@ -78,16 +78,20 @@ issues: * Related to the previous point, we cannot test our framework easily anymore as it's impossible to mock internal objects; -* We cannot change the charset passed to ResponseListener anymore (a +* We cannot change the charset passed to ``ResponseListener`` anymore (a workaround could be to pass it as a constructor argument). The previous code did not exhibit the same issues because we used dependency injection; all dependencies of our objects were injected into their -constructors (for instance, the event dispatcher were injected into the +constructors (for instance, the event dispatchers were injected into the framework so that we had total control of its creation and configuration). -Does it means that we have to make a choice between flexibility, +Does it mean that we have to make a choice between flexibility, +<<<<<<< .merge_file_8XJJxl customization, ease of testing and not having to copy and paste the same code +======= +customization, ease of testing and not to copy and paste the same code +>>>>>>> .merge_file_kv38Yk into each application front controller? As you might expect, there is a solution. We can solve all these issues and some more by using the Symfony2 dependency injection container: @@ -249,7 +253,7 @@ Have fun! *P.S.:* If there is enough interest (leave a comment on this post), I might write some more articles on specific topics (using a configuration file for -routing, using HttpKernel debugging tools, using the build-in client to +routing, using HttpKernel debugging tools, using the built-in client to simulate a browser are some of the topics that come to my mind for instance). .. _`Pimple`: https://github.com/fabpot/Pimple From 1f43dbfc5e550ab82ef64d6a4723e46067bb15c9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 25 Jan 2012 15:55:08 +0100 Subject: [PATCH 46/74] removed conflict merge --- book/part12.rst | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/book/part12.rst b/book/part12.rst index f8174c24a58..83ac3977f5d 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -86,15 +86,11 @@ injection; all dependencies of our objects were injected into their constructors (for instance, the event dispatchers were injected into the framework so that we had total control of its creation and configuration). -Does it mean that we have to make a choice between flexibility, -<<<<<<< .merge_file_8XJJxl -customization, ease of testing and not having to copy and paste the same code -======= -customization, ease of testing and not to copy and paste the same code ->>>>>>> .merge_file_kv38Yk -into each application front controller? As you might expect, there is a -solution. We can solve all these issues and some more by using the Symfony2 -dependency injection container: +Does it mean that we have to make a choice between flexibility, customization, +ease of testing and not to copy and paste the same code into each application +front controller? As you might expect, there is a solution. We can solve all +these issues and some more by using the Symfony2 dependency injection +container: .. code-block:: json From 006b1e2efbfb75de4529f8e573b6aa22234b529c Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Fri, 27 Jan 2012 20:37:33 +0100 Subject: [PATCH 47/74] fixed markup --- book/part04.rst | 2 +- book/part12.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book/part04.rst b/book/part04.rst index 6a168aeac89..7437aade78c 100644 --- a/book/part04.rst +++ b/book/part04.rst @@ -57,7 +57,7 @@ To support this feature, we are going to use the Symfony2 Routing component. As always, add it to ``composer.json`` and run the ``php composer.phar update`` command to install it: -.. code-block:: json +.. code-block:: javascript { "require": { diff --git a/book/part12.rst b/book/part12.rst index 83ac3977f5d..08978478bc3 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -92,7 +92,7 @@ front controller? As you might expect, there is a solution. We can solve all these issues and some more by using the Symfony2 dependency injection container: -.. code-block:: json +.. code-block:: javascript { "require": { From de69a874c1ecfd229c7a886918e918f39e7eef36 Mon Sep 17 00:00:00 2001 From: Stefan hr Berder Date: Tue, 7 Feb 2012 04:48:25 +0100 Subject: [PATCH 48/74] HttpKernel name can't be imported twice, if importing only Symfony\Component\HttpKernel\HttpKernel there will be problems later with HttpKernel subclasses (HttpKernel\Controller\ControllerResolver first and the others following). Could use 'use ... as ...' but I don't like it. --- book/part12.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/book/part12.rst b/book/part12.rst index 83ac3977f5d..deea9d642b7 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -12,12 +12,11 @@ some code from the front controller to it:: namespace Simplex; - use Symfony\Component\HttpKernel\HttpKernel; use Symfony\Component\Routing; use Symfony\Component\HttpKernel; use Symfony\Component\EventDispatcher\EventDispatcher; - class Framework extends HttpKernel + class Framework extends HttpKernel\HttpKernel { public function __construct($routes) { From 400c087fee27ad39156b297fc0766f4c44d83eba Mon Sep 17 00:00:00 2001 From: Stefan hr Berder Date: Tue, 7 Feb 2012 05:09:57 +0100 Subject: [PATCH 49/74] add framework code as people would probably modify it following first code example (putting object creations in src/Simplex/Framework.php) --- book/part12.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/book/part12.rst b/book/part12.rst index deea9d642b7..8ef48ef0ee7 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -181,6 +181,20 @@ The front controller is now only about wiring everything together:: $response->send(); +As all the objects are now created in the dependency injection container, the framework code should be the previous simple version:: + + Date: Tue, 7 Feb 2012 05:56:28 +0100 Subject: [PATCH 50/74] updated titles --- book/part01.rst | 4 ++-- book/part02.rst | 4 ++-- book/part03.rst | 4 ++-- book/part04.rst | 4 ++-- book/part05.rst | 4 ++-- book/part06.rst | 4 ++-- book/part07.rst | 4 ++-- book/part08.rst | 4 ++-- book/part09.rst | 4 ++-- book/part10.rst | 4 ++-- book/part11.rst | 4 ++-- book/part12.rst | 4 ++-- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index cebc1b98503..092db5c4b51 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 1) -======================================================================= +Introduction +============ Symfony2 is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems. diff --git a/book/part02.rst b/book/part02.rst index f2bc1502025..d76ee84d7c8 100644 --- a/book/part02.rst +++ b/book/part02.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 2) -======================================================================= +The HttpFoundation Component +============================ Before we dive into the code refactoring, I first want to step back and take a look at why you would like to use a framework instead of keeping your diff --git a/book/part03.rst b/book/part03.rst index be9e5124fee..abf548815d0 100644 --- a/book/part03.rst +++ b/book/part03.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 3) -======================================================================= +The Front Controller +==================== Up until now, our application is simplistic as there is only one page. To spice things up a little bit, let's go crazy and add another page that says diff --git a/book/part04.rst b/book/part04.rst index 7437aade78c..4222366b4b5 100644 --- a/book/part04.rst +++ b/book/part04.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 4) -======================================================================= +The Routing Component +===================== Before we start with today's topic, let's refactor our current framework just a little to make templates even more readable:: diff --git a/book/part05.rst b/book/part05.rst index f657ceb5901..03b5d854716 100644 --- a/book/part05.rst +++ b/book/part05.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 5) -======================================================================= +Templating +========== The astute reader has noticed that our framework hardcodes the way specific "code" (the templates) is run. For simple pages like the ones we have created diff --git a/book/part06.rst b/book/part06.rst index d70c31ddc20..aaa40de1057 100644 --- a/book/part06.rst +++ b/book/part06.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 6) -======================================================================= +The HttpKernel Component: the Controller Resolver +================================================= You might think that our framework is already pretty solid and you are probably right. But let's see how we can improve it nonetheless. diff --git a/book/part07.rst b/book/part07.rst index 964c897b403..e6e0d76e56a 100644 --- a/book/part07.rst +++ b/book/part07.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 7) -======================================================================= +The Separation of Concerns +========================== One down-side of our framework right now is that we need to copy and paste the code in ``front.php`` each time we create a new website. 40 lines of code is diff --git a/book/part08.rst b/book/part08.rst index 6e50b557ca7..35c00de6cf0 100644 --- a/book/part08.rst +++ b/book/part08.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 8) -======================================================================= +Unit Testing +============ Some watchful readers pointed out some subtle but nonetheless important bugs in the framework we have built yesterday. When creating a framework, you must diff --git a/book/part09.rst b/book/part09.rst index c35b871565a..f06d1011d8b 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 9) -======================================================================= +The EventDispatcher Component +============================= Our framework is still missing a major characteristic of any good framework: *extensibility*. Being extensible means that the developer should be able to diff --git a/book/part10.rst b/book/part10.rst index 7e32d77970a..b302ada39fe 100644 --- a/book/part10.rst +++ b/book/part10.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 10) -======================================================================== +The HttpKernel Component: HttpKernelInterface +============================================= In the conclusion of the second part of this series, I've talked about one great benefit of using the Symfony2 components: the *interoperability* between diff --git a/book/part11.rst b/book/part11.rst index 1d55c653aac..c165a04c1bf 100644 --- a/book/part11.rst +++ b/book/part11.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 11) -======================================================================== +The HttpKernel Component: The HttpKernel Class +============================================== If you were to use our framework right now, you would probably have to add support for custom error messages. Right now, we have 404 and 500 error diff --git a/book/part12.rst b/book/part12.rst index 08978478bc3..d1a6fa2ca6d 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -1,5 +1,5 @@ -Create your own framework... on top of the Symfony2 Components (part 12) -======================================================================== +The DependencyInjection Component +================================= In the last installment of this series, we have emptied the ``Simplex\\Framework`` class by extending the ``HttpKernel`` class from From aae0705d9cb986f1529a8834106a6b2e6ff4897f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 7 Feb 2012 05:57:11 +0100 Subject: [PATCH 51/74] fixed CS --- book/part12.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/part12.rst b/book/part12.rst index 5442570909f..0a2b7642cd5 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -181,7 +181,8 @@ The front controller is now only about wiring everything together:: $response->send(); -As all the objects are now created in the dependency injection container, the framework code should be the previous simple version:: +As all the objects are now created in the dependency injection container, the +framework code should be the previous simple version:: Date: Sat, 25 Feb 2012 14:48:31 +0100 Subject: [PATCH 52/74] Fixed one typo. --- book/part11.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part11.rst b/book/part11.rst index c165a04c1bf..ab17a38049c 100644 --- a/book/part11.rst +++ b/book/part11.rst @@ -75,7 +75,7 @@ make your error management configurable:: $msg = 'Something went wrong! ('.$exception->getMessage().')'; return new Response($msg, $exception->getStatusCode()); - }); + }; $dispatcher->addSubscriber(new HttpKernel\EventListener\ExceptionListener($errorHandler)); ``ExceptionListener`` gives you a ``FlattenException`` instance instead of the From 8e7106d7309c57d5c112158ed32986018748802c Mon Sep 17 00:00:00 2001 From: ubick Date: Mon, 30 Apr 2012 13:53:48 +0200 Subject: [PATCH 53/74] Fixed a typo in part02.rst ( Date: Fri, 14 Sep 2012 09:15:23 +0300 Subject: [PATCH 54/74] Fix little typo --- book/part09.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part09.rst b/book/part09.rst index f06d1011d8b..28057eaad06 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -275,7 +275,7 @@ there is a solution: use subscribers instead of listeners:: $dispatcher->addSubscriber(new Simplex\ContentLengthListener()); $dispatcher->addSubscriber(new Simplex\GoogleListener()); -A subscriber knowns about all the events it is interested in and pass this +A subscriber knows about all the events it is interested in and pass this information to the dispatcher via the ``getSubscribedEvents()`` method. Have a look at the new version of the ``GoogleListener``:: From 91e46f6e7307eedc0ab337209693a0b853ba5dc7 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Sat, 29 Sep 2012 00:29:01 +0200 Subject: [PATCH 55/74] removed the paragraph about CS as we now have standards --- book/part01.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index 092db5c4b51..b4f2bf65402 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -93,14 +93,6 @@ To store our framework, create a directory somewhere on your machine: $ mkdir framework $ cd framework -Coding Standards -~~~~~~~~~~~~~~~~ - -Before anyone starts a flame war about coding standards and why the one used -here suck hard, let's all admit that this does not matter that much as long as -you are consistent. For this book, we are going to use the `Symfony2 Coding -Standards`_. - Components Installation ~~~~~~~~~~~~~~~~~~~~~~~ From 799e963a7e7a00afcdbefaa500a3b418bfd5ae5a Mon Sep 17 00:00:00 2001 From: Bilal Amarni Date: Wed, 3 Oct 2012 12:58:58 +0200 Subject: [PATCH 56/74] updated composer autoload path --- book/part04.rst | 4 ++-- book/part05.rst | 2 +- book/part06.rst | 2 +- book/part09.rst | 2 +- book/part11.rst | 2 +- book/part12.rst | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/book/part04.rst b/book/part04.rst index 4222366b4b5..4983d5a9072 100644 --- a/book/part04.rst +++ b/book/part04.rst @@ -75,7 +75,7 @@ reference in ``front.php``:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; // ... @@ -155,7 +155,7 @@ With this knowledge in mind, let's write the new version of our framework:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/book/part05.rst b/book/part05.rst index 03b5d854716..a10d9a7a21a 100644 --- a/book/part05.rst +++ b/book/part05.rst @@ -106,7 +106,7 @@ Here is the updated and improved version of our framework:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/book/part06.rst b/book/part06.rst index aaa40de1057..abce4d8e88a 100644 --- a/book/part06.rst +++ b/book/part06.rst @@ -158,7 +158,7 @@ Let's conclude with the new version of our framework:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/book/part09.rst b/book/part09.rst index 28057eaad06..efca2b9d421 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -136,7 +136,7 @@ the registration of a listener for the ``response`` event:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; // ... diff --git a/book/part11.rst b/book/part11.rst index ab17a38049c..79ea6bf3849 100644 --- a/book/part11.rst +++ b/book/part11.rst @@ -40,7 +40,7 @@ And the new front controller:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/book/part12.rst b/book/part12.rst index 482cd0c0fd8..8f08531116e 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -38,7 +38,7 @@ The front controller code would become more concise:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; @@ -168,7 +168,7 @@ The front controller is now only about wiring everything together:: // example.com/web/front.php - require_once __DIR__.'/../vendor/.composer/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; From c88c20a20473214b890bd17416fdc922877ee975 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 15 Oct 2012 14:50:50 +0200 Subject: [PATCH 57/74] removed usage of the ClassLoader component in favor of Composer --- book/part01.rst | 64 ++++++++++--------------------------------------- book/part02.rst | 27 +++++++-------------- book/part03.rst | 9 ++++--- book/part04.rst | 15 +----------- book/part06.rst | 1 - book/part07.rst | 1 - book/part08.rst | 2 +- book/part09.rst | 1 - book/part12.rst | 1 - 9 files changed, 26 insertions(+), 95 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index b4f2bf65402..c6342833315 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -64,9 +64,9 @@ Propel, or plain-old PDO for the Model; PHP or Twig for the View). When creating a framework, following the MVC pattern is not the right goal. The main goal should be the Separation of Concerns; I actually think that this is the only design pattern that you should really care about. The fundamental -principles of the Symfony2 Components are centered around the HTTP -specification. As such, the frameworks that we are going to create should be -more accurately labelled as HTTP frameworks or Request/Response frameworks. +principles of the Symfony2 Components are focused on the HTTP specification. +As such, the frameworks that we are going to create should be more accurately +labelled as HTTP frameworks or Request/Response frameworks. Before we start --------------- @@ -97,20 +97,18 @@ Components Installation ~~~~~~~~~~~~~~~~~~~~~~~ To install the Symfony2 Components that we need for our framework, we are -going to use `Composer`_, a project dependency manager for PHP. First, list -your dependencies in a ``composer.json`` file: +going to use `Composer`_, a project dependency manager for PHP. Create a +``composer.json`` file, where we will list our dependencies: .. code-block:: javascript { "require": { - "symfony/class-loader": "2.1.*" } } -Here, we tell Composer that our project depends on the Symfony2 ClassLoader -component, version 2.1.0 or later. To actually install the project -dependencies, download the composer binary and run it: +The file is empty for now as we do not depend on anything yet. To install the +project dependencies, download the composer binary and run it: .. code-block:: sh @@ -121,13 +119,7 @@ dependencies, download the composer binary and run it: $ php composer.phar install After running the ``install`` command, you must see a new ``vendor/`` -directory that must contain the Symfony2 ClassLoader code. - -.. note:: - - Even if we highly recommend you the use of Composer, you can also download - the archives of the components directly or use Git submodules. That's - really up to you. +directory. Naming Conventions and Autoloading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -137,42 +129,8 @@ require the file where a class is defined before being able to use it. But with some conventions, we can just let PHP do the hard work for us. Symfony2 follows the de-facto PHP standard, `PSR-0`_, for class names and -autoloading. The Symfony2 ClassLoader Component provides an autoloader that -implements this PSR-0 standard and most of the time, the Symfony2 ClassLoader -is all you need to autoload all your project classes. - -Create an empty autoloader in a new ``autoload.php`` file: - -.. code-block:: php - - register(); - -You can now run the ``autoload.php`` on the CLI, it should not do anything and -should not throw any error: - -.. code-block:: sh - - $ php autoload.php - -.. tip:: - - The Symfony website has more information about the `ClassLoader`_ - component. - -.. note:: - - Composer automatically creates an autoloader for all your installed - dependencies; instead of using the ClassLoader component, you can also - just require ``vendor/.composer/autoload.php``. +autoloading and Composer generates such an autoloader for all the dependencies +it manages; it can be enabled by requiring the ``vendor/autoload.php`` file. Our Project ----------- @@ -183,6 +141,8 @@ start with the simplest web application we can think of in PHP:: registerNamespace('Symfony\\Component\\HttpFoundation', __DIR__.'/vendor/symfony/http-foundation'); - Now, let's rewrite our application by using the ``Request`` and the ``Response`` classes:: @@ -158,7 +147,7 @@ Now, let's rewrite our application by using the ``Request`` and the // framework/index.php - require_once __DIR__.'/autoload.php'; + require_once __DIR__.'/vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/book/part03.rst b/book/part03.rst index abf548815d0..d795d57b565 100644 --- a/book/part03.rst +++ b/book/part03.rst @@ -9,7 +9,7 @@ goodbye:: // framework/bye.php - require_once __DIR__.'/autoload.php'; + require_once __DIR__.'/vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -31,7 +31,7 @@ include file:: // framework/init.php - require_once __DIR__.'/autoload.php'; + require_once __DIR__.'/vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -87,7 +87,7 @@ Such a script might look like the following:: // framework/front.php - require_once __DIR__.'/autoload.php'; + require_once __DIR__.'/vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -160,7 +160,6 @@ outside the web root directory: example.com ├── composer.json │ src - │ ├── autoload.php │ └── pages │ ├── hello.php │ └── bye.php @@ -212,7 +211,7 @@ We have our framework for today:: // example.com/web/front.php - require_once __DIR__.'/../src/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; diff --git a/book/part04.rst b/book/part04.rst index 4983d5a9072..668e98f36c9 100644 --- a/book/part04.rst +++ b/book/part04.rst @@ -8,7 +8,7 @@ a little to make templates even more readable:: // example.com/web/front.php - require_once __DIR__.'/../src/autoload.php'; + require_once __DIR__.'/../vendor/autoload.php'; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -61,24 +61,11 @@ update`` command to install it: { "require": { - "symfony/class-loader": "2.1.*", "symfony/http-foundation": "2.1.*", "symfony/routing": "2.1.*" } } -From now on, we are going to use the generated Composer autoloader instead of -our own ``autoload.php``. Remove the ``autoload.php`` file and replace its -reference in ``front.php``:: - - diff --git a/book/part09.rst b/book/part09.rst index efca2b9d421..3ba1257ed41 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -21,7 +21,6 @@ version of this pattern: { "require": { - "symfony/class-loader": "2.1.*", "symfony/http-foundation": "2.1.*", "symfony/routing": "2.1.*", "symfony/http-kernel": "2.1.*", diff --git a/book/part12.rst b/book/part12.rst index 8f08531116e..4cd4c36dbf7 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -95,7 +95,6 @@ container: { "require": { - "symfony/class-loader": "2.1.*", "symfony/http-foundation": "2.1.*", "symfony/routing": "2.1.*", "symfony/http-kernel": "2.1.*", From 10e27324571a86ec256f7f13829b864cca306293 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 15 May 2013 19:09:49 +0200 Subject: [PATCH 58/74] added an index file --- book/index.rst | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 book/index.rst diff --git a/book/index.rst b/book/index.rst new file mode 100644 index 00000000000..120c334d0f1 --- /dev/null +++ b/book/index.rst @@ -0,0 +1,17 @@ +Create your PHP Framework +========================= + +.. toctree:: + + part01 + part02 + part03 + part04 + part05 + part06 + part07 + part08 + part09 + part10 + part11 + part12 From f6656e49c8215d46d25afafea6da816d65344fe9 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 15 May 2013 19:16:44 +0200 Subject: [PATCH 59/74] removed a note that is not relevant anymore --- book/part12.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/book/part12.rst b/book/part12.rst index 4cd4c36dbf7..0a8e6dc1762 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -258,12 +258,5 @@ the Silex micro-framework, and especially its `Application`_ class. Have fun! -~~ FIN ~~ - -*P.S.:* If there is enough interest (leave a comment on this post), I might -write some more articles on specific topics (using a configuration file for -routing, using HttpKernel debugging tools, using the built-in client to -simulate a browser are some of the topics that come to my mind for instance). - .. _`Pimple`: https://github.com/fabpot/Pimple .. _`Application`: https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php From 249b7041d3eafe0b89f48a14dd76092fb3072df6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 15 May 2013 19:17:26 +0200 Subject: [PATCH 60/74] added missing links --- book/part01.rst | 3 ++- book/part12.rst | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index c6342833315..e27a9de3b12 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -1,7 +1,7 @@ Introduction ============ -Symfony2 is a reusable set of standalone, decoupled, and cohesive PHP +`Symfony2`_ is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems. Instead of using these low-level components, you can use the ready-to-be-used @@ -150,6 +150,7 @@ start with the simplest web application we can think of in PHP:: That's all for the first part of this series. Next time, we will introduce the HttpFoundation Component and see what it brings us. +.. _`Symfony2`: http://symfony.com/ .. _`documentation`: http://symfony.com/doc .. _`Silex`: http://silex.sensiolabs.org/ .. _`autoload`: http://fr.php.net/autoload diff --git a/book/part12.rst b/book/part12.rst index 0a8e6dc1762..19b34a546a3 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -254,9 +254,10 @@ great details, but hopefully it gives you enough information to get started on your own and to better understand how the Symfony2 framework works internally. If you want to learn more, I highly recommend you to read the source code of -the Silex micro-framework, and especially its `Application`_ class. +the `Silex`_ micro-framework, and especially its `Application`_ class. Have fun! .. _`Pimple`: https://github.com/fabpot/Pimple +.. _`Silex`: https://silex.sensiolabs.org/ .. _`Application`: https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php From ca9d5d8e26315b7091f49ac24059cbe7e8702582 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 15 May 2013 19:18:35 +0200 Subject: [PATCH 61/74] removed unused references --- book/part01.rst | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index e27a9de3b12..b65e4f257a1 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -150,11 +150,9 @@ start with the simplest web application we can think of in PHP:: That's all for the first part of this series. Next time, we will introduce the HttpFoundation Component and see what it brings us. -.. _`Symfony2`: http://symfony.com/ -.. _`documentation`: http://symfony.com/doc -.. _`Silex`: http://silex.sensiolabs.org/ -.. _`autoload`: http://fr.php.net/autoload -.. _`Composer`: http://packagist.org/about-composer -.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md -.. _`Symfony2 Coding Standards`: http://symfony.com/doc/current/contributing/code/standards.html -.. _`ClassLoader`: http://symfony.com/doc/current/components/class_loader.html +.. _`Symfony2`: http://symfony.com/ +.. _`documentation`: http://symfony.com/doc +.. _`Silex`: http://silex.sensiolabs.org/ +.. _`autoload`: http://fr.php.net/autoload +.. _`Composer`: http://packagist.org/about-composer +.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md From f3c151cff8b0bd29c3522b41d6230ac9ff298d63 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 15 May 2013 19:27:42 +0200 Subject: [PATCH 62/74] reworded slightly some sentence to convert the text from a series of articles to a book --- book/part01.rst | 12 ++++++------ book/part02.rst | 9 ++++----- book/part04.rst | 4 ++-- book/part08.rst | 14 +++++++------- book/part09.rst | 2 +- book/part10.rst | 2 +- book/part11.rst | 12 ++++++------ book/part12.rst | 17 +++++++++-------- 8 files changed, 36 insertions(+), 36 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index b65e4f257a1..513cb82d243 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -6,7 +6,7 @@ components that solve common web development problems. Instead of using these low-level components, you can use the ready-to-be-used Symfony2 full-stack web framework, which is based on these components... or -you can create your very own framework. This series is about the latter. +you can create your very own framework. This book is about the latter. .. note:: @@ -48,7 +48,7 @@ Symfony2 Components. .. tip:: - If you don't have time to read the whole series, or if you want to get + If you don't have time to read the whole book, or if you want to get started fast, you can also have a look at `Silex`_, a micro-framework based on the Symfony2 Components. The code is rather slim and it leverages many aspects of the Symfony2 Components. @@ -56,8 +56,8 @@ Symfony2 Components. Many modern web frameworks call themselves MVC frameworks. We won't talk about MVC here as the Symfony2 Components are able to create any type of frameworks, not just the ones that follow the MVC architecture. Anyway, if you have a look -at the MVC semantics, this series is about how to create the Controller part -of a framework. For the Model and the View, it really depends on your personal +at the MVC semantics, this book is about how to create the Controller part of +a framework. For the Model and the View, it really depends on your personal taste and I will let you use any existing third-party libraries (Doctrine, Propel, or plain-old PDO for the Model; PHP or Twig for the View). @@ -147,8 +147,8 @@ start with the simplest web application we can think of in PHP:: printf('Hello %s', $input); -That's all for the first part of this series. Next time, we will introduce the -HttpFoundation Component and see what it brings us. +In the next chapter, we are going to introduce the HttpFoundation Component +and see what it brings us. .. _`Symfony2`: http://symfony.com/ .. _`documentation`: http://symfony.com/doc diff --git a/book/part02.rst b/book/part02.rst index b000de0d201..c86da9deabb 100644 --- a/book/part02.rst +++ b/book/part02.rst @@ -15,8 +15,8 @@ framework from scratch. developers; the Internet has already plenty of good resources on that topic. -Even if the "application" we wrote yesterday was simple enough, it suffers -from a few problems:: +Even if the "application" we wrote in the previous chapter was simple enough, +it suffers from a few problems:: Date: Wed, 15 May 2013 20:06:43 +0200 Subject: [PATCH 63/74] updated code for Symfony 2.3 and made minor tweaks to the text --- book/part01.rst | 25 +++++++++++++------------ book/part02.rst | 17 +++++++++-------- book/part03.rst | 12 ++++++------ book/part04.rst | 13 +++---------- book/part05.rst | 12 ++++++------ book/part06.rst | 8 ++++---- book/part07.rst | 8 ++++---- book/part09.rst | 10 +++++----- book/part11.rst | 2 +- book/part12.rst | 12 ++++++------ 10 files changed, 57 insertions(+), 62 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index 513cb82d243..b22945c1365 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -53,20 +53,21 @@ Symfony2 Components. based on the Symfony2 Components. The code is rather slim and it leverages many aspects of the Symfony2 Components. -Many modern web frameworks call themselves MVC frameworks. We won't talk about -MVC here as the Symfony2 Components are able to create any type of frameworks, -not just the ones that follow the MVC architecture. Anyway, if you have a look -at the MVC semantics, this book is about how to create the Controller part of -a framework. For the Model and the View, it really depends on your personal -taste and I will let you use any existing third-party libraries (Doctrine, -Propel, or plain-old PDO for the Model; PHP or Twig for the View). +Many modern web frameworks advertize themselves as being MVC frameworks. We +won't talk about the MVC pattern as the Symfony2 Components are able to create +any type of frameworks, not just the ones that follow the MVC architecture. +Anyway, if you have a look at the MVC semantics, this book is about how to +create the Controller part of a framework. For the Model and the View, it +really depends on your personal taste and I will let you use any existing +third-party libraries (Doctrine, Propel, or plain-old PDO for the Model; PHP +or Twig for the View). When creating a framework, following the MVC pattern is not the right goal. -The main goal should be the Separation of Concerns; I actually think that this -is the only design pattern that you should really care about. The fundamental -principles of the Symfony2 Components are focused on the HTTP specification. -As such, the frameworks that we are going to create should be more accurately -labelled as HTTP frameworks or Request/Response frameworks. +The main goal should be the **Separation of Concerns**; I actually think that +this is the only design pattern that you should really care about. The +fundamental principles of the Symfony2 Components are focused on the HTTP +specification. As such, the frameworks that we are going to create should be +more accurately labelled as HTTP frameworks or Request/Response frameworks. Before we start --------------- diff --git a/book/part02.rst b/book/part02.rst index c86da9deabb..226547a6ab1 100644 --- a/book/part02.rst +++ b/book/part02.rst @@ -129,7 +129,7 @@ dependency for the project: { "require": { - "symfony/http-foundation": "2.1.*" + "symfony/http-foundation": "~2.3" } } @@ -170,8 +170,8 @@ first outputs the HTTP headers followed by the content). Before the ``send()`` call, we should have added a call to the ``prepare()`` method (``$response->prepare($request);``) to ensure that our Response were compliant with the HTTP specification. For instance, if - we were to call the page with the ``HEAD`` method, it would have removed - the content of the Response. + we were to call the page with the ``HEAD`` method, it would remove the + content of the Response. The main difference with the previous code is that you have total control of the HTTP messages. You can create whatever request you want and you are in @@ -275,11 +275,12 @@ secure? The ``$_SERVER['HTTP_X_FORWARDED_FOR']`` value cannot be trusted as it can be manipulated by the end user when there is no proxy. So, if you are using this code in production without a proxy, it becomes trivially easy to abuse your system. That's not the case with the ``getClientIp()`` method as -you must explicitly trust this header by calling ``trustProxyData()``:: +you must explicitly trust your reverse proxies by calling +``setTrustedProxies()``:: getClientIp(true)) { // the client is a known one, so give it some more privilege @@ -302,9 +303,9 @@ Using just the Symfony2 HttpFoundation component already allows you to write better and more testable code. It also allows you to write code faster as many day-to-day problems have already been solved for you. -As a matter of fact, projects like Drupal have adopted (for the upcoming -version 8) the HttpFoundation component; if it works for them, it will -probably work for you. Don't reinvent the wheel. +As a matter of fact, projects like Drupal have adopted the HttpFoundation +component; if it works for them, it will probably work for you. Don't reinvent +the wheel. I've almost forgot to talk about one added benefit: using the HttpFoundation component is the start of better interoperability between all frameworks and diff --git a/book/part03.rst b/book/part03.rst index d795d57b565..4ca7ee1cc51 100644 --- a/book/part03.rst +++ b/book/part03.rst @@ -65,8 +65,8 @@ And for the "Goodbye" page:: We have indeed moved most of the shared code into a central place, but it does not feel like a good abstraction, doesn't it? First, we still have the -``send()`` method in all pages, then our pages does not look like templates, -and we are still not able to test this code properly. +``send()`` method in all pages, then our pages do not look like templates, and +we are still not able to test this code properly. Moreover, adding a new page means that we need to create a new PHP script, which name is exposed to the end user via the URL @@ -140,9 +140,9 @@ To access a page, you must now use the ``front.php`` script: able to type ``http://example.com/hello?name=Fabien``, which looks much better. -So, the trick is the usage of the ``Request::getPathInfo()`` method which -returns the path of the Request by removing the front controller script name -including its sub-directories (only if needed -- see above tip). +The trick is the usage of the ``Request::getPathInfo()`` method which returns +the path of the Request by removing the front controller script name including +its sub-directories (only if needed -- see above tip). .. tip:: @@ -205,7 +205,7 @@ And the ``hello.php`` script can now be converted to a template:: Hello -We have our framework for today:: +We have the first version of our framework:: -* Routes configuration has been moved to its own file: +* Route configuration has been moved to its own file: .. code-block:: php @@ -232,11 +232,4 @@ generate absolute URLs:: echo $dumper->dump(); - Want even more performance? Dump your routes as a set of Apache rewrite - rules:: - - $dumper = new Routing\Matcher\Dumper\ApacheMatcherDumper($routes); - - echo $dumper->dump(); - .. _`documentation`: http://symfony.com/doc/current/components/routing.html diff --git a/book/part05.rst b/book/part05.rst index a10d9a7a21a..f2494c332e7 100644 --- a/book/part05.rst +++ b/book/part05.rst @@ -33,8 +33,8 @@ As the rendering is now done by an external function (``render_template()`` here), we need to pass to it the attributes extracted from the URL. We could have passed them as an additional argument to ``render_template()``, but instead, let's use another feature of the ``Request`` class called -*attributes*: Request attributes lets you attach additional information about -the Request that is not directly related to the HTTP Request data. +*attributes*: Request attributes is a way to attach additional information +about the Request that is not directly related to the HTTP Request data. You can now create the ``render_template()`` function, a generic controller that renders a template when there is no specific logic. To keep the same @@ -177,10 +177,10 @@ framework does not need to be modified in any way, just create a new return $routes; The ``is_leap_year()`` function returns ``true`` when the given year is a leap -year, ``false`` otherwise. If the year is null, the current year is tested. -The controller is simple: it gets the year from the request attributes, pass -it to the `is_leap_year()`` function, and according to the return value it -creates a new Response object. +year, ``false`` otherwise. If the year is ``null``, the current year is +tested. The controller is simple: it gets the year from the request +attributes, pass it to the `is_leap_year()`` function, and according to the +return value it creates a new Response object. As always, you can decide to stop here and use the framework as is; it's probably all you need to create simple websites like those fancy one-page diff --git a/book/part06.rst b/book/part06.rst index 868ea17a057..d7d721c73ef 100644 --- a/book/part06.rst +++ b/book/part06.rst @@ -41,9 +41,9 @@ component:: { "require": { - "symfony/http-foundation": "2.1.*", - "symfony/routing": "2.1.*", - "symfony/http-kernel": "2.1.*" + "symfony/http-foundation": "~2.3", + "symfony/routing": "~2.3", + "symfony/http-kernel": "~2.3" } } @@ -145,7 +145,7 @@ method is not defined, an argument has no matching attribute, ...). With the great flexibility of the default controller resolver, you might wonder why someone would want to create another one (why would there be an - interface if not). Two examples: in Symfony2, ``getController()`` is + interface if not?). Two examples: in Symfony2, ``getController()`` is enhanced to support `controllers as services`_; and in `FrameworkExtraBundle`_, ``getArguments()`` is enhanced to support parameter converters, where request attributes are converted to objects diff --git a/book/part07.rst b/book/part07.rst index 76f95a3df0e..004ca4a140d 100644 --- a/book/part07.rst +++ b/book/part07.rst @@ -89,12 +89,12 @@ be autoloaded, update the ``composer.json`` file: { "require": { - "symfony/http-foundation": "2.1.*", - "symfony/routing": "2.1.*", - "symfony/http-kernel": "2.1.*" + "symfony/http-foundation": "~2.3", + "symfony/routing": "~2.3", + "symfony/http-kernel": "~2.3" }, "autoload": { - "psr-0": { "Simplex": "src/", "Calendar": "src/" } + "psr-0": { "Simplex\\": "src/", "Calendar\\": "src/" } } } diff --git a/book/part09.rst b/book/part09.rst index 53649990457..05604afce89 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -21,13 +21,13 @@ version of this pattern: { "require": { - "symfony/http-foundation": "2.1.*", - "symfony/routing": "2.1.*", - "symfony/http-kernel": "2.1.*", - "symfony/event-dispatcher": "2.1.*" + "symfony/http-foundation": "~2.3", + "symfony/routing": "~2.3", + "symfony/http-kernel": "~2.3", + "symfony/event-dispatcher": "~2.3" }, "autoload": { - "psr-0": { "Simplex": "src/", "Calendar": "src/" } + "psr-0": { "Simplex\\": "src/", "Calendar\\": "src/" } } } diff --git a/book/part11.rst b/book/part11.rst index 1e29e214fc5..0352fbf4fba 100644 --- a/book/part11.rst +++ b/book/part11.rst @@ -110,7 +110,7 @@ The error controller reads as follows:: Voilà! Clean and customizable error management without efforts. And of course, if your controller throws an exception, HttpKernel will handle it nicely. -In part 2, we have talked about the ``Response::prepare()`` method, which +In chapter two, we talked about the ``Response::prepare()`` method, which ensures that a Response is compliant with the HTTP specification. It is probably a good idea to always call it just before sending the Response to the client; that's what the ``ResponseListener`` does:: diff --git a/book/part12.rst b/book/part12.rst index 0b79feae509..41330f5666c 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -95,14 +95,14 @@ container: { "require": { - "symfony/http-foundation": "2.1.*", - "symfony/routing": "2.1.*", - "symfony/http-kernel": "2.1.*", - "symfony/event-dispatcher": "2.1.*", - "symfony/dependency-injection": "2.1.*" + "symfony/http-foundation": "~2.3", + "symfony/routing": "~2.3", + "symfony/http-kernel": "~2.3", + "symfony/event-dispatcher": "~2.3", + "symfony/dependency-injection": "~2.3" }, "autoload": { - "psr-0": { "Simplex": "src/", "Calendar": "src/" } + "psr-0": { "Simplex\\": "src/", "Calendar\\": "src/" } } } From a1336e042316f591b365a59a1915ea606605865e Mon Sep 17 00:00:00 2001 From: revollat Date: Thu, 6 Jun 2013 16:51:52 +0300 Subject: [PATCH 64/74] Update part06.rst If $request is not typed there is an error : Controller "render_template" requires that you provide a value for the "$request" argument (because there is no default value or because there is a non optional argument after this one). --- book/part06.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/part06.rst b/book/part06.rst index d7d721c73ef..aa7b534e4d0 100644 --- a/book/part06.rst +++ b/book/part06.rst @@ -164,7 +164,7 @@ Let's conclude with the new version of our framework:: use Symfony\Component\Routing; use Symfony\Component\HttpKernel; - function render_template($request) + function render_template(Request $request) { extract($request->attributes->all()); ob_start(); From 55f5c12fd61c0e5863c561330ef35dc06bd08a2b Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 23 Jun 2014 09:24:02 +0200 Subject: [PATCH 65/74] updated the whole book (changes mainly related to Composer) --- book/part01.rst | 59 ++++++++++++++++++------------------------------- book/part02.rst | 31 ++++++++++++++------------ book/part03.rst | 34 ++++++++++++++++------------ book/part04.rst | 13 +++-------- book/part06.rst | 12 ++++------ book/part07.rst | 14 +++++------- book/part09.rst | 14 ++---------- book/part12.rst | 17 +++----------- 8 files changed, 77 insertions(+), 117 deletions(-) diff --git a/book/part01.rst b/book/part01.rst index b22945c1365..acdd97284f1 100644 --- a/book/part01.rst +++ b/book/part01.rst @@ -94,44 +94,23 @@ To store our framework, create a directory somewhere on your machine: $ mkdir framework $ cd framework -Components Installation -~~~~~~~~~~~~~~~~~~~~~~~ +Dependency Management +~~~~~~~~~~~~~~~~~~~~~ -To install the Symfony2 Components that we need for our framework, we are -going to use `Composer`_, a project dependency manager for PHP. Create a -``composer.json`` file, where we will list our dependencies: - -.. code-block:: javascript - - { - "require": { - } - } - -The file is empty for now as we do not depend on anything yet. To install the -project dependencies, download the composer binary and run it: +To install the Symfony2 Components that we need for our framework, we are going +to use `Composer`_, a project dependency manager for PHP. If you don't have it +yet, `download and install`_ Composer now: .. code-block:: sh - $ wget http://getcomposer.org/composer.phar - $ # or - $ curl -O http://getcomposer.org/composer.phar - - $ php composer.phar install - -After running the ``install`` command, you must see a new ``vendor/`` -directory. + $ curl -sS https://getcomposer.org/installer | php -Naming Conventions and Autoloading -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Then, generate an empty ``composer.json`` file, where Composer will store the +framework dependencies: -We are going to `autoload`_ all our classes. Without autoloading, you need to -require the file where a class is defined before being able to use it. But -with some conventions, we can just let PHP do the hard work for us. +.. code-block:: sh -Symfony2 follows the de-facto PHP standard, `PSR-0`_, for class names and -autoloading and Composer generates such an autoloader for all the dependencies -it manages; it can be enabled by requiring the ``vendor/autoload.php`` file. + $ php composer.phar init -n Our Project ----------- @@ -148,12 +127,18 @@ start with the simplest web application we can think of in PHP:: printf('Hello %s', $input); +Use the PHP built-in server to test this great application in a browser +(``http://localhost:4321/index.php?name=Fabien``): + +.. code-block:: sh + + $ php -S 127.0.0.1:4321 + In the next chapter, we are going to introduce the HttpFoundation Component and see what it brings us. -.. _`Symfony2`: http://symfony.com/ -.. _`documentation`: http://symfony.com/doc -.. _`Silex`: http://silex.sensiolabs.org/ -.. _`autoload`: http://fr.php.net/autoload -.. _`Composer`: http://packagist.org/about-composer -.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md +.. _`Symfony2`: http://symfony.com/ +.. _`documentation`: http://symfony.com/doc +.. _`Silex`: http://silex.sensiolabs.org/ +.. _`Composer`: http://packagist.org/about-composer +.. _`download and install`: https://getcomposer.org/doc/01-basic-usage.md diff --git a/book/part02.rst b/book/part02.rst index 226547a6ab1..f5c007990a7 100644 --- a/book/part02.rst +++ b/book/part02.rst @@ -26,7 +26,7 @@ it suffers from a few problems:: printf('Hello %s', $input); -First, if the ``name`` query parameter is not given in the URL query string, +First, if the ``name`` query parameter is not defined in the URL query string, you will get a PHP warning; so let's fix it:: send(); We have indeed moved most of the shared code into a central place, but it does -not feel like a good abstraction, doesn't it? First, we still have the -``send()`` method in all pages, then our pages do not look like templates, and -we are still not able to test this code properly. +not feel like a good abstraction, does it? We still have the ``send()`` method +for all pages, our pages do not look like templates, and we are still not able +to test this code properly. Moreover, adding a new page means that we need to create a new PHP script, which name is exposed to the end user via the URL -(``http://example.com/bye.php``): there is a direct mapping between the PHP +(``http://127.0.0.1:4321/bye.php``): there is a direct mapping between the PHP script name and the client URL. This is because the dispatching of the request is done by the web server directly. It might be a good idea to move this dispatching to our code for better flexibility. This can be easily achieved by @@ -127,18 +127,17 @@ we return a custom 404 page; you are now in control of your website. To access a page, you must now use the ``front.php`` script: -* ``http://example.com/front.php/hello?name=Fabien`` +* ``http://127.0.0.1:4321/front.php/hello?name=Fabien`` -* ``http://example.com/front.php/bye`` +* ``http://127.0.0.1:4321/front.php/bye`` ``/hello`` and ``/bye`` are the page *path*s. .. tip:: - Most web servers like Apache or nginx are able to rewrite the incoming - URLs and remove the front controller script so that your users will be - able to type ``http://example.com/hello?name=Fabien``, which looks much - better. + Most web servers like Apache or nginx are able to rewrite the incoming URLs + and remove the front controller script so that your users will be able to + type ``http://127.0.0.1:4321/hello?name=Fabien``, which looks much better. The trick is the usage of the ``Request::getPathInfo()`` method which returns the path of the Request by removing the front controller script name including @@ -152,8 +151,8 @@ its sub-directories (only if needed -- see above tip). argument is the URL path you want to simulate. Now that the web server always access the same script (``front.php``) for all -our pages, we can secure our code further by moving all other PHP files -outside the web root directory: +pages, we can secure the code further by moving all other PHP files outside the +web root directory: .. code-block:: text @@ -170,14 +169,21 @@ outside the web root directory: Now, configure your web server root directory to point to ``web/`` and all other files won't be accessible from the client anymore. +To test your changes in a browser (``http://localhost:4321/?name=Fabien``), run +the PHP built-in server: + +.. code-block:: sh + + $ php -S 127.0.0.1:4321 -t web/ web/front.php + .. note:: For this new structure to work, you will have to adjust some paths in various PHP files; the changes are left as an exercise for the reader. The last thing that is repeated in each page is the call to ``setContent()``. -We can convert all pages to "templates" by just echoing the content and -calling the ``setContent()`` directly from the front controller script:: +We can convert all pages to "templates" by just echoing the content and calling +the ``setContent()`` directly from the front controller script:: Date: Fri, 24 Oct 2014 10:09:48 +0200 Subject: [PATCH 66/74] removed versions when adding Symfony component with Composer --- book/part02.rst | 2 +- book/part04.rst | 2 +- book/part06.rst | 2 +- book/part09.rst | 2 +- book/part12.rst | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/book/part02.rst b/book/part02.rst index f5c007990a7..933fbb6baae 100644 --- a/book/part02.rst +++ b/book/part02.rst @@ -126,7 +126,7 @@ To use this component, add it as a dependency of the project: .. code-block:: sh - $ php composer.phar require symfony/http-foundation 2.5.* + $ php composer.phar require symfony/http-foundation Running this command will also automatically download the Symfony HttpFoundation component and install it under the ``vendor/`` directory. diff --git a/book/part04.rst b/book/part04.rst index 3d370079651..65ddc425315 100644 --- a/book/part04.rst +++ b/book/part04.rst @@ -57,7 +57,7 @@ To support this feature, add the Symfony2 Routing component as a dependency: .. code-block:: sh - $ php composer.phar require symfony/routing 2.5.* + $ php composer.phar require symfony/routing Instead of an array for the URL map, the Routing component relies on a ``RouteCollection`` instance:: diff --git a/book/part06.rst b/book/part06.rst index 148dc839062..3e39bb85782 100644 --- a/book/part06.rst +++ b/book/part06.rst @@ -41,7 +41,7 @@ component: .. code-block:: sh - $ php composer.phar require symfony/http-kernel 2.5.* + $ php composer.phar require symfony/http-kernel The HttpKernel component has many interesting features, but the one we need right now is the *controller resolver*. A controller resolver knows how to diff --git a/book/part09.rst b/book/part09.rst index 7d12e55e973..5a0967205d2 100644 --- a/book/part09.rst +++ b/book/part09.rst @@ -19,7 +19,7 @@ version of this pattern: .. code-block:: sh - $ php composer.phar require symfony/event-dispatcher 2.5.* + $ php composer.phar require symfony/event-dispatcher How does it work? The *dispatcher*, the central object of the event dispatcher system, notifies *listeners* of an *event* dispatched to it. Put another way: diff --git a/book/part12.rst b/book/part12.rst index a1ede9d5833..fbeb189d567 100644 --- a/book/part12.rst +++ b/book/part12.rst @@ -93,7 +93,7 @@ container: .. code-block:: sh - $ php composer.phar require symfony/dependency-injection 2.5.* + $ php composer.phar require symfony/dependency-injection Create a new file to host the dependency injection container configuration:: From 409dba54363f5099c8b9059fedb6e14ce697d719 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 12 Nov 2014 22:05:59 +0100 Subject: [PATCH 67/74] move things around --- LICENSE.md | 4 ---- {book => create_framework}/index.rst | 0 {book => create_framework}/part01.rst | 0 {book => create_framework}/part02.rst | 0 {book => create_framework}/part03.rst | 0 {book => create_framework}/part04.rst | 0 {book => create_framework}/part05.rst | 0 {book => create_framework}/part06.rst | 0 {book => create_framework}/part07.rst | 0 {book => create_framework}/part08.rst | 0 {book => create_framework}/part09.rst | 0 {book => create_framework}/part10.rst | 0 {book => create_framework}/part11.rst | 0 {book => create_framework}/part12.rst | 0 14 files changed, 4 deletions(-) delete mode 100644 LICENSE.md rename {book => create_framework}/index.rst (100%) rename {book => create_framework}/part01.rst (100%) rename {book => create_framework}/part02.rst (100%) rename {book => create_framework}/part03.rst (100%) rename {book => create_framework}/part04.rst (100%) rename {book => create_framework}/part05.rst (100%) rename {book => create_framework}/part06.rst (100%) rename {book => create_framework}/part07.rst (100%) rename {book => create_framework}/part08.rst (100%) rename {book => create_framework}/part09.rst (100%) rename {book => create_framework}/part10.rst (100%) rename {book => create_framework}/part11.rst (100%) rename {book => create_framework}/part12.rst (100%) diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index 176160d1345..00000000000 --- a/LICENSE.md +++ /dev/null @@ -1,4 +0,0 @@ -This work is licensed under a Creative Commons Attribution-Share Alike 3.0 -Unported License. - -http://creativecommons.org/licenses/by-sa/3.0/ diff --git a/book/index.rst b/create_framework/index.rst similarity index 100% rename from book/index.rst rename to create_framework/index.rst diff --git a/book/part01.rst b/create_framework/part01.rst similarity index 100% rename from book/part01.rst rename to create_framework/part01.rst diff --git a/book/part02.rst b/create_framework/part02.rst similarity index 100% rename from book/part02.rst rename to create_framework/part02.rst diff --git a/book/part03.rst b/create_framework/part03.rst similarity index 100% rename from book/part03.rst rename to create_framework/part03.rst diff --git a/book/part04.rst b/create_framework/part04.rst similarity index 100% rename from book/part04.rst rename to create_framework/part04.rst diff --git a/book/part05.rst b/create_framework/part05.rst similarity index 100% rename from book/part05.rst rename to create_framework/part05.rst diff --git a/book/part06.rst b/create_framework/part06.rst similarity index 100% rename from book/part06.rst rename to create_framework/part06.rst diff --git a/book/part07.rst b/create_framework/part07.rst similarity index 100% rename from book/part07.rst rename to create_framework/part07.rst diff --git a/book/part08.rst b/create_framework/part08.rst similarity index 100% rename from book/part08.rst rename to create_framework/part08.rst diff --git a/book/part09.rst b/create_framework/part09.rst similarity index 100% rename from book/part09.rst rename to create_framework/part09.rst diff --git a/book/part10.rst b/create_framework/part10.rst similarity index 100% rename from book/part10.rst rename to create_framework/part10.rst diff --git a/book/part11.rst b/create_framework/part11.rst similarity index 100% rename from book/part11.rst rename to create_framework/part11.rst diff --git a/book/part12.rst b/create_framework/part12.rst similarity index 100% rename from book/part12.rst rename to create_framework/part12.rst From d44e4a2070b685824f76221ca4619f7fff1d9899 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 12 Nov 2014 22:10:17 +0100 Subject: [PATCH 68/74] added the new tutorial in the main index --- .../{part01.rst => 01-introduction.rst} | 0 .../{part02.rst => 02-http-foundation.rst} | 0 .../{part03.rst => 03-front-controller.rst} | 0 .../{part04.rst => 04-routing.rst} | 0 .../{part05.rst => 05-templating.rst} | 0 .../{part06.rst => 06-http-kernel.rst} | 0 ...rt07.rst => 07-separation-of-concerns.rst} | 0 .../{part08.rst => 08-unit-testing.rst} | 0 .../{part09.rst => 09-event-dispatcher.rst} | 0 .../{part10.rst => 10-http-kernel.rst} | 0 .../{part11.rst => 11-http-kernel.rst} | 0 ...part12.rst => 12-dependency-injection.rst} | 0 create_framework/index.rst | 24 +++++++++---------- create_framework/map.rst.inc | 12 ++++++++++ index.rst | 12 ++++++++++ 15 files changed, 36 insertions(+), 12 deletions(-) rename create_framework/{part01.rst => 01-introduction.rst} (100%) rename create_framework/{part02.rst => 02-http-foundation.rst} (100%) rename create_framework/{part03.rst => 03-front-controller.rst} (100%) rename create_framework/{part04.rst => 04-routing.rst} (100%) rename create_framework/{part05.rst => 05-templating.rst} (100%) rename create_framework/{part06.rst => 06-http-kernel.rst} (100%) rename create_framework/{part07.rst => 07-separation-of-concerns.rst} (100%) rename create_framework/{part08.rst => 08-unit-testing.rst} (100%) rename create_framework/{part09.rst => 09-event-dispatcher.rst} (100%) rename create_framework/{part10.rst => 10-http-kernel.rst} (100%) rename create_framework/{part11.rst => 11-http-kernel.rst} (100%) rename create_framework/{part12.rst => 12-dependency-injection.rst} (100%) create mode 100644 create_framework/map.rst.inc diff --git a/create_framework/part01.rst b/create_framework/01-introduction.rst similarity index 100% rename from create_framework/part01.rst rename to create_framework/01-introduction.rst diff --git a/create_framework/part02.rst b/create_framework/02-http-foundation.rst similarity index 100% rename from create_framework/part02.rst rename to create_framework/02-http-foundation.rst diff --git a/create_framework/part03.rst b/create_framework/03-front-controller.rst similarity index 100% rename from create_framework/part03.rst rename to create_framework/03-front-controller.rst diff --git a/create_framework/part04.rst b/create_framework/04-routing.rst similarity index 100% rename from create_framework/part04.rst rename to create_framework/04-routing.rst diff --git a/create_framework/part05.rst b/create_framework/05-templating.rst similarity index 100% rename from create_framework/part05.rst rename to create_framework/05-templating.rst diff --git a/create_framework/part06.rst b/create_framework/06-http-kernel.rst similarity index 100% rename from create_framework/part06.rst rename to create_framework/06-http-kernel.rst diff --git a/create_framework/part07.rst b/create_framework/07-separation-of-concerns.rst similarity index 100% rename from create_framework/part07.rst rename to create_framework/07-separation-of-concerns.rst diff --git a/create_framework/part08.rst b/create_framework/08-unit-testing.rst similarity index 100% rename from create_framework/part08.rst rename to create_framework/08-unit-testing.rst diff --git a/create_framework/part09.rst b/create_framework/09-event-dispatcher.rst similarity index 100% rename from create_framework/part09.rst rename to create_framework/09-event-dispatcher.rst diff --git a/create_framework/part10.rst b/create_framework/10-http-kernel.rst similarity index 100% rename from create_framework/part10.rst rename to create_framework/10-http-kernel.rst diff --git a/create_framework/part11.rst b/create_framework/11-http-kernel.rst similarity index 100% rename from create_framework/part11.rst rename to create_framework/11-http-kernel.rst diff --git a/create_framework/part12.rst b/create_framework/12-dependency-injection.rst similarity index 100% rename from create_framework/part12.rst rename to create_framework/12-dependency-injection.rst diff --git a/create_framework/index.rst b/create_framework/index.rst index 120c334d0f1..a7291c1966d 100644 --- a/create_framework/index.rst +++ b/create_framework/index.rst @@ -3,15 +3,15 @@ Create your PHP Framework .. toctree:: - part01 - part02 - part03 - part04 - part05 - part06 - part07 - part08 - part09 - part10 - part11 - part12 + 01-introduction + 02-http-foundation + 03-front-controller + 04-routing + 05-templating + 06-http-kernel + 07-separation-of-concerns + 08-unit-testing + 09-event-dispatcher + 10-http-kernel + 11-http-kernel + 12-dependency-injection diff --git a/create_framework/map.rst.inc b/create_framework/map.rst.inc new file mode 100644 index 00000000000..8e11e1700c3 --- /dev/null +++ b/create_framework/map.rst.inc @@ -0,0 +1,12 @@ +* :doc:`/create_framework/01-introduction` +* :doc:`/create_framework/02-http-foundation` +* :doc:`/create_framework/03-front-controller` +* :doc:`/create_framework/04-routing` +* :doc:`/create_framework/05-templating` +* :doc:`/create_framework/06-http-kernel` +* :doc:`/create_framework/07-separation-of-concerns` +* :doc:`/create_framework/08-unit-testing` +* :doc:`/create_framework/09-event-dispatcher` +* :doc:`/create_framework/10-http-kernel` +* :doc:`/create_framework/11-http-kernel` +* :doc:`/create_framework/12-dependency-injection` diff --git a/index.rst b/index.rst index 2ef2df24f45..cdfd5ff67f8 100644 --- a/index.rst +++ b/index.rst @@ -88,3 +88,15 @@ Contribute to Symfony: contributing/index .. include:: /contributing/map.rst.inc + +Create your Own Framework +------------------------- + +Want to create your own framework based on Symfony? + +.. toctree:: + :hidden: + + create_framework/index + +.. include:: /create_framework/map.rst.inc From bf9c8714f603dccf6c27169e19e610d28927972e Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Wed, 12 Nov 2014 22:33:39 +0100 Subject: [PATCH 69/74] fixed markup --- create_framework/02-http-foundation.rst | 4 +++- create_framework/03-front-controller.rst | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/create_framework/02-http-foundation.rst b/create_framework/02-http-foundation.rst index 933fbb6baae..caee3c592c2 100644 --- a/create_framework/02-http-foundation.rst +++ b/create_framework/02-http-foundation.rst @@ -310,7 +310,7 @@ the wheel. I've almost forgot to talk about one added benefit: using the HttpFoundation component is the start of better interoperability between all frameworks and applications using it (like `Symfony2`_, `Drupal 8`_, `phpBB 4`_, `ezPublish -5`, `Laravel`_, `Silex`_, and `more`_). +5`_, `Laravel`_, `Silex`_, and `more`_). .. _`Twig`: http://twig.sensiolabs.com/ .. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html @@ -321,6 +321,8 @@ applications using it (like `Symfony2`_, `Drupal 8`_, `phpBB 4`_, `ezPublish .. _`Symfony2`: http://symfony.com/ .. _`Drupal 8`: http://drupal.org/ .. _`phpBB 4`: http://www.phpbb.com/ +.. _`ezPublish 5`: http://ez.no/ +.. _`Laravel`: http://laravel.com/ .. _`Silex`: http://silex.sensiolabs.org/ .. _`Midgard CMS`: http://www.midgard-project.org/ .. _`Zikula`: http://zikula.org/ diff --git a/create_framework/03-front-controller.rst b/create_framework/03-front-controller.rst index e4dc6e69367..b9d101f25bc 100644 --- a/create_framework/03-front-controller.rst +++ b/create_framework/03-front-controller.rst @@ -131,7 +131,7 @@ To access a page, you must now use the ``front.php`` script: * ``http://127.0.0.1:4321/front.php/bye`` -``/hello`` and ``/bye`` are the page *path*s. +``/hello`` and ``/bye`` are the page *paths*. .. tip:: From 13a7170df6ca540798a45d94a8d4a946a722d162 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 20 Nov 2014 17:51:55 +0100 Subject: [PATCH 70/74] made some changes to better integrate the tutorial into the current documentation --- create_framework/01-introduction.rst | 43 ++++++++----------- create_framework/02-http-foundation.rst | 36 +++++++--------- create_framework/03-front-controller.rst | 2 +- create_framework/04-routing.rst | 4 +- create_framework/06-http-kernel.rst | 4 +- .../07-separation-of-concerns.rst | 2 +- create_framework/08-unit-testing.rst | 5 +-- create_framework/09-event-dispatcher.rst | 6 +-- create_framework/10-http-kernel.rst | 4 +- create_framework/12-dependency-injection.rst | 8 ++-- 10 files changed, 52 insertions(+), 62 deletions(-) diff --git a/create_framework/01-introduction.rst b/create_framework/01-introduction.rst index acdd97284f1..6891897a854 100644 --- a/create_framework/01-introduction.rst +++ b/create_framework/01-introduction.rst @@ -8,19 +8,14 @@ Instead of using these low-level components, you can use the ready-to-be-used Symfony2 full-stack web framework, which is based on these components... or you can create your very own framework. This book is about the latter. -.. note:: - - If you just want to use the Symfony2 full-stack framework, you'd better - read its official `documentation`_ instead. - Why would you like to create your own framework? ------------------------------------------------ Why would you like to create your own framework in the first place? If you look around, everybody will tell you that it's a bad thing to reinvent the wheel and that you'd better choose an existing framework and forget about -creating your own altogether. Most of the time, they are right but I can think -of a few good reasons to start creating your own framework: +creating your own altogether. Most of the time, they are right but there are +a few good reasons to start creating your own framework: * To learn more about the low level architecture of modern web frameworks in general and about the Symfony2 full-stack framework internals in particular; @@ -37,11 +32,11 @@ of a few good reasons to start creating your own framework: * To prove the world that you can actually create a framework on your own (... but with little effort). -I will gently guide you through the creation of a web framework, one step at a -time. At each step, you will have a fully-working framework that you can use -as is or as a start for your very own. We will start with simple frameworks -and more features will be added with time. Eventually, you will have a -fully-featured full-stack web framework. +This tutorial will gently guide you through the creation of a web framework, +one step at a time. At each step, you will have a fully-working framework that +you can use as is or as a start for your very own. We will start with simple +frameworks and more features will be added with time. Eventually, you will have +a fully-featured full-stack web framework. And of course, each step will be the occasion to learn more about some of the Symfony2 Components. @@ -58,16 +53,16 @@ won't talk about the MVC pattern as the Symfony2 Components are able to create any type of frameworks, not just the ones that follow the MVC architecture. Anyway, if you have a look at the MVC semantics, this book is about how to create the Controller part of a framework. For the Model and the View, it -really depends on your personal taste and I will let you use any existing +really depends on your personal taste and you can use any existing third-party libraries (Doctrine, Propel, or plain-old PDO for the Model; PHP or Twig for the View). -When creating a framework, following the MVC pattern is not the right goal. -The main goal should be the **Separation of Concerns**; I actually think that -this is the only design pattern that you should really care about. The -fundamental principles of the Symfony2 Components are focused on the HTTP -specification. As such, the frameworks that we are going to create should be -more accurately labelled as HTTP frameworks or Request/Response frameworks. +When creating a framework, following the MVC pattern is not the right goal. The +main goal should be the **Separation of Concerns**; this is probably the only +design pattern that you should really care about. The fundamental principles of +the Symfony2 Components are focused on the HTTP specification. As such, the +frameworks that we are going to create should be more accurately labelled as +HTTP frameworks or Request/Response frameworks. Before we start --------------- @@ -89,7 +84,7 @@ classes, how we will reference external dependencies, etc. To store our framework, create a directory somewhere on your machine: -.. code-block:: sh +.. code-block:: bash $ mkdir framework $ cd framework @@ -101,16 +96,16 @@ To install the Symfony2 Components that we need for our framework, we are going to use `Composer`_, a project dependency manager for PHP. If you don't have it yet, `download and install`_ Composer now: -.. code-block:: sh +.. code-block:: bash $ curl -sS https://getcomposer.org/installer | php Then, generate an empty ``composer.json`` file, where Composer will store the framework dependencies: -.. code-block:: sh +.. code-block:: bash - $ php composer.phar init -n + $ composer init -n Our Project ----------- @@ -130,7 +125,7 @@ start with the simplest web application we can think of in PHP:: Use the PHP built-in server to test this great application in a browser (``http://localhost:4321/index.php?name=Fabien``): -.. code-block:: sh +.. code-block:: bash $ php -S 127.0.0.1:4321 diff --git a/create_framework/02-http-foundation.rst b/create_framework/02-http-foundation.rst index caee3c592c2..a1662f7cf75 100644 --- a/create_framework/02-http-foundation.rst +++ b/create_framework/02-http-foundation.rst @@ -1,16 +1,15 @@ The HttpFoundation Component ============================ -Before diving into the framework creation process, I first want to step back -and take a look at why you would like to use a framework instead of keeping -your plain-old PHP applications as is. Why using a framework is actually a -good idea, even for the simplest snippet of code and why creating your -framework on top of the Symfony2 components is better than creating a -framework from scratch. +Before diving into the framework creation process, let's first step back and +let's take a look at why you would like to use a framework instead of keeping +your plain-old PHP applications as is. Why using a framework is actually a good +idea, even for the simplest snippet of code and why creating your framework on +top of the Symfony2 components is better than creating a framework from scratch. .. note:: - I won't talk about the obvious and traditional benefits of using a + We won't talk about the obvious and traditional benefits of using a framework when working on big applications with more than a few developers; the Internet has already plenty of good resources on that topic. @@ -124,9 +123,9 @@ layer. To use this component, add it as a dependency of the project: -.. code-block:: sh +.. code-block:: bash - $ php composer.phar require symfony/http-foundation + $ composer require symfony/http-foundation Running this command will also automatically download the Symfony HttpFoundation component and install it under the ``vendor/`` directory. @@ -270,13 +269,12 @@ chained proxies):: // the client is a known one, so give it some more privilege } -And there is an added benefit: it is *secure* by default. What do I mean by -secure? The ``$_SERVER['HTTP_X_FORWARDED_FOR']`` value cannot be trusted as it -can be manipulated by the end user when there is no proxy. So, if you are -using this code in production without a proxy, it becomes trivially easy to -abuse your system. That's not the case with the ``getClientIp()`` method as -you must explicitly trust your reverse proxies by calling -``setTrustedProxies()``:: +And there is an added benefit: it is *secure* by default. What does it mean? +The ``$_SERVER['HTTP_X_FORWARDED_FOR']`` value cannot be trusted as it can be +manipulated by the end user when there is no proxy. So, if you are using this +code in production without a proxy, it becomes trivially easy to abuse your +system. That's not the case with the ``getClientIp()`` method as you must +explicitly trust your reverse proxies by calling ``setTrustedProxies()``:: `. Believe or not but we have our first framework. You can stop now if you want. Using just the Symfony2 HttpFoundation component already allows you to write @@ -315,8 +313,6 @@ applications using it (like `Symfony2`_, `Drupal 8`_, `phpBB 4`_, `ezPublish .. _`Twig`: http://twig.sensiolabs.com/ .. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html .. _`HTTP specification`: http://tools.ietf.org/wg/httpbis/ -.. _`API`: http://api.symfony.com/2.0/Symfony/Component/HttpFoundation.html -.. _`documentation`: http://symfony.com/doc/current/components/http_foundation.html .. _`audited`: http://symfony.com/blog/symfony2-security-audit .. _`Symfony2`: http://symfony.com/ .. _`Drupal 8`: http://drupal.org/ diff --git a/create_framework/03-front-controller.rst b/create_framework/03-front-controller.rst index b9d101f25bc..f53a6034b03 100644 --- a/create_framework/03-front-controller.rst +++ b/create_framework/03-front-controller.rst @@ -172,7 +172,7 @@ other files won't be accessible from the client anymore. To test your changes in a browser (``http://localhost:4321/?name=Fabien``), run the PHP built-in server: -.. code-block:: sh +.. code-block:: bash $ php -S 127.0.0.1:4321 -t web/ web/front.php diff --git a/create_framework/04-routing.rst b/create_framework/04-routing.rst index 65ddc425315..7055884efea 100644 --- a/create_framework/04-routing.rst +++ b/create_framework/04-routing.rst @@ -55,9 +55,9 @@ instead of relying on a query string: To support this feature, add the Symfony2 Routing component as a dependency: -.. code-block:: sh +.. code-block:: bash - $ php composer.phar require symfony/routing + $ composer require symfony/routing Instead of an array for the URL map, the Routing component relies on a ``RouteCollection`` instance:: diff --git a/create_framework/06-http-kernel.rst b/create_framework/06-http-kernel.rst index 3e39bb85782..e921a7c6db0 100644 --- a/create_framework/06-http-kernel.rst +++ b/create_framework/06-http-kernel.rst @@ -39,9 +39,9 @@ instantiated. To solve this issue, and a bunch more, let's install and use the HttpKernel component: -.. code-block:: sh +.. code-block:: bash - $ php composer.phar require symfony/http-kernel + $ composer require symfony/http-kernel The HttpKernel component has many interesting features, but the one we need right now is the *controller resolver*. A controller resolver knows how to diff --git a/create_framework/07-separation-of-concerns.rst b/create_framework/07-separation-of-concerns.rst index f3675809d0e..29b4e749638 100644 --- a/create_framework/07-separation-of-concerns.rst +++ b/create_framework/07-separation-of-concerns.rst @@ -98,7 +98,7 @@ be autoloaded, update the ``composer.json`` file: .. note:: - For the Composer autoloader to be updated, run ``php composer.phar update``. + For the Composer autoloader to be updated, run ``composer update``. Move the controller to ``Calendar\\Controller\\LeapYearController``:: diff --git a/create_framework/08-unit-testing.rst b/create_framework/08-unit-testing.rst index e61da949a3e..e0d2ba1d6ff 100644 --- a/create_framework/08-unit-testing.rst +++ b/create_framework/08-unit-testing.rst @@ -119,9 +119,8 @@ Executing this test is as simple as running ``phpunit`` from the .. note:: - I do not explain how the code works in details as this is not the goal of - this book, but if you don't understand what the hell is going on, I highly - recommend you to read PHPUnit documentation on `test doubles`_. + If you don't understand what the hell is going on in the code, read + PHPUnit documentation on `test doubles`_. After the test ran, you should see a green bar. If not, you have a bug either in the test or in the framework code! diff --git a/create_framework/09-event-dispatcher.rst b/create_framework/09-event-dispatcher.rst index 5a0967205d2..8cf8b968250 100644 --- a/create_framework/09-event-dispatcher.rst +++ b/create_framework/09-event-dispatcher.rst @@ -17,9 +17,9 @@ pattern, the *Observer*, to allow any kind of behaviors to be attached to our framework; the Symfony2 EventDispatcher Component implements a lightweight version of this pattern: -.. code-block:: sh +.. code-block:: bash - $ php composer.phar require symfony/event-dispatcher + $ composer require symfony/event-dispatcher How does it work? The *dispatcher*, the central object of the event dispatcher system, notifies *listeners* of an *event* dispatched to it. Put another way: @@ -165,7 +165,7 @@ type is HTML (these conditions demonstrate the ease of manipulating the Request and Response data from your code). So far so good, but let's add another listener on the same event. Let's say -that I want to set the ``Content-Length`` of the Response if it is not already +that we want to set the ``Content-Length`` of the Response if it is not already set:: $dispatcher->addListener('response', function (Simplex\ResponseEvent $event) { diff --git a/create_framework/10-http-kernel.rst b/create_framework/10-http-kernel.rst index c6828ca7907..3c236d21a65 100644 --- a/create_framework/10-http-kernel.rst +++ b/create_framework/10-http-kernel.rst @@ -99,8 +99,8 @@ content and check that the number only changes every 10 seconds:: Using HTTP cache headers to manage your application cache is very powerful and allows you to tune finely your caching strategy as you can use both the expiration and the validation models of the HTTP specification. If you are not -comfortable with these concepts, I highly recommend you to read the `HTTP -caching`_ chapter of the Symfony2 documentation. +comfortable with these concepts, read the `HTTP caching`_ chapter of the +Symfony2 documentation. The Response class contains many other methods that let you configure the HTTP cache very easily. One of the most powerful is ``setCache()`` as it diff --git a/create_framework/12-dependency-injection.rst b/create_framework/12-dependency-injection.rst index fbeb189d567..9ebc1d0181c 100644 --- a/create_framework/12-dependency-injection.rst +++ b/create_framework/12-dependency-injection.rst @@ -91,9 +91,9 @@ front controller? As you might expect, there is a solution. We can solve all these issues and some more by using the Symfony2 dependency injection container: -.. code-block:: sh +.. code-block:: bash - $ php composer.phar require symfony/dependency-injection + $ composer require symfony/dependency-injection Create a new file to host the dependency injection container configuration:: @@ -243,8 +243,8 @@ in great details, but hopefully it gives you enough information to get started on your own and to better understand how the Symfony2 framework works internally. -If you want to learn more, I highly recommend you to read the source code of -the `Silex`_ micro-framework, and especially its `Application`_ class. +If you want to learn more, read the source code of the `Silex`_ +micro-framework, and especially its `Application`_ class. Have fun! From 126bcef1e7f8d6695e0340bfbac4beca649b8e8a Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 5 Feb 2015 17:25:43 +0100 Subject: [PATCH 71/74] removed external references alignement --- create_framework/01-introduction.rst | 8 +++--- create_framework/02-http-foundation.rst | 28 ++++++++++---------- create_framework/05-templating.rst | 2 +- create_framework/06-http-kernel.rst | 4 +-- create_framework/08-unit-testing.rst | 4 +-- create_framework/10-http-kernel.rst | 4 +-- create_framework/12-dependency-injection.rst | 4 +-- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/create_framework/01-introduction.rst b/create_framework/01-introduction.rst index 6891897a854..5b31758a01e 100644 --- a/create_framework/01-introduction.rst +++ b/create_framework/01-introduction.rst @@ -132,8 +132,8 @@ Use the PHP built-in server to test this great application in a browser In the next chapter, we are going to introduce the HttpFoundation Component and see what it brings us. -.. _`Symfony2`: http://symfony.com/ -.. _`documentation`: http://symfony.com/doc -.. _`Silex`: http://silex.sensiolabs.org/ -.. _`Composer`: http://packagist.org/about-composer +.. _`Symfony2`: http://symfony.com/ +.. _`documentation`: http://symfony.com/doc +.. _`Silex`: http://silex.sensiolabs.org/ +.. _`Composer`: http://packagist.org/about-composer .. _`download and install`: https://getcomposer.org/doc/01-basic-usage.md diff --git a/create_framework/02-http-foundation.rst b/create_framework/02-http-foundation.rst index a1662f7cf75..42e32112121 100644 --- a/create_framework/02-http-foundation.rst +++ b/create_framework/02-http-foundation.rst @@ -310,18 +310,18 @@ component is the start of better interoperability between all frameworks and applications using it (like `Symfony2`_, `Drupal 8`_, `phpBB 4`_, `ezPublish 5`_, `Laravel`_, `Silex`_, and `more`_). -.. _`Twig`: http://twig.sensiolabs.com/ +.. _`Twig`: http://twig.sensiolabs.com/ .. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html -.. _`HTTP specification`: http://tools.ietf.org/wg/httpbis/ -.. _`audited`: http://symfony.com/blog/symfony2-security-audit -.. _`Symfony2`: http://symfony.com/ -.. _`Drupal 8`: http://drupal.org/ -.. _`phpBB 4`: http://www.phpbb.com/ -.. _`ezPublish 5`: http://ez.no/ -.. _`Laravel`: http://laravel.com/ -.. _`Silex`: http://silex.sensiolabs.org/ -.. _`Midgard CMS`: http://www.midgard-project.org/ -.. _`Zikula`: http://zikula.org/ -.. _`autoloaded`: http://php.net/autoload -.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md -.. _`more`: http://symfony.com/components/HttpFoundation +.. _`HTTP specification`: http://tools.ietf.org/wg/httpbis/ +.. _`audited`: http://symfony.com/blog/symfony2-security-audit +.. _`Symfony2`: http://symfony.com/ +.. _`Drupal 8`: http://drupal.org/ +.. _`phpBB 4`: http://www.phpbb.com/ +.. _`ezPublish 5`: http://ez.no/ +.. _`Laravel`: http://laravel.com/ +.. _`Silex`: http://silex.sensiolabs.org/ +.. _`Midgard CMS`: http://www.midgard-project.org/ +.. _`Zikula`: http://zikula.org/ +.. _`autoloaded`: http://php.net/autoload +.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md +.. _`more`: http://symfony.com/components/HttpFoundation diff --git a/create_framework/05-templating.rst b/create_framework/05-templating.rst index f2494c332e7..45440edf055 100644 --- a/create_framework/05-templating.rst +++ b/create_framework/05-templating.rst @@ -187,4 +187,4 @@ probably all you need to create simple websites like those fancy one-page `websites`_ and hopefully a few others. .. _`callbacks`: http://php.net/callback#language.types.callback -.. _`websites`: http://kottke.org/08/02/single-serving-sites +.. _`websites`: http://kottke.org/08/02/single-serving-sites diff --git a/create_framework/06-http-kernel.rst b/create_framework/06-http-kernel.rst index e921a7c6db0..3039abcf4da 100644 --- a/create_framework/06-http-kernel.rst +++ b/create_framework/06-http-kernel.rst @@ -195,6 +195,6 @@ Let's conclude with the new version of our framework:: Think about it once more: our framework is more robust and more flexible than ever and it still has less than 40 lines of code. -.. _`reflection`: http://php.net/reflection -.. _`FrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html +.. _`reflection`: http://php.net/reflection +.. _`FrameworkExtraBundle`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/converters.html .. _`controllers as services`: http://symfony.com/doc/current/cookbook/controller/service.html diff --git a/create_framework/08-unit-testing.rst b/create_framework/08-unit-testing.rst index e0d2ba1d6ff..dc1c75715b9 100644 --- a/create_framework/08-unit-testing.rst +++ b/create_framework/08-unit-testing.rst @@ -189,6 +189,6 @@ Symfony2 code. Now that we are confident (again) about the code we have written, we can safely think about the next batch of features we want to add to our framework. -.. _`PHPUnit`: http://www.phpunit.de/manual/current/en/index.html +.. _`PHPUnit`: http://www.phpunit.de/manual/current/en/index.html .. _`test doubles`: http://www.phpunit.de/manual/current/en/test-doubles.html -.. _`XDebug`: http://xdebug.org/ +.. _`XDebug`: http://xdebug.org/ diff --git a/create_framework/10-http-kernel.rst b/create_framework/10-http-kernel.rst index 3c236d21a65..a4afdd3754b 100644 --- a/create_framework/10-http-kernel.rst +++ b/create_framework/10-http-kernel.rst @@ -188,5 +188,5 @@ the many features built into the HttpKernel component; HTTP caching being just one of them but an important one as it can make your applications fly! .. _`HTTP caching`: http://symfony.com/doc/current/book/http_cache.html -.. _`ESI`: http://en.wikipedia.org/wiki/Edge_Side_Includes -.. _`Varnish`: https://www.varnish-cache.org/ +.. _`ESI`: http://en.wikipedia.org/wiki/Edge_Side_Includes +.. _`Varnish`: https://www.varnish-cache.org/ diff --git a/create_framework/12-dependency-injection.rst b/create_framework/12-dependency-injection.rst index 9ebc1d0181c..000e49b9aea 100644 --- a/create_framework/12-dependency-injection.rst +++ b/create_framework/12-dependency-injection.rst @@ -248,6 +248,6 @@ micro-framework, and especially its `Application`_ class. Have fun! -.. _`Pimple`: https://github.com/fabpot/Pimple -.. _`Silex`: https://silex.sensiolabs.org/ +.. _`Pimple`: https://github.com/fabpot/Pimple +.. _`Silex`: https://silex.sensiolabs.org/ .. _`Application`: https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php From db9fa4361587222e0dbe50f61d9a5f9e124dae7e Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 22 Jun 2015 16:31:35 +0200 Subject: [PATCH 72/74] Made a lot of improvements suggested by reviewers --- create_framework/01-introduction.rst | 91 ++++++++----------- create_framework/02-http-foundation.rst | 38 ++------ create_framework/03-front-controller.rst | 16 ---- create_framework/04-routing.rst | 8 +- create_framework/05-templating.rst | 6 -- create_framework/06-http-kernel.rst | 4 +- .../07-separation-of-concerns.rst | 8 -- create_framework/08-unit-testing.rst | 10 +- create_framework/09-event-dispatcher.rst | 16 +--- create_framework/10-http-kernel.rst | 10 +- create_framework/11-http-kernel.rst | 12 +-- create_framework/12-dependency-injection.rst | 16 +--- 12 files changed, 62 insertions(+), 173 deletions(-) diff --git a/create_framework/01-introduction.rst b/create_framework/01-introduction.rst index 5b31758a01e..8975d349b37 100644 --- a/create_framework/01-introduction.rst +++ b/create_framework/01-introduction.rst @@ -1,14 +1,14 @@ Introduction ============ -`Symfony2`_ is a reusable set of standalone, decoupled, and cohesive PHP +`Symfony`_ is a reusable set of standalone, decoupled and cohesive PHP components that solve common web development problems. Instead of using these low-level components, you can use the ready-to-be-used -Symfony2 full-stack web framework, which is based on these components... or -you can create your very own framework. This book is about the latter. +Symfony full-stack web framework, which is based on these components... or +you can create your very own framework. This tutorial is about the latter. -Why would you like to create your own framework? +Why would you Like to Create your Own Framework? ------------------------------------------------ Why would you like to create your own framework in the first place? If you @@ -18,7 +18,7 @@ creating your own altogether. Most of the time, they are right but there are a few good reasons to start creating your own framework: * To learn more about the low level architecture of modern web frameworks in - general and about the Symfony2 full-stack framework internals in particular; + general and about the Symfony full-stack framework internals in particular; * To create a framework tailored to your very specific needs (just be sure first that your needs are really specific); @@ -34,55 +34,55 @@ a few good reasons to start creating your own framework: This tutorial will gently guide you through the creation of a web framework, one step at a time. At each step, you will have a fully-working framework that -you can use as is or as a start for your very own. We will start with simple -frameworks and more features will be added with time. Eventually, you will have +you can use as is or as a start for your very own. It will start with a simple +framework and more features will be added with time. Eventually, you will have a fully-featured full-stack web framework. And of course, each step will be the occasion to learn more about some of the -Symfony2 Components. +Symfony Components. .. tip:: If you don't have time to read the whole book, or if you want to get started fast, you can also have a look at `Silex`_, a micro-framework - based on the Symfony2 Components. The code is rather slim and it leverages - many aspects of the Symfony2 Components. - -Many modern web frameworks advertize themselves as being MVC frameworks. We -won't talk about the MVC pattern as the Symfony2 Components are able to create -any type of frameworks, not just the ones that follow the MVC architecture. -Anyway, if you have a look at the MVC semantics, this book is about how to -create the Controller part of a framework. For the Model and the View, it -really depends on your personal taste and you can use any existing -third-party libraries (Doctrine, Propel, or plain-old PDO for the Model; PHP -or Twig for the View). + based on the Symfony Components. The code is rather slim and it leverages + many aspects of the Symfony Components. + +Many modern web frameworks advertize themselves as being MVC frameworks. This +tutorial won't talk about the MVC pattern, as the Symfony Components are able to +create any type of frameworks, not just the ones that follow the MVC +architecture. Anyway, if you have a look at the MVC semantics, this book is +about how to create the Controller part of a framework. For the Model and the +View, it really depends on your personal taste and you can use any existing +third-party libraries (Doctrine, Propel or plain-old PDO for the Model; PHP or +Twig for the View). When creating a framework, following the MVC pattern is not the right goal. The main goal should be the **Separation of Concerns**; this is probably the only design pattern that you should really care about. The fundamental principles of -the Symfony2 Components are focused on the HTTP specification. As such, the -frameworks that we are going to create should be more accurately labelled as -HTTP frameworks or Request/Response frameworks. +the Symfony Components are focused on the HTTP specification. As such, the +framework that you are going to create should be more accurately labelled as a +HTTP framework or Request/Response framework. -Before we start ---------------- +Before You Start +---------------- Reading about how to create a framework is not enough. You will have to follow -along and actually type all the examples we will work on. For that, you need a -recent version of PHP (5.3.8 or later is good enough), a web server (like -Apache or NGinx), a good knowledge of PHP and an understanding of Object -Oriented programming. +along and actually type all the examples included in this tutorial. For that, +you need a recent version of PHP (5.3.9 or later is good enough), a web server +(like Apache, NGinx or PHP's built-in web server), a good knowledge of PHP and +an understanding of Object Oriented programming. -Ready to go? Let's start. +Ready to go? Read on! Bootstrapping ------------- -Before we can even think of creating our first framework, we need to talk -about some conventions: where we will store our code, how we will name our -classes, how we will reference external dependencies, etc. +Before you can even think of creating the first framework, you need to think +about some conventions: where you will store the code, how you will name the +classes, how you will reference external dependencies, etc. -To store our framework, create a directory somewhere on your machine: +To store your new framework, create a directory somewhere on your machine: .. code-block:: bash @@ -92,20 +92,9 @@ To store our framework, create a directory somewhere on your machine: Dependency Management ~~~~~~~~~~~~~~~~~~~~~ -To install the Symfony2 Components that we need for our framework, we are going +To install the Symfony Components that you need for your framework, you are going to use `Composer`_, a project dependency manager for PHP. If you don't have it -yet, `download and install`_ Composer now: - -.. code-block:: bash - - $ curl -sS https://getcomposer.org/installer | php - -Then, generate an empty ``composer.json`` file, where Composer will store the -framework dependencies: - -.. code-block:: bash - - $ composer init -n +yet, :doc:`download and install Composer ` now. Our Project ----------- @@ -114,16 +103,15 @@ Instead of creating our framework from scratch, we are going to write the same "application" over and over again, adding one abstraction at a time. Let's start with the simplest web application we can think of in PHP:: - getPathInfo(); @@ -215,8 +203,6 @@ You can also simulate a request:: With the ``Response`` class, you can easily tweak the response:: - setContent('Hello world!'); @@ -240,8 +226,6 @@ framework? Even something as simple as getting the client IP address can be insecure:: - getClientIp()) { @@ -276,8 +256,6 @@ code in production without a proxy, it becomes trivially easy to abuse your system. That's not the case with the ``getClientIp()`` method as you must explicitly trust your reverse proxies by calling ``setTrustedProxies()``:: - getClientIp(true)) { @@ -297,7 +275,7 @@ cases by yourself. Why not using a technology that already works? its dedicated :doc:`documentation `. Believe or not but we have our first framework. You can stop now if you want. -Using just the Symfony2 HttpFoundation component already allows you to write +Using just the Symfony HttpFoundation component already allows you to write better and more testable code. It also allows you to write code faster as many day-to-day problems have already been solved for you. @@ -307,14 +285,14 @@ the wheel. I've almost forgot to talk about one added benefit: using the HttpFoundation component is the start of better interoperability between all frameworks and -applications using it (like `Symfony2`_, `Drupal 8`_, `phpBB 4`_, `ezPublish +applications using it (like `Symfony`_, `Drupal 8`_, `phpBB 4`_, `ezPublish 5`_, `Laravel`_, `Silex`_, and `more`_). .. _`Twig`: http://twig.sensiolabs.com/ -.. _`Symfony2 versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html +.. _`Symfony versus Flat PHP`: http://symfony.com/doc/current/book/from_flat_php_to_symfony2.html .. _`HTTP specification`: http://tools.ietf.org/wg/httpbis/ .. _`audited`: http://symfony.com/blog/symfony2-security-audit -.. _`Symfony2`: http://symfony.com/ +.. _`Symfony`: http://symfony.com/ .. _`Drupal 8`: http://drupal.org/ .. _`phpBB 4`: http://www.phpbb.com/ .. _`ezPublish 5`: http://ez.no/ diff --git a/create_framework/03-front-controller.rst b/create_framework/03-front-controller.rst index f53a6034b03..90e7e69dbb0 100644 --- a/create_framework/03-front-controller.rst +++ b/create_framework/03-front-controller.rst @@ -5,8 +5,6 @@ Up until now, our application is simplistic as there is only one page. To spice things up a little bit, let's go crazy and add another page that says goodbye:: - get('name', 'World'); @@ -185,8 +173,6 @@ The last thing that is repeated in each page is the call to ``setContent()``. We can convert all pages to "templates" by just echoing the content and calling the ``setContent()`` directly from the front controller script:: - Date: Mon, 22 Jun 2015 18:05:08 +0200 Subject: [PATCH 73/74] Removed the numbers from the file names --- ...injection.rst => dependency-injection.rst} | 0 ...nt-dispatcher.rst => event-dispatcher.rst} | 0 ...nt-controller.rst => front-controller.rst} | 0 ...ttp-foundation.rst => http-foundation.rst} | 0 ...st => http-kernel-controller-resolver.rst} | 0 ...l.rst => http-kernel-httpkernel-class.rst} | 0 ...st => http-kernel-httpkernelinterface.rst} | 0 create_framework/index.rst | 24 +++++++++---------- .../{01-introduction.rst => introduction.rst} | 0 .../{04-routing.rst => routing.rst} | 0 ...oncerns.rst => separation-of-concerns.rst} | 0 .../{05-templating.rst => templating.rst} | 0 .../{08-unit-testing.rst => unit-testing.rst} | 0 13 files changed, 12 insertions(+), 12 deletions(-) rename create_framework/{12-dependency-injection.rst => dependency-injection.rst} (100%) rename create_framework/{09-event-dispatcher.rst => event-dispatcher.rst} (100%) rename create_framework/{03-front-controller.rst => front-controller.rst} (100%) rename create_framework/{02-http-foundation.rst => http-foundation.rst} (100%) rename create_framework/{06-http-kernel.rst => http-kernel-controller-resolver.rst} (100%) rename create_framework/{11-http-kernel.rst => http-kernel-httpkernel-class.rst} (100%) rename create_framework/{10-http-kernel.rst => http-kernel-httpkernelinterface.rst} (100%) rename create_framework/{01-introduction.rst => introduction.rst} (100%) rename create_framework/{04-routing.rst => routing.rst} (100%) rename create_framework/{07-separation-of-concerns.rst => separation-of-concerns.rst} (100%) rename create_framework/{05-templating.rst => templating.rst} (100%) rename create_framework/{08-unit-testing.rst => unit-testing.rst} (100%) diff --git a/create_framework/12-dependency-injection.rst b/create_framework/dependency-injection.rst similarity index 100% rename from create_framework/12-dependency-injection.rst rename to create_framework/dependency-injection.rst diff --git a/create_framework/09-event-dispatcher.rst b/create_framework/event-dispatcher.rst similarity index 100% rename from create_framework/09-event-dispatcher.rst rename to create_framework/event-dispatcher.rst diff --git a/create_framework/03-front-controller.rst b/create_framework/front-controller.rst similarity index 100% rename from create_framework/03-front-controller.rst rename to create_framework/front-controller.rst diff --git a/create_framework/02-http-foundation.rst b/create_framework/http-foundation.rst similarity index 100% rename from create_framework/02-http-foundation.rst rename to create_framework/http-foundation.rst diff --git a/create_framework/06-http-kernel.rst b/create_framework/http-kernel-controller-resolver.rst similarity index 100% rename from create_framework/06-http-kernel.rst rename to create_framework/http-kernel-controller-resolver.rst diff --git a/create_framework/11-http-kernel.rst b/create_framework/http-kernel-httpkernel-class.rst similarity index 100% rename from create_framework/11-http-kernel.rst rename to create_framework/http-kernel-httpkernel-class.rst diff --git a/create_framework/10-http-kernel.rst b/create_framework/http-kernel-httpkernelinterface.rst similarity index 100% rename from create_framework/10-http-kernel.rst rename to create_framework/http-kernel-httpkernelinterface.rst diff --git a/create_framework/index.rst b/create_framework/index.rst index a7291c1966d..10517e1565c 100644 --- a/create_framework/index.rst +++ b/create_framework/index.rst @@ -3,15 +3,15 @@ Create your PHP Framework .. toctree:: - 01-introduction - 02-http-foundation - 03-front-controller - 04-routing - 05-templating - 06-http-kernel - 07-separation-of-concerns - 08-unit-testing - 09-event-dispatcher - 10-http-kernel - 11-http-kernel - 12-dependency-injection + introduction + http-foundation + front-controller + routing + templating + http-kernel-controller-resolver + separation-of-concerns + unit-testing + event-dispatcher + http-kernel-httpkernelinterface + http-kernel-httpkernel-class + dependency-injection diff --git a/create_framework/01-introduction.rst b/create_framework/introduction.rst similarity index 100% rename from create_framework/01-introduction.rst rename to create_framework/introduction.rst diff --git a/create_framework/04-routing.rst b/create_framework/routing.rst similarity index 100% rename from create_framework/04-routing.rst rename to create_framework/routing.rst diff --git a/create_framework/07-separation-of-concerns.rst b/create_framework/separation-of-concerns.rst similarity index 100% rename from create_framework/07-separation-of-concerns.rst rename to create_framework/separation-of-concerns.rst diff --git a/create_framework/05-templating.rst b/create_framework/templating.rst similarity index 100% rename from create_framework/05-templating.rst rename to create_framework/templating.rst diff --git a/create_framework/08-unit-testing.rst b/create_framework/unit-testing.rst similarity index 100% rename from create_framework/08-unit-testing.rst rename to create_framework/unit-testing.rst From bca9bae2743f663332219fbb7ac2d9470b97abfc Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Mon, 22 Jun 2015 18:29:05 +0200 Subject: [PATCH 74/74] Updated doc references in the map.rst.inc file --- create_framework/map.rst.inc | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/create_framework/map.rst.inc b/create_framework/map.rst.inc index 8e11e1700c3..574c0f5e769 100644 --- a/create_framework/map.rst.inc +++ b/create_framework/map.rst.inc @@ -1,12 +1,12 @@ -* :doc:`/create_framework/01-introduction` -* :doc:`/create_framework/02-http-foundation` -* :doc:`/create_framework/03-front-controller` -* :doc:`/create_framework/04-routing` -* :doc:`/create_framework/05-templating` -* :doc:`/create_framework/06-http-kernel` -* :doc:`/create_framework/07-separation-of-concerns` -* :doc:`/create_framework/08-unit-testing` -* :doc:`/create_framework/09-event-dispatcher` -* :doc:`/create_framework/10-http-kernel` -* :doc:`/create_framework/11-http-kernel` -* :doc:`/create_framework/12-dependency-injection` +* :doc:`/create_framework/introduction` +* :doc:`/create_framework/http-foundation` +* :doc:`/create_framework/front-controller` +* :doc:`/create_framework/routing` +* :doc:`/create_framework/templating` +* :doc:`/create_framework/http-kernel-controller-resolver` +* :doc:`/create_framework/separation-of-concerns` +* :doc:`/create_framework/unit-testing` +* :doc:`/create_framework/event-dispatcher` +* :doc:`/create_framework/http-kernel-httpkernelinterface` +* :doc:`/create_framework/http-kernel-httpkernel-class` +* :doc:`/create_framework/dependency-injection`