diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 2d19fc7..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.html diff --git a/README.md b/README.md deleted file mode 100644 index b678af6..0000000 --- a/README.md +++ /dev/null @@ -1,44 +0,0 @@ -PHP -=== - -PHP lectures by [William Durand](http://github.com/willdurand). - - -Installation ------------- - -Get the code: - - git clone git://github.com/licpro/php-slides.git --recursive - -Install [landslide](https://github.com/adamzap/landslide#installation), then -build the presentations: - - landslide iut-php.cfg - landslide iut-extended.cfg - landslide isima-php.cfg - - -Usage ------ - -Basically, build the presentation you are interested in, and open it in your -favorite browser. - - -Credits -------- - -* [Julien Muetton](http://github.com/themouette) as co-author of the very first - version of these slides -* [All - contributors](https://github.com/willdurand-edu/php-slides/graphs/contributors) - - -License -------- - -[![](http://i.creativecommons.org/l/by-nc-sa/3.0/88x31.png) -](http://creativecommons.org/licenses/by-nc-sa/3.0/)
This work is -licensed under a [Creative Commons Attribution-NonCommercial-ShareAlike 3.0 -Unported License](http://creativecommons.org/licenses/by-nc-sa/3.0/). diff --git a/css/custom.css b/css/custom.css deleted file mode 100644 index 6bc9e18..0000000 --- a/css/custom.css +++ /dev/null @@ -1,195 +0,0 @@ -@import url(http://fonts.googleapis.com/css?family=Droid+Sans); - -body { - font-size: 1.5em; - font-family: 'Droid Sans', sans-serif; -} - -p, li { - line-height: 1.5em; -} - -body, -.slides, -.slide.current { - color: #FFFFFF; - background-color: #69A8C2; -} - -div.slide h1, -div.slide h2, -div.slide h3, -div.slide h4, -div.slide h5, -div.slide h6 { - color: #3E6478; - font-weight: bold; -} - -div.slide h1 { - margin: auto; -} - -.slide.future { - display:none; -} - -.slide header:only-child h1 { - font-size: 70px; -} - -.center { - text-align: center; -} - -blockquote:before { - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1.5em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - content: "\f02d"; - margin-right: 10px; - float: left; -} - -blockquote { - margin: auto; - text-indent: 0; - background: #373637; - padding: 10px 10px 7px 10px; - clear: both; -} - -blockquote p:before, -blockquote p:after, -blockquote.no-before-icon:before { - content: none; -} - -blockquote.no-before-icon i { - margin-right: 10px; - line-height: 1.5em; - float: left; -} - -blockquote.quote p, -blockquote.info p { - display: inline; -} - -blockquote.quote:before, -blockquote.info:before { - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1.5em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - margin-right: 10px; - display: inline-block; -} - -blockquote.quote:before { - content: "\f10d"; -} - -blockquote.info:before { - content: "\f05a"; -} - -blockquote.quote:after { - font-family: FontAwesome; - font-style: normal; - font-weight: normal; - line-height: 1.5em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - content: "\f10e"; - margin-left: 10px; - display: inline-block; -} - -a, -a:visited { - color: #FFFFFF; -} - -a:hover, -a:focus { - color: #3E6478; -} - -pre { - padding: 5px 10px 5px 10px; - font-size: 75%; - font-family: Courier, monospace; -} - -code { - padding: 2px 6px 3px 6px; - font-size: 100%; - font-family: Courier, monospace; - background-color: #073642; - color: #FFF; -} - -strong, b { - font-weight: bolder; - font-size: 110%; -} - -li { - padding: 5px 0; -} - -ol li p { - margin-top: 0; - margin-bottom: 0; -} - -h1 { - margin-top: 10px; -} - -h1 img { - margin-top: 30px; -} - -img { - border-radius: 3px; - box-shadow: 3px 3px 10px 5px #373637; -} - -.no-border h1 img { - margin-top: auto; -} - -.no-border img { - border-radius: 0; - box-shadow: none; -} - -.sidebar, -.sidebar a, -.sidebar a:visited { - color: #000; -} - -#toc img { - display: none; -} - -.slides .color-red h1 { - color: #C82D26; - font-size: 8em !important; -} - -table { - margin-bottom: 3%; -} - -.more-on-this-later { - display: none; -} diff --git a/css/isima.css b/css/isima.css deleted file mode 100644 index 89bf04f..0000000 --- a/css/isima.css +++ /dev/null @@ -1,24 +0,0 @@ -code { - background-color: #ffffff; -} - -code, pre { - color: #000; -} - -div.slide h1, -div.slide h2, -div.slide h3, -div.slide h4, -div.slide h5, -div.slide h6 { - color: #fff; -} - -.more-on-this-next-week { - display: none; -} - -.more-on-this-later { - display: block; -} diff --git a/css/light.css b/css/light.css deleted file mode 100644 index b90eeb6..0000000 --- a/css/light.css +++ /dev/null @@ -1,59 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #ffffff; } -.highlight .c { color: #aaaaaa; font-style: italic } /* Comment */ -.highlight .err { color: #F00000; background-color: #F0A0A0 } /* Error */ -.highlight .k { color: #0000aa } /* Keyword */ -.highlight .cm { color: #aaaaaa; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #4c8317 } /* Comment.Preproc */ -.highlight .c1 { color: #aaaaaa; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #0000aa; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #aa0000 } /* Generic.Deleted */ -.highlight .ge { font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #aa0000 } /* Generic.Error */ -.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ -.highlight .gi { color: #00aa00 } /* Generic.Inserted */ -.highlight .go { color: #888888 } /* Generic.Output */ -.highlight .gp { color: #555555 } /* Generic.Prompt */ -.highlight .gs { font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #aa0000 } /* Generic.Traceback */ -.highlight .kc { color: #0000aa } /* Keyword.Constant */ -.highlight .kd { color: #0000aa } /* Keyword.Declaration */ -.highlight .kn { color: #0000aa } /* Keyword.Namespace */ -.highlight .kp { color: #0000aa } /* Keyword.Pseudo */ -.highlight .kr { color: #0000aa } /* Keyword.Reserved */ -.highlight .kt { color: #00aaaa } /* Keyword.Type */ -.highlight .m { color: #009999 } /* Literal.Number */ -.highlight .s { color: #aa5500 } /* Literal.String */ -.highlight .na { color: #1e90ff } /* Name.Attribute */ -.highlight .nb { color: #00aaaa } /* Name.Builtin */ -.highlight .nc { color: #00aa00; text-decoration: underline } /* Name.Class */ -.highlight .no { color: #aa0000 } /* Name.Constant */ -.highlight .nd { color: #888888 } /* Name.Decorator */ -.highlight .ni { color: #800000; font-weight: bold } /* Name.Entity */ -.highlight .nf { color: #00aa00 } /* Name.Function */ -.highlight .nn { color: #00aaaa; text-decoration: underline } /* Name.Namespace */ -.highlight .nt { color: #1e90ff; font-weight: bold } /* Name.Tag */ -.highlight .nv { color: #aa0000 } /* Name.Variable */ -.highlight .ow { color: #0000aa } /* Operator.Word */ -.highlight .w { color: #bbbbbb } /* Text.Whitespace */ -.highlight .mf { color: #009999 } /* Literal.Number.Float */ -.highlight .mh { color: #009999 } /* Literal.Number.Hex */ -.highlight .mi { color: #009999 } /* Literal.Number.Integer */ -.highlight .mo { color: #009999 } /* Literal.Number.Oct */ -.highlight .sb { color: #aa5500 } /* Literal.String.Backtick */ -.highlight .sc { color: #aa5500 } /* Literal.String.Char */ -.highlight .sd { color: #aa5500 } /* Literal.String.Doc */ -.highlight .s2 { color: #aa5500 } /* Literal.String.Double */ -.highlight .se { color: #aa5500 } /* Literal.String.Escape */ -.highlight .sh { color: #aa5500 } /* Literal.String.Heredoc */ -.highlight .si { color: #aa5500 } /* Literal.String.Interpol */ -.highlight .sx { color: #aa5500 } /* Literal.String.Other */ -.highlight .sr { color: #009999 } /* Literal.String.Regex */ -.highlight .s1 { color: #aa5500 } /* Literal.String.Single */ -.highlight .ss { color: #0000aa } /* Literal.String.Symbol */ -.highlight .bp { color: #00aaaa } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #aa0000 } /* Name.Variable.Class */ -.highlight .vg { color: #aa0000 } /* Name.Variable.Global */ -.highlight .vi { color: #aa0000 } /* Name.Variable.Instance */ -.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ diff --git a/css/solarized.css b/css/solarized.css deleted file mode 100644 index 20e3a35..0000000 --- a/css/solarized.css +++ /dev/null @@ -1 +0,0 @@ -.highlight{background-color:#073642;color:#93a1a1}.highlight .c{color:#586e75 !important;font-style:italic !important}.highlight .cm{color:#586e75 !important;font-style:italic !important}.highlight .cp{color:#586e75 !important;font-style:italic !important}.highlight .c1{color:#586e75 !important;font-style:italic !important}.highlight .cs{color:#586e75 !important;font-weight:bold !important;font-style:italic !important}.highlight .err{color:#dc322f !important;background:none !important}.highlight .k{color:#cb4b16 !important}.highlight .o{color:#93a1a1 !important;font-weight:bold !important}.highlight .p{color:#93a1a1 !important}.highlight .ow{color:#2aa198 !important;font-weight:bold !important}.highlight .gd{color:#93a1a1 !important;background-color:#372c34 !important;display:inline-block}.highlight .gd .x{color:#93a1a1 !important;background-color:#4d2d33 !important;display:inline-block}.highlight .ge{color:#93a1a1 !important;font-style:italic !important}.highlight .gr{color:#aa0000}.highlight .gh{color:#586e75 !important}.highlight .gi{color:#93a1a1 !important;background-color:#1a412b !important;display:inline-block}.highlight .gi .x{color:#93a1a1 !important;background-color:#355720 !important;display:inline-block}.highlight .go{color:#888888}.highlight .gp{color:#555555}.highlight .gs{color:#93a1a1 !important;font-weight:bold !important}.highlight .gu{color:#6c71c4 !important}.highlight .gt{color:#aa0000}.highlight .kc{color:#859900 !important;font-weight:bold !important}.highlight .kd{color:#268bd2 !important}.highlight .kp{color:#cb4b16 !important;font-weight:bold !important}.highlight .kr{color:#d33682 !important;font-weight:bold !important}.highlight .kt{color:#2aa198 !important}.highlight .n{color:#268bd2 !important}.highlight .na{color:#268bd2 !important}.highlight .nb{color:#859900 !important}.highlight .nc{color:#d33682 !important}.highlight .no{color:#b58900 !important}.highlight .ni{color:#800080}.highlight .nl{color:#859900 !important}.highlight .ne{color:#268bd2 !important;font-weight:bold !important}.highlight .nf{color:#268bd2 !important;font-weight:bold !important}.highlight .nn{color:#b58900 !important}.highlight .nt{color:#268bd2 !important;font-weight:bold !important}.highlight .nx{color:#b58900 !important}.highlight .bp{color:#999999}.highlight .vc{color:#008080}.highlight .vg{color:#268bd2 !important}.highlight .vi{color:#268bd2 !important}.highlight .nv{color:#268bd2 !important}.highlight .w{color:#bbbbbb}.highlight .mf{color:#2aa198 !important}.highlight .m{color:#2aa198 !important}.highlight .mh{color:#2aa198 !important}.highlight .mi{color:#2aa198 !important}.highlight .mo{color:#009999}.highlight .s{color:#2aa198 !important}.highlight .sb{color:#d14}.highlight .sc{color:#d14}.highlight .sd{color:#2aa198 !important}.highlight .s2{color:#2aa198 !important}.highlight .se{color:#dc322f !important}.highlight .sh{color:#d14}.highlight .si{color:#268bd2 !important}.highlight .sx{color:#d14}.highlight .sr{color:#2aa198 !important}.highlight .s1{color:#2aa198 !important}.highlight .ss{color:#990073}.highlight .il{color:#009999}.highlight div .gd,.highlight div .gd .x,.highlight div .gi,.highlight div .gi .x{display:inline-block;width:100%} diff --git a/extended.html b/extended.html new file mode 100644 index 0000000..a4fa159 --- /dev/null +++ b/extended.html @@ -0,0 +1,9726 @@ + + + + + + + PHP Extended + + + + + + + + + + + + + + + + + +
+
+
+ 00:00:00 +
+
+
+
+
+ + +
+
+
+ +

PHP Extended

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Let's Do Professional Development Now!

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Who Is Speaking?

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

William DURAND

+ + +

PhD / CTO TailorDev

+

Graduated from IUT, ISIMA, Blaise Pascal University. Worked at:

+ +

Open-Source evangelist:

+ +

+ twitter.com/couac +  |  + github.com/willdurand +  |  + williamdurand.fr +

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

edu@drnd.me

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +
    +
  • Symfony
      +
    • Controllers
    • +
    • Templating
    • +
    • Dependency Injection
    • +
    • Command Line
    • +
    • Forms
    • +
    • Validation
    • +
    • Translation
    • +
    • HTTP Cache
    • +
    +
  • +
  • Stack PHP
  • +
  • Hack
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Framework To Simplify Developments

+ + +

A framework helps you work better by structuring developments, +and faster by reusing generic modules.

+

A framework facilitates long-term maintenance and scalability by +complying with standard development rules.

+

Compliance with development standards also simplifies integrating and +interfacing the application with the rest of the information system.

+

In other words, it works as a tool to make the development process +easier and more productive.

+

Most of the time, a framework implements many kinds of design patterns.

+
+

Read more: Symfony explained to a +Developer.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is Symfony?

+ + +

First of all:

+
+

Symfony is a reusable set of standalone, decoupled, and cohesive PHP + components that solve common web development problems.

+
+ +

Then, based on these components:

+
+

Symfony is also a full-stack web framework.

+
+ +

Fabien Potencier, +http://fabien.potencier.org/article/49/what-is-symfony2.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Is Symfony A MVC Framework?

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

NO!

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Why You Should Use Symfony

+ + +

Symfony is built on powerful concepts:

+
    +
  • Separation of Concerns;
  • +
  • Pragmatism;
  • +
  • Best Practices.
  • +
+

+

It has been written by ~1502 developers.

+

Open Source, MIT licensed.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Symfony Components

+ + +

The Components implement common features needed to develop websites.

+

They are the foundation of the Symfony full-stack framework, but they can +also be used standalone even if you don't use the framework as they don't +have any mandatory dependencies.

+

There are ~30 components, including:

+
BrowserKit              EventDispatcher     OptionsResolver     Templating
+ClassLoader             ExpressionLanguage  Process             Translation
+Config                  Filesystem          PropertyAccess      VarDumper
+Console                 Finder              PropertyInfo        Yaml
+CssSelector             Form                Routing
+Debug                   HttpFoundation      Security
+DependencyInjection     HttpKernel          Serializer
+DomCrawler              Intl                Stopwatch
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Ready With Components

+ + +

Say you want to play with YAML files, start by requiring the symfony/yaml +component into your composer.json file:

+
{
+    "require": {
+        "symfony/yaml": "~3.0"
+    }
+}
+
+ +

Install it by running php composer.phar install, and use it:

+
require __DIR__ . '/vendor/autoload.php';
+
+use Symfony\Component\Yaml\Yaml;
+
+$yaml = Yaml::parse('/path/to/file.yml');
+
+ +
+

http://symfony.com/doc/current/components/yaml/introduction.html

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Full-Stack Framework

+ + +

The Symfony Framework accomplishes two distinct tasks:

+
    +
  • Provides a selection of components;
  • +
  • Provides sensible configuration and a "glue" library that ties all of these + pieces together.
  • +
+

The goal of the framework is to integrate many independent tools in order to +provide a consistent experience for the developer. Even the framework itself is +a Symfony bundle (i.e. a plugin) that can be configured or replaced entirely.

+

Symfony provides a powerful set of tools for rapidly developing web +applications without imposing on your application.

+
+

http://symfony.com/doc/current/book/index.html

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Overall Architecture

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Symfony Request

+ + +
use Symfony\Component\HttpFoundation\Request;
+
+$request = Request::createFromGlobals();
+
+// the URI being requested (e.g. /about) minus any query parameters
+$request->getPathInfo();
+
+// the HTTP verb
+$request->getMethod();
+
+// GET variables
+$request->query->get('foo');
+
+// POST variables
+$request->request->get('bar');
+
+// SERVER variables
+$request->server->get('HTTP_HOST');
+
+// retrieve an HTTP request header, with normalized, lowercase keys
+$request->headers->get('host');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Symfony Response

+ + +
use Symfony\Component\HttpFoundation\Response;
+
+$response = new Response();
+
+$response->setContent(<<<HTML
+<html>
+    <body>
+        <h1>Hello world!</h1>
+    </body>
+</html>
+HTML
+);
+
+$response->setStatusCode(200);
+
+$response->headers->set('Content-Type', 'text/html');
+
+// prints the HTTP headers followed by the content
+$response->send();
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Simplest Front Controller Ever

+ + +
// index.php
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+
+$request = Request::createFromGlobals();
+$path    = $request->getPathInfo();
+
+if (in_array($path, ['', '/'])) {
+    $response = new Response('Welcome to the homepage.');
+} elseif ('/hello' === $path) {
+    $response = new Response('hello, World!');
+} else {
+    $response = new Response('Page not found.', 404);
+}
+
+$response->send();
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Symfony Application Flow

+ + +

It's all about transforming a Request into a Response: +

+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Routing Definition

+ + +

The routing system determines which PHP function should be executed based on +information from the request and routing configuration you've created.

+
# app/config/routing.yml
+hello:
+    path:  /hello
+    defaults: { _controller: AppBundle:Main:hello }
+
+ +

The AppBundle:Main:hello string is a short syntax that points to a +specific PHP method named helloAction() inside a class called +MainController.

+
+

+ This example uses YAML to define the routing configuration. + Routing configuration can also be written in other formats such as XML or PHP. +

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Your First Controller

+ + +

In Symfony, a method in a controller is called an action. The convention is +to suffix each method with Action.

+

Also, each controller should be suffixed with Controller.

+
// src/AppBundle/Controller/MainController.php
+namespace AppBundle\Controller;
+
+use Symfony\Component\HttpFoundation\Response;
+
+class MainController
+{
+    public function helloAction()
+    {
+        return new Response('<h1>Hello, World!</h1>');
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Symfony Project (1/2)

+ + +

Recommended structure of a Symfony (3.x) project:

+
path/to/project/
+    app/
+        config/
+        Resources/
+            views/
+    bin/
+        console
+    src/
+        ...
+    tests/
+        ...
+    var/
+        cache/
+        logs/
+        sessions/
+    vendor/
+        ...
+    web/
+        app.php
+        ...
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Symfony Project (2/2)

+ + +

Each directory has its own purpose (and set of files):

+
    +
  • app/ contains the application kernel, views, and the configuration;
  • +
  • src/ contains your bundles;
  • +
  • tests/ contains your tests;
  • +
  • var/ contains files that change often (like in Unix systems);
  • +
  • vendor/ contains your dependencies;
  • +
  • web/ contains your front controllers and your assets.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Application Kernel

+ + +

This is the central part of your application:

+
// app/AppKernel.php
+use Symfony\Component\HttpKernel\Kernel;
+
+class AppKernel extends Kernel
+{
+    public function registerBundles()
+    {
+        $bundles = [
+            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+            // ...
+        ];
+
+        if (in_array($this->getEnvironment(), ['dev', 'test'])) {
+            $bundles[] = // dev bundle;
+        }
+
+        return $bundles;
+    }
+
+    // ...
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Application Configuration

+ + +

An application consists of a collection of "bundles" representing all of the +features and capabilities of your application.

+

Each "bundle" can be customized via configuration files written in YAML, XML +or PHP.

+

By default, the main configuration file lives in the app/config/ +directory and is called either config.yml, config.xml or config.php +depending on which format you prefer.

+

Symfony is all about configuring everything, and you can do pretty much +everything you want. That's why people agreed on some conventions, but then +again, a convention is just A way to do things, not THE way to do them.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

YAML Configuration

+ + +

Example:

+
# app/config/config.yml
+imports:
+    - { resource: parameters.yml }
+    - { resource: security.yml }
+
+framework:
+    secret: '%secret%'
+    router: { resource: '%kernel.root_dir%/config/routing.yml' }
+    # ...
+
+# Twig Configuration
+twig:
+    debug:            '%kernel.debug%'
+    strict_variables: '%kernel.debug%'
+
+# ...
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

XML Configuration

+ + +

Example:

+
<!-- app/config/config.xml -->
+<imports>
+    <import resource="parameters.yml"/>
+    <import resource="security.yml"/>
+</imports>
+
+<framework:config secret="%secret%">
+    <framework:router resource="%kernel.root_dir%/config/routing.xml"/>
+    <!-- ... -->
+</framework:config>
+
+<!-- Twig Configuration -->
+<twig:config debug="%kernel.debug%" strict-variables="%kernel.debug%"/>
+
+<!-- ... -->
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Configuration

+ + +

Example:

+
$this->import('parameters.yml');
+$this->import('security.yml');
+
+$container->loadFromExtension('framework', [
+    'secret' => '%secret%',
+    'router' => [
+        'resource' => '%kernel.root_dir%/config/routing.php'
+    ],
+    // ...
+]);
+
+// Twig Configuration
+$container->loadFromExtension('twig', [
+    'debug'            => '%kernel.debug%',
+    'strict_variables' => '%kernel.debug%',
+]);
+
+// ...
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Rules (Well... My Rules)

+ + +

The main configuration MUST be written in YAML:

+
# app/config/config.yml
+# ...
+twig:
+    debug:            '%kernel.debug%'
+    strict_variables: '%kernel.debug%'
+
+ +

The routing definition MUST be written in YAML:

+
# app/config/routing.yml
+hello:
+    path:  /hello
+    defaults: { _controller: AppBundle:Main:hello }
+
+ +

The DI Container configuration MUST be written in XML:

+
<services>
+    <service id="acme_demo.controllers.main"
+        class="AppBundle\MainController" />
+</services>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Environments

+ + +

An application can run in various environments. The different environments +share the same PHP code, but use different configuration.

+

A Symfony project generally uses three environments: dev, test and prod.

+
// web/app.php
+
+// ...
+$kernel = new AppKernel('prod', false);
+
+ +

The AppKernel class is responsible for actually loading the configuration file +of your choice:

+
// app/AppKernel.php
+public function registerContainerConfiguration(LoaderInterface $loader)
+{
+    $loader->load(
+        __DIR__ . '/config/config_' . $this->getEnvironment() . '.yml'
+    );
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is A Bundle?

+ + +

A Bundle is a directory containing a set of files (PHP files, stylesheets, +JavaScripts, images, ...) that implement a single feature (a blog, a forum, +etc).

+

It should be reusable, so that you don't reinvent the wheel each time you +need a common feature. In Symfony, (almost) everything lives inside a bundle.

+

In order to use a bundle in your application, you need to register it in the +AppKernel, using the registerBundles() method:

+
public function registerBundles()
+{
+    $bundles = array(
+        // ...
+
+        new My\AwesomeBundle\MyAwesomeBundle(),
+    );
+
+    // ...
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Bundle: Directory Structure

+ + +

Recommended structure for a bundle:

+
XXX/...
+    DemoBundle/
+        DemoBundle.php
+        Controller/
+        Resources/
+            config/
+            doc/
+                index.rst
+            translations/
+            views/
+            public/
+        Tests/
+        LICENSE
+
+ +

The DemoBundle class is mandatory, and both LICENSE and +Resources/doc/index.rst files should be present.

+

The XXX directory(ies) reflects the namespace structure of the bundle.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Bundle: Where To Put Your Classes?

+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeDirectory
CommandsCommand/
ControllersController/
Service Container ExtensionsDependencyInjection/
Event ListenersEventListener/
ConfigurationResources/config/
Web ResourcesResources/public/
Translation filesResources/translations/
TemplatesResources/views/
Unit and Functional TestsTests/
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Creating a Bundle

+ + +

A bundle has to extend the Symfony\Component\HttpKernel\Bundle\Bundle +class:

+
// src/Acme/MyFirstBundle/AcmeMyFirstBundle.php
+namespace Acme\MyFirstBundle;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+class AcmeMyFirstBundle extends Bundle
+{
+}
+
+ +

Then, you can register your bundle:

+
// app/AppKernel.php
+public function registerBundles()
+{
+    $bundles = array(
+        new Acme\MyFirstBundle\AcmeMyFirstBundle(),
+    );
+
+    return $bundles;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Web Directory

+ + +

The web root directory is the home of all public and static files including +images, stylesheets, and JavaScript files. It is also where each front +controller lives:

+
// web/app.php
+require_once __DIR__.'/../app/bootstrap.php.cache';
+require_once __DIR__.'/../app/AppKernel.php';
+
+use Symfony\Component\HttpFoundation\Request;
+
+$kernel   = new AppKernel('prod', false);
+$request  = Request::createFromGlobals();
+$response = $kernel->handle($request);
+$response->send();
+
+ +

The front controller file (app.php in this example) is the actual PHP file +that's executed when using a Symfony application and its job is to use a +Kernel class, AppKernel, to bootstrap the application, for a given +environment.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Summary

+ + +

Creating a page is a three-step process involving a route, a controller, and +(optionally) a template.

+

Each project contains just a few main directories: web/ (web assets and the +front controllers), app/ (configuration), src/ (your bundles), and vendor/ +(third-party code).

+

Each feature in Symfony (including the Symfony framework core) is organized +into a bundle, which is a structured set of files for that feature.

+

The configuration for each bundle lives in the Resources/config directory of the +bundle and can be specified in YAML, XML or PHP.

+

The global application configuration lives in the app/config/ directory.

+

Each environment is accessible via a different front controller (e.g. app.php +and app_dev.php) and loads a different configuration file.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Read The Best Practices!

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Controllers

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Request, Controller, Response

+ + +

A controller is a PHP function you create that takes information from the +HTTP request and constructs and returns an HTTP response.

+

Every request handled by a Symfony project goes through the same lifecycle:

+
    +
  1. Each request is handled by a single front controller file (e.g. app.php or +app_dev.php) that bootstraps the application;
  2. +
  3. The Router reads information from the request (e.g. the URI), finds a route +that matches that information, and reads the _controller parameter from the +route;
  4. +
  5. The controller from the matched route is executed and the code inside the +controller creates and returns a Response object;
  6. +
  7. The HTTP headers and content of the Response object are sent back to the +client.
  8. +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Simplest Page Ever

+ + +

Routing Definition

+
# app/config/routing.yml
+homepage:
+    path:  /
+    defaults: { _controller: AppBundle:Hello:index }
+
+ +

Controller Implementation

+
// src/AppBundle/Controller/HelloController.php
+namespace AppBundle\Controller;
+
+use Symfony\Component\HttpFoundation\Response;
+
+class HelloController
+{
+    public function indexAction()
+    {
+        return new Response('Home, Sweet Home!');
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Controller Naming Pattern

+ + +

Every route must have a _controller parameter, which dictates which controller +should be executed when that route is matched.

+

This parameter uses a simple string pattern called the logical controller name. +The pattern has three parts, each separated by a colon: bundle:controller:action.

+

For example, a _controller value of AcmeBlogBundle:Blog:show means:

+
    +
  • Bundle: AcmeBlogBundle;
  • +
  • Controller Class: BlogController;
  • +
  • Method Name: showAction.
  • +
+

Notice that Symfony adds the string Controller to the class name (Blog => +BlogController) and Action to the method name (show => showAction).

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Route Params as Controller Args

+ + +

Routing Definition

+
# src/AppBundle/Resources/config/routing.yml
+app.hello_hello:
+    path:  /hello/{name}
+    defaults: { _controller: AppBundle:Hello:hello }
+    requirements:
+        _method: GET
+
+ +

Controller Implementation

+
// src/AppBundle/Controller/HelloController.php
+
+class HelloController
+{
+    // ...
+
+    public function helloAction($name)
+    {
+        return new Response(sprintf('Home, Sweet %s!', $name));
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Request as a Controller Argument

+ + +

For convenience, you can also have Symfony pass you the Request object as an +argument to your controller:

+
use Symfony\Component\HttpFoundation\Request;
+
+class HelloController
+{
+    // ...
+
+    public function updateAction(Request $request)
+    {
+        // do something useful with $request
+    }
+}
+
+ +

This is useful when you are working with forms.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Base Controller Class

+ + +

Symfony comes with a base Controller class that assists with some of the most +common controller tasks and gives your controller class access to any resource +it might need:

+
use Symfony\Bundle\FrameworkBundle\Controller\Controller
+
+class HelloController extends Controller
+{
+    // ...
+}
+
+ +

Redirecting

+
$this->redirect($this->generateUrl('homepage'));
+
+ +

Rendering Templates

+
return $this->render(
+    'hello/hello.html.twig', array('name' => $name)
+);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Response

+ + +

The only requirement for a controller is to return a Response object.

+

Create a simple Response with a 200 status code:

+
use Symfony\Component\HttpFoundation\Response;
+
+$response = new Response('Hello, ' . $name, 200);
+
+ +

Create a JSON response with a 200 status code:

+
$response = new Response(json_encode(array('name' => $name)));
+$response->headers->set('Content-Type', 'application/json');
+
+ +

Or:

+
use Symfony\Component\HttpFoundation\JsonResponse;
+
+$response = new JsonResponse(array('name' => $name));
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Routing

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Basic Route Configuration

+ + +

The Symfony router lets you define URLs that you map to different areas of +your application.

+

A route is a map from a URL path to a controller. Each route is named, and +maps a path to a _controller:

+
# app/config/routing.yml
+homepage:
+    path:  /
+    defaults: { _controller: AppBundle:Hello:index }
+
+ +

This route matches the homepage (/) and maps it to the +AppBundle:Hello:index controller.

+
+

http://symfony.com/doc/master/book/routing.html

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Routing with Placeholders (1/2)

+ + +

Required Placeholders

+
blog:
+    path:      /blog/{page}
+    defaults:  { _controller: AcmeBlogBundle:Blog:index }
+
+ +

The path will match anything that looks like /blog/*.

+

Even better, the value matching the {page} placeholder will be available +inside your controller.

+

/blog will not match.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Routing with Placeholders (2/2)

+ + +

Optional Placeholders

+
blog:
+    path:      /blog/{page}
+    defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
+
+ +

By adding page to the defaults key, {page} is no longer required.

+

/blog will match this route and the value of the page parameter will be +set to 1. /blog/2 will also match, giving the page parameter a value of 2.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Requirements

+ + +
blog:
+    path:      /blog/{page}
+    defaults:  { _controller: AcmeBlogBundle:Blog:index, page: 1 }
+    requirements:
+        page:  \d+
+
+ +

The \d+ requirement is a regular expression that says that the value of +the {page} parameter must be a digit (i.e. a number).

+

HTTP Method Requirements

+
# src/AppBundle/Resources/config/routing.yml
+app.hello_hello:
+    path:  /hello/{name}
+    defaults: { _controller: AppBundle:Hello:hello }
+    methods:  [ GET ]
+    # methods:  [ GET, POST ]
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Including External Routing Resources

+ + +

All routes are loaded via a single configuration file, most of the time it will +be app/config/routing.yml.

+

In order to respect the "bundle" principle, the routing configuration should be +located in the bundle itself, and you should just require it:

+
# app/config/routing.yml
+appa:
+    resource: '@AppBundle/Resources/config/routing.yml'
+
+ +

Prefixing Imported Routes

+
# app/config/routing.yml
+app:
+    resource: '@AppBundle/Resources/config/routing.yml'
+    prefix:   /demo
+
+ +

The string /demo now be prepended to the path of each route loaded from +the new routing resource.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Generating URLs

+ + +

The Router is able to generate both relative and absolute URLs.

+
$router = $this->get('router');
+
+ +

Relative URLs

+
$router->generate('app.hello_hello', [ 'name' => 'will' ]);
+// /hello/will
+
+ +

Absolute URLs

+
$router->generate('app.hello_hello', [ 'name' => 'will' ], true);
+// http://example.com/hello/will
+
+ +

Query String

+
$router->generate('app.hello_hello', [
+    'name' => 'will', 'some' => 'thing'
+]);
+// /hello/will?some=thing
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Templating

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Why Twig?

+ + +

Fast, Secure, Flexible.

+

Before

+
<ul id="navigation">
+    <?php foreach ($navigation as $item): ?>
+        <li>
+            <a href="<?php echo $item->getHref() ?>">
+                <?php echo $item->getCaption() ?>
+            </a>
+        </li>
+    <?php endforeach; ?>
+</ul>
+
+ +

After

+
<ul id="navigation">
+    {% for item in navigation %}
+        <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
+    {% endfor %}
+</ul>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Familiar With Twig

+ + +

Delimiters

+
    +
  • {{ ... }}: prints a variable or the result of an expression;
  • +
  • {% ... %}: controls the logic of the template; it is used to execute for + loops and if statements, for example;
  • +
  • {# ... #}: comments.
  • +
+
+

http://twig.sensiolabs.org/

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Accessing Variables

+ + +
{# array('name' => 'Fabien') #}
+{{ name }}
+
+{# array('user' => array('name' => 'Fabien')) #}
+{{ user.name }}
+
+{# force array lookup #}
+{{ user['name'] }}
+
+{# array('user' => new User('Fabien')) #}
+{{ user.name }}
+{{ user.getName }}
+
+{# force method name lookup #}
+{{ user.name() }}
+{{ user.getName() }}
+
+{# pass arguments to a method #}
+{{ user.date('Y-m-d') }}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Control Structure

+ + +

Conditions

+
{% if user.isSuperAdmin() %}
+    ...
+{% elseif user.isMember() %}
+    ...
+{% else %}
+    ...
+{% endif %}
+
+ +

Loops

+
<ul>
+    {% for user in users if user.active %}
+        <li>{{ user.username }}</li>
+    {% else %}
+        <li>No users found</li>
+    {% endfor %}
+</ul>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Filters

+ + +

Filters are used to modify Twig variables.

+

You can use inline filters by using the | symbol:

+
{{ 'hello'|upper }}
+
+ +

But you can also use the block syntax:

+
{% filter upper %}
+    hello
+{% endfilter %}
+
+ +

Filters can be parametrized:

+
{{ post.createdAt|date('Y-m-d') }}
+
+ +
+

http://twig.sensiolabs.org/doc/filters/index.html

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Including Other Templates

+ + +

The include tag is useful to include a template and return the rendered +content of that template into the current one:

+
{% include 'sidebar.html' %}
+
+ +

Example

+

Given the following template:

+
{% for user in users %}
+    {% include "render_user.html" %}
+{% endfor %}
+
+ +

with render_user.html:

+
<p>{{ user.username }}</p>
+
+ +

+ +
<p>William D.</p>
+<p>Julien M.</p>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Template Inheritance (1/2)

+ + +

Let's define a base template, base.html, which defines a simple HTML skeleton:

+
{# app/Resources/views/base.html.twig #}
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>{% block title %}Test Application{% endblock %}</title>
+    </head>
+    <body>
+        <div id="sidebar">
+            {% block sidebar %}
+            <ul>
+                <li><a href="/">Home</a></li>
+                <li><a href="/blog">Blog</a></li>
+            </ul>
+            {% endblock %}
+        </div>
+
+        <div id="content">
+            {% block body %}{% endblock %}
+        </div>
+    </body>
+</html>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Template Inheritance (2/2)

+ + +

The key to template inheritance is the {% extends %} tag.

+

A child template might look like this:

+
{# app/Resources/views/Blog/index.html.twig #}
+{% extends 'base.html.twig' %}
+
+{% block title %}My cool blog posts{% endblock %}
+
+{% block body %}
+    {% for entry in blog_entries %}
+        <h2>{{ entry.title }}</h2>
+        <p>{{ entry.body }}</p>
+    {% endfor %}
+{% endblock %}
+
+ +

If you need to get the content of a block from the parent template, you can +use the {{ parent() }} function.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Template Naming and Locations (1/2)

+ + +

By default, templates can live in two different locations:

+
    +
  • app/Resources/views/: The applications views directory can contain + application-wide base templates (i.e. your application's layouts), + templates specific to your app as well as templates that override bundle + templates;
  • +
  • path/to/bundle/Resources/views/: Each (public) bundle houses its templates in its + Resources/views directory (and subdirectories).
  • +
+

Symfony uses a bundle:controller:template string syntax for templates.

+

You can skip the controller string: bundle::template. The template +file would live in Resources/views/.

+

You can also skip the bundle string. It refers to an application-wide base +template or layout. This means that the template is not located in any bundle, +but instead in the root app/Resources/views/ directory.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Template Naming and Locations (2/2)

+ + +

Example

+
AcmeBlogBundle:Blog:index.html.twig
+
+ +
    +
  • AcmeBlogBundle: (bundle) the template lives inside the AcmeBlogBundle (e.g. +src/Acme/BlogBundle);
  • +
  • Blog: (controller) indicates that the template lives inside the Blog +subdirectory of Resources/views;
  • +
  • index.html.twig: (template) the actual name of the file is index.html.twig.
  • +
+

Assuming that the AcmeBlogBundle lives at src/Acme/BlogBundle, the final +path to the layout would be:

+
src/Acme/BlogBundle/Resources/views/Blog/index.html.twig
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Overriding Bundle Templates

+ + +

Once you use a third-party bundle, you'll likely need to override and customize +one or more of its templates.

+

When the FooBarBundle:Bar:index.html.twig is rendered, Symfony actually +looks in two different locations for the template:

+
    +
  • app/Resources/FooBarBundle/views/Bar/index.html.twig;
  • +
  • src/Foo/BarBundle/Resources/views/Bar/index.html.twig.
  • +
+

In order to override the bundle template, copy the index.html.twig template +from the bundle to: app/Resources/FooBarBundle/views/Bar/index.html.twig.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Overriding Core Templates

+ + +

The core TwigBundle contains a number of different templates that can be +overridden by copying each from the Resources/views/ directory of the +TwigBundle to the app/Resources/TwigBundle/views/ directory.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Twig Into Symfony

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Rendering A Template

+ + +

Using The Base Controller

+
public function listAction()
+{
+    // ...
+
+    return $this->render('blog/index.html.twig', array(
+        'posts' => $posts,
+    ));
+}
+
+ +

Using the Templating Service

+
$engine  = $this->container->get('templating');
+$content = $engine->render('blog/index.html.twig', array(
+    'posts' => $posts,
+));
+
+return new Response($content);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Linking to Pages

+ + +

Assuming the following routing definition:

+
homepage:
+    path:     /
+    defaults: { _controller: AppBundle:Hello:index }
+
+acme_blog.post_show:
+    path:     /posts/{slug}
+    defaults: { _controller: AcmeBlogBundle:Post:show }
+
+ +

You can create a relative URL using path():

+
<a href="{{ path('homepage') }}">Home</a>
+
+ +

You can create an absolute URL using url():

+
<a href="{{ url('homepage') }}">Home</a>
+
+ +

The second argument is used to pass parameters:

+
<a href="{{ path('acme_blog.post_show', {'slug': 'my-super-slug'}) }}">
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Linking to Assets

+ + +
<script src={{ asset('js/script.js') }}></script>
+
+<link href="{{ asset('css/style.css') }}" rel="stylesheet">
+
+<img src="{{ asset('images/logo.png') }}" alt="Symfony!" />
+
+ +

Cache Busting

+

Cache busting is the process of forcing browsers or proxy servers to +update their cache, for instance, JavaScript and CSS files or images.

+
# app/config/config.yml
+framework:
+    # ...
+    templating: { engines: ['twig'], assets_version: v2 }
+
+ +

The asset_version parameter is used to bust the cache on assets by globally +adding a query parameter to all rendered asset paths:

+
/images/logo.png?v2
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Linking To Pages In JavaScript

+ + +

The FOSJsRoutingBundle +allows you to expose your routing in your JavaScript code. That means you'll +be able to generate URL with given parameters like you can do with the Router +component provided by Symfony.

+
# app/config/routing.yml
+my_route_to_expose:
+    path:  /foo/{id}/bar
+    defaults: { _controller: FooBarBundle:Foo:bar }
+    options:
+        expose: true
+
+ +

According to the routing definition above, you can write the following +JavaScript code to generate URLs:

+
Routing.generate('my_route_to_expose', { id: 10 });
+// /foo/10/bar
+
+Routing.generate('my_route_to_expose', { id: 10 }, true);
+// http://example.org/foo/10/bar
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Global Template Variables

+ + +
    +
  • app.security: the security context;
  • +
  • app.user: the current user object;
  • +
  • app.request: the request object;
  • +
  • app.session: the session object;
  • +
  • app.environment: the current environment (dev, prod, etc);
  • +
  • app.debug: true if in debug mode. false otherwise.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Service Container

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is A Service?

+ + +

A Service is a generic term for any PHP object that performs a specific task.

+

A service is usually used globally, such as a database connection object or an +object that delivers email messages.

+

In Symfony, services are often configured and retrieved from the service +container.

+

An application that has many decoupled services is said to follow a +Service-Oriented Architecture (SOA).

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is A Service Container?

+ + +

A Service Container, also known as a Dependency Injection Container +(DIC), is a special object that manages the instantiation of services inside +an application.

+

The service container takes care of lazily instantiating and injecting +dependent services.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Creating A Service

+ + +
class Foo
+{
+    private $bar;
+    private $debug;
+
+    public function __construct(Bar $bar = null, $debug = false)
+    {
+        $this->bar   = $bar;
+        $this->debug = $debug;
+    }
+}
+
+ +

The service definition for the class described above is:

+
<services>
+    <service id="foo" class="My\Bundle\Foo" />
+</services>
+
+ +

This service is now available in the container, and you can access it by +asking the service from the container:

+
$foo = $this->container->get('foo');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Service Parameters

+ + +

The service definition described before is not flexible enough. For instance, +$debug argument is never configured.

+

Parameters make defining services more organized and flexible:

+
<parameters>
+    <parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
+</parameters>
+
+<services>
+    <service id="foo" class="%my_bundle.foo.class%">
+        <argument></argument> <!-- null -->
+        <argument>%kernel.debug%</argument>
+    </service>
+</services>
+
+ +

In the definition above, kernel.debug is a parameter defined by the framework +itself. The foo service is now parametrized.

+

Also, it becomes easy to change the implementation of this service by simply +overriding the my_bundle.foo.class parameter.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Injecting Services

+ + +

As you may noticed, the Foo class takes an instance of Bar as first +argument. You can inject this instance in your foo service by +referencing the bar service:

+
<parameters>
+    <parameter key="my_bundle.foo.class">My\Bundle\Foo</parameter>
+    <parameter key="my_bundle.bar.class">My\Bundle\Bar</parameter>
+</parameters>
+
+<services>
+    <service id="bar" class="%my_bundle.bar.class%" />
+
+    <service id="foo" class="%my_bundle.foo.class%">
+        <argument type="service" id="bar" />
+        <argument>%kernel.debug%</argument>
+    </service>
+</services>
+
+ +

Optional Dependencies: Setter Injection

+
<call method="setBar">
+    <argument type="service" id="bar" />
+</call>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Importing Configuration Resources

+ + +

The imports Way

+
# app/config/config.yml
+imports:
+    - { resource: "@AcmeDemoBundle/Resources/config/services.xml" }
+
+ +

Container Extensions

+

A service container extension is a PHP class to accomplish two things:

+
    +
  • import all service container resources needed to configure the services for + the bundle;
  • +
  • provide semantic, straightforward configuration so that the bundle can + be configured without interacting with the flat parameters of the bundle's + service container configuration.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Creating an Extension Class

+ + +

An extension class should live in the DependencyInjection directory of your +bundle and its name should be constructed by replacing the Bundle suffix of +the Bundle class name with Extension.

+
// Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php
+namespace Acme\DemoBundle\DependencyInjection;
+
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+
+class AcmeDemoExtension extends Extension
+{
+    public function load(array $configs, ContainerBuilder $container)
+    {
+        $loader = new XmlFileLoader($container, new FileLocator(
+            __DIR__ . '/../Resources/config'
+        ));
+        $loader->load('services.xml');
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dealing With Configuration (1/2)

+ + +

The presence of the previous class means that you can now define an +acme_demo configuration namespace in any configuration file:

+
# app/config/config.yml
+acme_demo: ~
+
+ +

Take the following configuration:

+
acme_demo:
+    foo: fooValue
+    bar: barValue
+
+ +

The array passed to your load() method will look like this:

+
array(
+    array(
+        'foo' => 'fooValue',
+        'bar' => 'barValue',
+    )
+)
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dealing With Configuration (2/2)

+ + +

The $configs argument is an array of arrays, not just a single flat array +of the configuration values.

+

It's your job to decide how these configurations should be merged together.

+

You might, for example, have later values override previous values or +somehow merge them together:

+
public function load(array $configs, ContainerBuilder $container)
+{
+    $config = array();
+    foreach ($configs as $subConfig) {
+        $config = array_merge($config, $subConfig);
+    }
+
+    // ... now use the flat $config array
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Configuration Class (1/2)

+ + +

Definition

+
// src/Acme/DemoBundle/DependencyInjection/Configuration.php
+namespace Acme\DemoBundle\DependencyInjection;
+
+use Symfony\Component\Config\Definition\Builder\TreeBuilder;
+use Symfony\Component\Config\Definition\ConfigurationInterface;
+
+class Configuration implements ConfigurationInterface
+{
+    public function getConfigTreeBuilder()
+    {
+        $treeBuilder = new TreeBuilder();
+        $rootNode    = $treeBuilder->root('acme_demo');
+
+        $rootNode
+            ->children()
+                ->scalarNode('my_type')->defaultValue('bar')->end()
+            ->end();
+
+        return $treeBuilder;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Configuration Class (2/2)

+ + +

Usage

+
public function load(array $configs, ContainerBuilder $container)
+{
+    $configuration = new Configuration();
+
+    $config = $this->processConfiguration($configuration, $configs);
+
+    // ...
+}
+
+ +

The processConfiguration() method uses the configuration tree you've defined +in the Configuration class to validate, normalize and merge all of +the configuration arrays together.

+
+

Read more on How to expose a Semantic Configuration for a Bundle: +http://symfony.com/doc/master/cookbook/bundles/extension.html.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

More On The Service Container

+ + +

Tags

+

In the service container, a tag implies that the service is meant to be used +for a specific purpose.

+
<service id="my_bundle.twig.foo" class="My\Bundle\Twig\FooExtension">
+    <tag name="twig.extension" />
+</service>
+
+ +

Twig finds all services tagged with twig.extension and automatically registers +them as extensions.

+

Debugging Services

+
$ php bin/console debug:container
+
+$ php bin/console debug:container foo
+
+ +
+

http://symfony.com/doc/master/book/service_container.html

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Symfony Commands

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Built-in Commands (1/2)

+ + +
$ php bin/console
+
+ +

Global Options

+

You can get help information:

+
$ php bin/console help cmd
+$ php bin/console cmd --help
+$ php bin/console cmd -h
+
+ +

You can get more verbose messages:

+
$ php bin/console cmd --verbose
+$ php bin/console cmd -v [-vv] [-vvv]
+
+ +

You can suppress output:

+
$ php bin/console cmd --quiet
+$ php bin/console cmd -q
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Built-in Commands (2/2)

+ + +
assets
+  assets:install          Installs bundles web assets under a public
+                          web directory
+cache
+  cache:clear             Clears the cache
+  cache:warmup            Warms up an empty cache
+config
+  config:dump-reference   Dumps default configuration for an extension
+container
+  container:debug         Displays current services for an application
+debug
+  debug:container         Displays current services for an application
+  debug:router            Displays current routes for an application
+router
+  router:match            Helps debug routes by simulating a path info
+                          match
+server
+  server:run              Runs PHP built-in web server
+translation
+  translation:update      Updates the translation file
+lint
+  lint:twig               Lints a template and outputs encountered
+                          errors
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Creating Commands

+ + +

Create a Command directory inside your bundle and create a php file suffixed +with Command.php for each command that you want to provide:

+
// src/AppBundle/Command/GreetCommand.php
+namespace AppBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class GreetCommand extends ContainerAwareCommand
+{
+    protected function configure()
+    {
+        $this->setName('demo:greet');
+    }
+
+    protected function execute(
+        InputInterface $input,
+        OutputInterface $output
+    ) {
+        // code ...
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Command Arguments

+ + +

Arguments are the strings, separated by spaces, that come after the command +name itself. They are ordered, and can be optional or required.

+
protected function configure()
+{
+    $this
+        // ...
+        ->addArgument(
+            'name',
+            InputArgument::REQUIRED,
+            'Who do you want to greet?'
+        )
+        ->addArgument(
+            'last_name',
+            InputArgument::OPTIONAL,
+            'Your last name?'
+        );
+}
+
+ +

Usage

+
$input->getArgument('last_name');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Command Options (1/2)

+ + +

Unlike arguments, options are not ordered, always optional, and can be +setup to accept a value or simply as a boolean flag without a value.

+
protected function configure()
+{
+    $this
+        // ...
+        ->addOption(
+            'yell',
+            null,
+            InputOption::VALUE_NONE,
+            'If set, the task will yell in uppercase letters'
+        );
+}
+
+ +

Usage

+
// php bin/console demo:greet --yell
+
+if ($input->getOption('yell')) {
+    // ...
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Command Options (2/2)

+ + +
protected function configure()
+{
+    $this
+        // ...
+        ->addOption(
+            'iterations',
+            null,
+            InputOption::VALUE_REQUIRED,
+            'How many times should the message be printed?',
+            1
+        );
+}
+
+ +

Usage

+
// php bin/console demo:greet --iterations=10
+
+for ($i = 0; $i < $input->getOption('iterations'); $i++) {
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

More On Commands

+ + +

Getting Services from the Service Container

+
protected function execute(
+    InputInterface $input,
+    OutputInterface $output
+) {
+    $translator = $this->getContainer()->get('translator');
+    // ...
+}
+
+ +

Calling an existing Command

+
$command   = $this->getApplication()->find('demo:greet');
+$arguments = array(
+    'command' => 'demo:greet',
+    'name'    => 'Fabien',
+    'yell'    => true,
+);
+
+$returnCode = $command->run(new ArrayInput($arguments), $output);
+
+ +
+

http://symfony.com/doc/master/cookbook/console/index.html

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Forms

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Building Your First Form

+ + +
public function newAction(Request $request)
+{
+    $form = $this->createFormBuilder()
+        ->add('name')
+        ->add('bio', 'textarea')
+        ->add('birthday', 'date')
+        ->getForm();
+
+    return $this->render('default/new.html.twig', [
+        'form' => $form->createView(),
+    ]);
+}
+
+ +

In order to display the Form, you need to pass a special view object to the +View layer. It's achieved through the createView() method.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Rendering The Form

+ + +

+

+
{# src/AppBundle/Resources/views/Default/new.html.twig #}
+<form action="{{ path('acme_demo.default_new') }}" method="post">
+    {{ form_widget(form) }}
+
+    <input type="submit" />
+</form>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Handling Forms: The Right Way

+ + +
    +
  1. +

    When initially loading the page in a browser, the request method is GET +and the form is simply created and rendered;

    +
  2. +
  3. +

    When the user submits the form (i.e. the method is POST) with invalid +data, the form is bound and then rendered, this time displaying all +validation errors;

    +
  4. +
  5. +

    When the user submits the form with valid data, the form is bound and you have +the opportunity to perform some actions before redirecting the user to some +other page (e.g. a "success" page).

    +
  6. +
+

Redirecting a user after a successful form submission prevents the user from +being able to hit "refresh" and re-post the data.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Handling Form Submissions

+ + +
public function newAction(Request $request)
+{
+    $form = $this->createFormBuilder()
+        ->add('name')
+        ->add('bio', 'textarea')
+        ->add('birthday', 'date')
+        ->getForm();
+
+    if ($form->handleRequest($request)->isValid()) {
+        $data = $form->getData();
+        // do something ...
+
+        return $this->redirect($this->generateUrl('success'));
+    }
+
+    // ...
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Built-in Form Types

+ + +

Everything is a Type!

+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Creating A Custom Type (Form Class)

+ + +
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+
+class PersonType extends AbstractType
+{
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+            ->add('name')
+            ->add('bio', 'textarea')
+            ->add('birthday', 'date');
+    }
+
+    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    {
+        $resolver->setDefaults([
+            'data_class' => My\Person::class,
+        ]);
+    }
+
+    public function getName()
+    {
+        return 'person';
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dealing With Objects

+ + +
public function newAction(Request $request)
+{
+    $person = new Person();
+    $form   = $this->createForm(PersonType::class, $person);
+
+    if ($form->handleRequest($request)->isValid()) {
+        $person->save(); // insert a new `person`
+
+        return $this->redirect($this->generateUrl('success'));
+    }
+
+    // ...
+}
+
+ +

Placing the form logic into its own class means that the form can be easily +reused elsewhere in your project.

+

This is the best way to create forms, but the choice is up to you!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The processForm() Method (1/2)

+ + +

Saving or updating an object is pretty much the same thing. In order to avoid +code duplication, you can use a processForm() method that can be used in both +the newAction() and the updateAction():

+
/**
+ * Create a new Person
+ */
+public function newAction(Request $request)
+{
+    return $this->processForm($request, new Person());
+}
+
+/**
+ * Update an existing Person
+ */
+public function updateAction(Request $request, $id)
+{
+    $person = ...; // get a `Person` by its $id
+
+    return $this->processForm($request, $person);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The processForm() Method (2/2)

+ + +
/**
+ * @param Request $request
+ * @param Person  $person
+ *
+ * @return Response
+ */
+private function processForm(Request $request, Person $person)
+{
+    $form = $this->createForm(PersonType::class, $person);
+
+    if ($form->handleRequest($request)->isValid()) {
+        $person->save();
+
+        return $this->redirect($this->generateUrl('success'));
+    }
+
+    return $this->render('default/new.html.twig', [
+        'form' => $form->createView(),
+    ]);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Cross-Site Request Forgery Protection

+ + +

CSRF is a method by which a malicious user attempts to make your legitimate +users unknowingly submit data that they don't intend to submit. Fortunately, +CSRF attacks can be prevented by using a CSRF token inside your forms.

+

CSRF protection works by adding a hidden field to your form, called _token +by default that contains a value that only you and your user knows.

+

This ensures that the user is submitting the given data. Symfony automatically +validates the presence and accuracy of this token.

+

The _token field is a hidden field and will be automatically rendered if you +include the form_rest() function in your template, which ensures that all +un-rendered fields are output.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Rendering a Form in a Template (1/2)

+ + +
<form action="" method="post" {{ form_enctype(form) }}>
+    {{ form_errors(form) }}
+
+    {{ form_row(form.name) }}
+
+    {{ form_row(form.bio) }}
+
+    {{ form_row(form.birthday) }}
+
+    {{ form_rest(form) }}
+
+    <input type="submit" />
+</form>
+
+ +
+

Read more: +http://symfony.com/doc/master/book/forms.html#rendering-a-form-in-a-template.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Rendering a Form in a Template (2/2)

+ + +
    +
  • +

    form_enctype(form): if at least one field is a file upload field, this renders + the obligatory enctype="multipart/form-data";

    +
  • +
  • +

    form_errors(form): renders any errors global to the whole form (field-specific + errors are displayed next to each field);

    +
  • +
  • +

    form_row(form.name): renders the label, any errors, and the HTML form widget + for the given field inside, by default, a div element;

    +
  • +
  • +

    form_rest(form): renders any fields that have not yet been rendered. It's + usually a good idea to place a call to this helper at the bottom of each form. + This helper is also useful for taking advantage of the automatic CSRF Protection.

    +
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Validation

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

About Form Validation

+ + +

In the previous section, you learned how a form can be submitted with valid or +invalid data. In Symfony, validation is applied to the underlying object.

+

In other words, the question isn't whether the "form" is valid, but whether the +object is valid after the form has applied the submitted data to it.

+

Calling $form->isValid() is a shortcut that asks the object whether it has +valid data using a Validation layer.

+

Validation is done by adding a set of rules (called constraints) to a class.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Validator Component

+ + +

This component is based on the JSR303 Bean Validation +specification.

+

Example

+

Given the following class:

+
namespace AppBundle\Entity;
+
+class Author
+{
+    public $name;
+}
+
+ +

You can configure a set of constraints on it:

+
# src/AppBundle/Resources/config/validation.yml
+AppBundle\Entity\Author:
+    properties:
+        name:
+            - NotBlank: ~
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using the validator Service

+ + +
$author = new Author();
+// ... do something to the $author object
+
+$validator = $this->get('validator');
+$errors    = $validator->validate($author);
+
+if (count($errors) > 0) {
+    // Ooops, errors!
+} else {
+    // Everything is ok :-)
+}
+
+ +

If the $name property is empty, you will see the following error message:

+
AppBundle\Author.name:
+    This value should not be blank
+
+ +

Most of the time, you won't interact directly with the validator service or need +to worry about printing out the errors. You will rather use validation +indirectly when handling submitted form data.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+ +
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Constraint Targets (1/2)

+ + +

Constraints can be applied to a class property or a public getter method +(e.g. getFullName()). The first is the most common and easy to use, but the +second allows you to specify more complex validation rules.

+

Properties

+

Validating class properties is the most basic validation technique. Symfony +allows you to validate private, protected or public properties.

+
# src/AppBundle/Resources/config/validation.yml
+AppBundle\Entity\Author:
+    properties:
+        firstName:
+            - NotBlank: ~
+
+ +

Classes

+

Some constraints apply to the entire class being validated. For example, the +Callback constraint is a generic constraint that's applied to the class +itself.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Constraint Targets (2/2)

+ + +

Getters

+

Constraints can also be applied to the return value of a method. Symfony +allows you to add a constraint to any public method whose name starts with +get or is.

+
# src/AppBundle/Resources/config/validation.yml
+AppBundle\Entity\Author:
+    getters:
+        passwordLegal:
+            - "False":
+                message: "The password cannot match your first name"
+
+ +

With the following code in the Author class:

+
public function isPasswordLegal()
+{
+    return ($this->firstName !== $this->password);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Validation Groups (1/2)

+ + +

In some cases, you will need to validate an object against only some of the +constraints on that class.

+

You can organize each constraint into one or more validation groups, and +then apply validation against just one group of constraints.

+

Example

+
# src/AppBundle/Resources/config/validation.yml
+AppBundle\Entity\User:
+    properties:
+        email:
+            - Email:    { groups: [ registration ] }
+        password:
+            - NotBlank: { groups: [ registration ] }
+            - Length:   { groups: [ registration ], min: 7 }
+        city:
+            - Length:
+                min: 2
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Validation Groups (2/2)

+ + +

With the configuration seen before, there are two validation groups:

+
    +
  • Default: contains the constraints not assigned to any other group;
  • +
  • registration: contains the constraints on the email and password fields only.
  • +
+

To tell the validator to use a specific group, pass one or more group names as +the second argument to the validate() method:

+
$errors = $validator->validate($author, [ 'registration' ]);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using Validation Groups In Forms

+ + +

If your object takes advantage of validation groups, you'll need to specify +which validation group(s) your form should use:

+
$form = $this
+    ->createFormBuilder($users, [
+        'validation_groups' => [ 'registration' ],
+    ])
+    ->add(...);
+
+ +

If you're creating form classes, then you'll need to add the following to +the setDefaultOptions() method:

+
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+
+public function setDefaultOptions(OptionsResolverInterface $resolver)
+{
+    $resolver->setDefaults([
+        'validation_groups' => [ 'registration' ],
+    ]);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Validating Values and Arrays

+ + +
use Symfony\Component\Validator\Constraints\Email;
+
+$emailConstraint = new Email();
+$emailConstraint->message = 'Invalid email address';
+
+$errorList = $this->get('validator')->validateValue(
+    $email, $emailConstraint
+);
+
+if (0 !== count($errorList)) {
+    // this is *not* a valid email address
+    $errorMessage = $errorList[0]->getMessage();
+}
+
+ +

By calling validateValue() on the validator, you can pass in a raw value and +the constraint object that you want to validate that value against.

+

The validateValue() method returns a ConstraintViolationList object, which +acts just like an array of errors.

+

Each error in the collection is a ConstraintViolation object, which holds the +error message on its getMessage() method.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Translations

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Definitions

+ + +

Internationalization

+

The term internationalization (often abbreviated i18n) refers to the +process of abstracting strings and other locale-specific pieces out of your +application and into a layer where they can be translated and converted +based on the user's locale (i.e. language and country).

+

Localization

+

The act of creating translation files is an important part of localization +(often abbreviated l10n). It is the process of adapting a product or +service to a particular language, culture, and desired local +look-and-feel.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using the translator Service

+ + +
# messages.fr.yml
+Symfony is great: J'aime Symfony
+'Hello %name%': Bonjour %name%
+
+ +

When the following code is executed, Symfony will attempt to translate the +message Symfony is great based on the locale of the user:

+
echo $this->get('translator')->trans('Symfony is great');
+
+ +

Now, if the language of the user's locale is French (e.g. fr_FR or fr_BE), +the message will be translated into J'aime Symfony.

+

Message Placeholders

+
echo $this->get('translator')->trans('Hello %name%', [
+    '%name%' => 'Will'
+]);
+
+// French:  Bonjour Will
+// Default: Hello Will
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Translation Process

+ + +

To translate the message, Symfony uses a simple process:

+
    +
  1. +

    The locale of the current user, which is stored on the request (or stored as +_locale on the session), is determined;

    +
  2. +
  3. +

    A catalog of translated messages is loaded from translation resources defined +for the locale (e.g. fr_FR). Messages from the fallback locale are also loaded +and added to the catalog if they don't already exist. The end result is a large +"dictionary" of translations;

    +
  4. +
  5. +

    If the message is located in the catalog, the translation is returned. If not, +the translator returns the original message.

    +
  6. +
+

When using the trans() method, Symfony looks for the exact string inside the +appropriate message catalog and returns it (if it exists).

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Locations and Naming Conventions

+ + +

Symfony looks for message files (i.e. translations) in the following locations:

+
    +
  • the <kernel root directory>/Resources/translations directory;
  • +
  • the <kernel root directory>/Resources/<bundle name>/translations directory;
  • +
  • the Resources/translations/ directory of the bundle.
  • +
+

The filename of the translations is also important as Symfony uses a convention +to determine details about the translations. Each message file must be named +according to the following path: domain.locale.loader:

+
    +
  • domain: an optional way to organize messages into groups;
  • +
  • locale: the locale that the translations are for (en_GB, en, etc);
  • +
  • loader: how Symfony should load and parse the file (xliff, php or yml).
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Pluralization

+ + +

When a translation has different forms due to pluralization, you can provide all +the forms as a string separated by a pipe (|):

+
'There is one apple|There are %count% apples'
+
+ +

To translate pluralized messages, use the transChoice() method:

+
$t = $this->get('translator')->transChoice(
+    'There is one apple|There are %count% apples',
+    10,
+    array('%count%' => 10)
+);
+
+ +

The second argument (10 in this example), is the number of objects being +described and is used to determine which translation to use and also to +populate the %count% placeholder.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Explicit Interval Pluralization

+ + +

Sometimes, you'll need more control or want a different translation for specific +cases (for 0, or when the count is negative, for example). For such cases, you +can use explicit math intervals:

+
'{0} There are no apples|{1} There is one apple|]1,19] There are
+%count% apples|[20,Inf[ There are many apples'
+
+ +

The intervals follow the ISO 31-11 +notation.

+

An Interval can represent a finite set of numbers:

+
{1,2,3,4}
+
+ +

Or numbers between two other numbers:

+
[1, +Inf[
+]-1,2[
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

BazingaJsTranslationBundle

+ + +
# app/Resources/translations/Hello.fr.yml
+ba:
+    bar:      Bonjour.
+place.holder: Bonjour %username%!
+plural:       Il y a %count% pomme|Il y a %count% pommes
+
+ +

+ +
<script src="{{ url('bazinga_jstranslation_js', { 'domain': 'Hello' }) }}">
+</script>
+
+ +

A Translator object is now available in your JavaScript:

+
Translator.trans('ba.bar', {}, 'Hello', 'fr');
+// "Bonjour."
+
+Translator.trans('place.holder', { "username" : "Will" }, 'Hello');
+// "Bonjour Will!"
+
+Translator.transChoice('plural', 1, { "count": 1 }, 'Hello');
+// "Il y a 1 pomme"
+
+Translator.transChoice('plural', 10, { "count": 10 }, 'Hello');
+// "Il y a 10 pommes"
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Cache

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Cache

+ + +

The nature of rich web applications means that they're dynamic. No matter how +efficient your application, each request will always contain more overhead +than serving a static file.

+

But as your site grows, that overhead can become a problem. The processing +that's normally performed on every request should be done only once.

+

This is exactly what caching aims to accomplish!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Terminology (1/2)

+ + +

Gateway Cache

+

A gateway cache, or reverse proxy, is an independent layer that sits in +front of your application.

+

The reverse proxy caches responses as they are returned from your application +and answers requests with cached responses before they hit your application.

+

Symfony provides its own reverse proxy, but any reverse proxy can be used.

+

HTTP Cache

+

HTTP cache headers are used to communicate with the gateway cache and any +other caches between your application and the client.

+

Symfony provides sensible defaults and a powerful interface for interacting +with the cache headers.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Terminology (2/2)

+ + +

HTTP Expiration

+

HTTP expiration and validation are the two models used for determining +whether cached content is fresh (can be reused from the cache) or stale (should +be regenerated by the application).

+

Edge Side Includes

+

Edge Side Includes (ESI) allow HTTP cache to be used to cache page +fragments (even nested fragments) independently. With ESI, you can even cache +an entire page for 60 minutes, but an embedded sidebar for only 5 minutes.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Caching with a Gateway Cache

+ + +

When caching with HTTP, the cache is separated from your application entirely +and sits between your application and the client making the request.

+

The job of the cache is to accept requests from the client and pass them back +to your application.

+

The cache will also receive responses back from your application and forward +them on to the client. The cache is the middle-man of the request-response +communication between the client and your application.

+

Along the way, the cache will store each response that is deemed cacheable. +If the same resource is requested again, the cache sends the cached response to +the client, ignoring your application entirely.

+

This type of cache is known as a HTTP gateway cache and many exist such as +Varnish, Squid in reverse proxy mode, and the Symfony reverse +proxy.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Types of Caches

+ + +

The HTTP cache headers sent by your application are consumed and interpreted by +up to three different types of caches:

+
    +
  • Browser Caches: every browser comes with its own local cache that is mainly +useful for when you hit "back" or for images and other assets. The browser cache +is a private cache as cached resources aren't shared with anyone else;
  • +
  • Proxy Caches: a proxy is a shared cache as many people can be behind a +single one. It's usually installed by large corporations and ISPs to reduce +latency and network traffic;
  • +
  • Gateway Caches: like a proxy, it's also a shared cache but on the +server side. Installed by network administrators, it makes websites more +scalable, reliable and performant.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Caching

+ + +

HTTP specifies four response cache headers that are looked at here:

+
    +
  • Cache-Control
  • +
  • Expires
  • +
  • ETag
  • +
  • Last-Modified
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Public vs Private Responses

+ + +

Both gateway and proxy caches are considered shared caches as the cached +content is shared by more than one user.

+

If a user-specific response were ever mistakenly stored by a shared cache, it +might be returned later to any number of different users. Imagine if your +account information were cached and then returned to every subsequent user who +asked for their account page!

+

To handle this situation, every response may be set to be public or private:

+
    +
  • public: indicates that the response may be cached by both private and +shared caches;
  • +
  • private: indicates that all or part of the response message is intended for +a single user and must not be cached by a shared cache.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Safe Methods

+ + +

HTTP caching only works for safe HTTP methods (like GET and HEAD). Being +safe means that you never change the application's state on the server when +serving the request.

+

This has two very reasonable consequences:

+
    +
  • You should never change the state of your application when responding to a +GET or HEAD request. Even if you don't use a gateway cache, the presence +of proxy caches mean that any GET or HEAD request may or may not actually +hit your server;
  • +
  • Don't expect PUT, POST or DELETE methods to cache. These methods are +meant to be used when mutating the state of your application. Caching them +would prevent certain requests from hitting and mutating your application.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Expiration

+ + +

The expiration model is the more efficient and straightforward of the two +caching models and should be used whenever possible.

+

When a response is cached with an expiration, the cache will store the response +and return it directly without hitting the application until it expires.

+

The expiration model can be accomplished using one of two HTTP headers:

+
    +
  • Cache-Control
  • +
  • Expires
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Cache-Control Header (1/2)

+ + +

The Cache-Control header is unique in that it contains not one, but +various pieces of information about the cacheability of a response.

+

Each piece of information is separated by a comma:

+
Cache-Control: private, max-age=0, must-revalidate
+Cache-Control: max-age=3600, must-revalidate
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Cache-Control Header (2/2)

+ + +

Symfony provides an abstraction around the Cache-Control header:

+
use Symfony\Component\HttpFoundation\Response;
+
+$response = new Response();
+
+// mark the response as either public or private
+$response->setPublic();
+$response->setPrivate();
+
+// set the private or shared max age
+$response->setMaxAge(600);
+$response->setSharedMaxAge(600);
+
+// set a custom Cache-Control directive
+$response->headers->addCacheControlDirective('must-revalidate', true);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Expires Header

+ + +

The Expires header can be set with the setExpires() Response method. It takes +a DateTime instance as an argument:

+
$date = new DateTime();
+$date->modify('+600 seconds');
+
+$response->setExpires($date);
+
+ +

The resulting HTTP header will look like this:

+
Expires: Thu, 01 Mar 2013 10:00:00 GMT
+
+ +

The setExpires() method automatically converts the date to the GMT timezone as +required by the specification.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Validation

+ + +

With the expiration model, the application won't be asked to return the updated +response until the cache finally becomes stale. It is not good!

+

The validation model addresses this issue.

+

Under this model, the cache continues to store responses. The difference is +that, for each request, the cache asks the application whether or not the +cached response is still valid.

+

If the cache is still valid, your application should return a 304 status code +and no content. This tells the cache that it's ok to return the cached response.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The ETag Header

+ + +

The ETag header is a string header called entity-tag that uniquely +identifies one representation of the target resource. It's entirely +generated and set by your application.

+

ETags are similar to fingerprints and they can be quickly compared to +determine if two versions of a resource are the same or not.

+
public function indexAction()
+{
+    $response = $this->render('main/index.html.twig');
+    $response->setETag(md5($response->getContent()));
+    $response->setPublic(); // make sure the response is public/cacheable
+    $response->isNotModified($this->getRequest());
+
+    return $response;
+}
+
+ +

The isNotModified() method compares the ETag sent with the Request with +the one set on the Response. If the two match, the method automatically sets +the Response status code to 304 Not Modified.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Last-Modified Header (1/2)

+ + +

According to the HTTP specification, the Last-Modified header field indicates +the date and time at which the origin server believes the representation was +last modified.

+

In other words, the application decides whether or not the cached content has +been updated based on whether or not it's been updated since the response was +cached.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Last-Modified Header (2/2)

+ + +
public function showAction($articleSlug)
+{
+    // ...
+
+    $articleDate = new \DateTime($article->getUpdatedAt());
+    $authorDate  = new \DateTime($author->getUpdatedAt());
+
+    $date = $authorDate > $articleDate ? $authorDate : $articleDate;
+
+    $response->setLastModified($date);
+    // Set response as public. Otherwise it will be private by default
+    $response->setPublic();
+
+    if ($response->isNotModified($this->getRequest())) {
+        return $response;
+    }
+
+    // ... do more work to populate the response
+    // with the full content
+
+    return $response;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Edge Side Includes (ESI)

+ + +

Edge Side Includes or ESI is a small markup language for dynamic +web content assembly at the reverse proxy level. The reverse proxy analyses the +HTML code, parses ESI specific markup and assembles the final result before +flushing it to the client.

+

+
<esi:include src="user.php" />
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Stack PHP

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HttpKernel

+ + +


+
+

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is Stack?

+ + +

A convention for composing HttpKernelInterface middlewares:

+

+
+

http://stackphp.com/

+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Implementation

+ + +
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
+
+class MyStackMiddleware implements HttpKernelInterface
+{
+    private $app;
+
+    public function __construct(HttpKernelInterface $app)
+    {
+        $this->app = $app;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public function handle(
+        Request $request,
+        $type = HttpKernelInterface::MASTER_REQUEST,
+        $catch = true
+    ) {
+        // do something awesome
+
+        return $this->app->handle($request, $type, $catch);
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Hack

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is Hack?

+ + +


+ +

+
    +
  • 2004 - Facebook was initially built with PHP;
  • +
  • 2009 - A PHP compiler called HipHop was released;
  • +
  • 2010 - Minor changes to the PHP language were introduced by the HPHP team to + improve development time and provide basic type safety. The changes were XHP + and parameter type constraints;
  • +
  • 2012 - Facebook engineering teams started exploring the idea of annotating + return types. And the Hack language was born...
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What Is Hack?

+ + +

Hack is a programming language for HHVM that interoperates seamlessly +with PHP. It has been created by Facebook. In general, anything you can write in +PHP, you can also write in Hack.

+

Hack reconciles the fast development cycle of PHP with the discipline provided by +static typing, while adding many features commonly found in other modern +programming languages such as +generics, +collections, and +nullable.

+

It also provides built-in asynchronous +programming.

+
+

Official website: hacklang.org

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Use <?hh at the top of your file; you can also change <?php to <?hh in +existing PHP files, and your code will run just as before in HHVM.

+

Optionally name your file with the .hh extension to distinguish your Hack files +from your PHP files. Of course, you can keep the name of the file .php (or any +other extension that you use).

+

Important: Hack and HTML code do not mix.

+

Example

+
<?hh
+class MyClass {
+    public function hello(): string {
+        return 'Hello, World!';
+    }
+}
+
+function f(MyClass $m): string {
+    return $m->hello();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Type Annotations

+ + +
<?hh
+
+class AnnotatedClass {
+    public int $x;
+    private string $s;
+    protected array $arr;
+    public AnotherClass $ac;
+
+    public function bar(string $str, bool $b): float {
+        if ($b && $str === "Hi") {
+            return 3.2;
+        }
+
+        return 0.3;
+    }
+}
+
+ +
+

http://docs.hhvm.com/manual/en/hack.annotations.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Generics

+ + +
<?hh
+
+class Box<T> {
+    public T $value;
+
+    public function __construct(T $v) {
+        $this->value = $v;
+    }
+}
+
+ +
+

http://docs.hhvm.com/manual/en/hack.generics.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Collections

+ + +

Hack provides a unified collections framework including: Vector, Map, Set, Pair.

+
+

http://docs.hhvm.com/manual/en/hack.collections.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Nullable Types

+ + +

Nullable allows any type to have null assigned and checked on it:

+
<?hh
+
+function check_not_null(?int $x): int {
+    if ($x === null) {
+        return -1;
+    } else {
+        return $x;
+    }
+}
+
+ +
+

http://docs.hhvm.com/manual/en/hack.nullable.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The End.

+ + +
+
+

Notes

+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + Fork me on GitHub + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..828da72 --- /dev/null +++ b/index.html @@ -0,0 +1,12965 @@ + + + + + + + PHP + + + + + + + + + + + + + + + + + +
+
+
+ 00:00:00 +
+
+
+
+
+ + +
+
+
+ +

PHP

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Who Is Speaking?

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

William DURAND

+ + +

PhD / CTO TailorDev

+

Graduated from IUT, ISIMA, Blaise Pascal University. Worked at:

+ +

Open-Source evangelist:

+ +

+ twitter.com/couac +  |  + github.com/willdurand +  |  + williamdurand.fr +

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

edu@drnd.me

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +

Week #1

+

PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, +Client/Server, REST

+

Week #2

+

Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller

+

Week #3

+

Databases

+

Week #4

+

Sessions, Authentication, Writing Better Code, Testing, Awesome Projects, +Embracing Open Source

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP: Hypertext Preprocessor

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

History & Numbers

+ + +
    +
  • Created by Rasmus Lerdorf
  • +
  • 4th language on GitHub +(August 2015)
  • +
  • 6th language in the world +(TIOBE January 2016)
  • +
  • 1st language for web development
  • +
  • Running on +75% of all web servers
  • +
  • 20 years old in 2015!
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Linux

+
$ sudo apt-get install php5-common libapache2-mod-php5 php5-cli
+
+ +
+

http://php.net/manual/en/install.unix.debian.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Mac OS X

+
$ curl -s http://php-osx.liip.ch/install.sh | bash -s 7.0
+
+ +

+ +
$ curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6
+
+ +
+

http://php-osx.liip.ch/

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Windows

+

+
+

http://php.net/manual/en/install.windows.installer.msi.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HHVM

+ + +

HipHop Virtual Machine for PHP, created by Facebook.

+

HHVM uses a just-in-time compilation approach to achieve superior performance.

+

+
+

http://hhvm.com

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

RTFM: http://php.net

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Syntax

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Primitive Types

+ + +

4 scalar types: boolean, integer, float, string;

+

2 compound types: array, object;

+

2 special types: resource, null;

+

And 3 pseudo types: mixed, number, callback.

+

Note: most of these types have aliases. E.g. double for float.

+
+

Read more about the PHP primitive types: +http://php.net/manual/en/language.types.intro.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Comparison Operators

+ + +
// so, this is a PHP variable
+$a = 5;
+
+// compare value; return true
+var_dump($a == 5);
+
+// compare value (ignore type); return true
+var_dump($a == '5');
+
+// compare type/value (integer vs. integer); return true
+var_dump($a === 5);
+
+// compare type/value (integer vs. string); return false
+var_dump($a === '5');
+
+ +
+

Read more about comparison operators: +http://php.net/manual/en/language.operators.comparison.php.

+
+

Timing Attack Safe String Comparison

+

The hash_equals() +function has been added in PHP 5.6 to compare two strings in constant time.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Operators

+ + +
$a++; // or: ++$a;
+
+$b--; // or: --$b;
+
+$a && $b;   // AND
+$a || $b;   // OR
+
+! $a;       // `true` if $a is not `true`
+
+$a . 'foo'; // concatenation
+
+2 ** 3 = 8 // exponentiation (PHP 5.6+)
+
+ +

But also:

+
$a  = 'foo';
+$a .= 'bar';
+// $a => 'foobar'
+
+$b  = 0;
+$b += 1;    // $b = 1
+$b -= 1;    // $b = 0
+
+$c = 2;
+$c **= 3;   // $c = 8
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

New Operators in PHP 7.0

+ + +

Null Coalescing Operator: ??

+
// Fetches the value of $_GET['user'] and returns 'nobody'
+// if it does not exist.
+$username = $_GET['user'] ?? 'nobody';
+
+// This is equivalent to:
+$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
+
+ +

Spaceship Operator: <=>

+

Returns -1, 0 or 1 when $a is respectively less than, equal to, or +greater than $b:

+
echo 1 <=> 1; // 0
+echo 1 <=> 2; // -1
+echo 2 <=> 1; // 1
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Classes (1/3)

+ + +

Simple class definition

+
class Foo
+{
+}
+
+ +

Important: No class-level visibility in PHP.

+

Abstract class definition

+
abstract class AbstractFoo
+{
+    abstract public function doSomething();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Classes (2/3)

+ + +

Creating an instance:

+
$foo = new Foo();
+// $foo = new Foo;
+
+// can also be done with a variable
+$class = 'Foo';
+$foo   = new $class();
+
+ +

Getting the class name of an instance:

+
echo get_class($foo);
+=> Foo
+
+ +

Useful keyword: instanceof

+
if ($foo instanceof Foo) {
+    // do something
+}
+
+ +
+

http://php.net/manual/en/language.oop5.basic.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Classes (3/3)

+ + +

Anonymous Classes (PHP >= 7.0)

+
new class {
+    public function foo() {
+        return 'foo';
+    }
+}
+
+ +

Anonymous classes behave as traditional classes:

+
interface Logger {
+    public function log($msg);
+}
+
+$logger = new class implements Logger {
+    public function log($msg) {
+        // ...
+    }
+};
+
+$logger->log('Hello, Anonymous Class');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Visibility

+ + +

Keywords

+
    +
  • public
  • +
  • protected
  • +
  • private
  • +
+

The Rules

+

Attribute visibility MUST be set.

+

Method visibility SHOULD be set.

+

Methods without any explicit visibility keyword are defined as public.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Properties

+ + +
class Foo
+{
+    const VALUE = 123;
+    // PHP 5.6+
+    const SENTENCE        = 'The value of VALUE is ' . self::VALUE;
+    const ARRAY_OF_VALUES = ['a', 'b'];
+
+    /**
+     * @var int
+     */
+    public static $count = 0;
+
+    /**
+     * @var Iterator
+     */
+    public $iterator;
+
+    /**
+     * @var array
+     */
+    protected $values = array();
+
+    /**
+     * @var string|null
+     */
+    private $language = null;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (1/4)

+ + +
class Foo
+{
+    public function doSomething()
+    {
+    }
+}
+
+ +

Type Hinting

+

Works with classes, interfaces, arrays, callable, and Closure. You cannot +use scalar types such as int or string with PHP < 7.0:

+
public function doSomething(Foo $foo);
+
+public function doSomething(Traversable $iterator);
+
+public function doSomething(array $values);
+
+public function doSomething(callable $callback);
+
+public function doSomething(Closure $closure);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (2/4)

+ + +

PHP 7 \o/

+

Scalar Type Declarations

+

Works with int, float, string, and bool:

+
function sumOfInts(int ...$ints) {
+    return array_sum($ints);
+}
+
+ +

Return Type Declarations

+
function sumOfInts(int ...$ints) : int {
+    return array_sum($ints);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (3/4)

+ + +

The -> operator is used to call methods on objects.

+

Usage

+
$foo = new Foo();
+$foo->doSomething();
+
+// >= PHP 5.4
+(new Foo())->doSomething();
+
+// can also be done with a variable
+$method = 'doSomething';
+$foo->$method();
+
+$foo->{$method . 'Else'}();
+// will call 'doSomethingElse()'; curly braces are required.
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (4/4)

+ + +
public function doSomething()
+{
+    // method call
+    $this->doSomethingElse();
+
+    // parent method call (inheritance)
+    parent::doSomething();
+
+    // accessing a constant
+    self::VALUE;
+
+    // accessing a constant from another class
+    Bar::ANOTHER_VALUE;
+
+    // accessing an attribute
+    return $this->attribute;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Static Keyword

+ + +

Attributes/Methods can be defined as static:

+
class Foo
+{
+    public static $value;
+
+    public static function doThings()
+    {
+        // accessing a static attribute
+        // don't forget the dollar sign!
+        self::$value;
+    }
+}
+
+ +

Warning: the static keyword can also be used to define static +variables +and for late static +bindings. +This is different!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Late Static Bindings

+ + +
class A
+{
+    public static function who() { echo __CLASS__; }
+
+    public static function testSelf()
+    {
+        self::who();
+    }
+
+    public static function testStatic()
+    {
+        static::who();
+    }
+}
+
+class B extends A
+{
+    public static function who() { echo __CLASS__; }
+}
+
+ +

+ +
B::testSelf();
+// A
+
+B::testStatic();
+// B
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Static Keyword

+ + +

Usage

+
$foo = new Foo();
+
+// accessing the attribute from an instance
+$foo::$value = 123;
+
+// accessing the attribute directly from the class
+echo Foo::$value;
+=> 123
+
+ +
+

Read more: +http://php.net/manual/en/language.oop5.static.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Variadic Functions

+ + +

New operator ... as of PHP 5.6:

+
function sum(...$numbers)
+{
+    return array_sum($numbers);
+}
+
+echo sum(1, 2);
+// 3
+
+ +

Argument Unpacking

+
$numbers = [ 2, 3 ];
+echo sum(1, ...$numbers);
+// 6
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Interfaces

+ + +

Simple interface definition

+
interface Fooable
+{
+    const VALUE = 123;
+
+    // it's always public anyway
+    public function doSomething();
+}
+
+ +

Inheritance

+
// Interface may extend several other interfaces.
+// This is not possible with class though!
+interface MyTraversable extends Traversable, Countable
+{
+}
+
+ +

Usage

+
// a class may implement several interfaces, but may extend only one class!
+class Foo implements Fooable, MyTraversable {}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Namespaces (1/2)

+ + +

Namespaces prevent naming collisions with identifiers such as function, class, +and interface names:

+
namespace Vendor\Model;
+// ...
+
+ +

Or:

+
namespace MyNamespace {
+    // ...
+}
+
+ +

PSR-0

+

PSR-0 describes a set of rules related to +namespaces for autoloader interoperability:

+
\ns\package\Class_Name      => vendor/ns/package/Class/Name.php
+\ns\package_name\Class_Name => vendor/ns/package_name/Class/Name.php
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Namespaces (2/2)

+ + +

Classes, functions, and constants have to be imported with the use +statement:

+
namespace My\Namespace;
+
+// Pre PHP 7 code
+use some\namespace\ClassA;
+use some\namespace\ClassB;
+
+use function some\namespace\fn_a;
+use function some\namespace\fn_b;
+
+// PHP 7+ code
+use some\namespace\{ClassA, ClassB};
+
+use function some\namespace\{fn_a, fn_b};
+
+class MyClass
+{
+    public function __construct(ClassA $a, ClassB $b) {
+        // ...
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The class Keyword

+ + +

Since PHP 5.5.0, class name resolution is possible via ::class.

+
namespace My\Namespace;
+
+class ClassName
+{
+}
+
+ +

Assuming the class definition above, you can get the Fully Qualified +Class Name (FQCN) by doing:

+
echo ClassName::class;
+// My\namespace\ClassName
+
+ +
+

Read more about the class keyword: +http://php.net/manual/en/language.oop5.basic.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Traits

+ + +

Horizontal Inheritance FTW!

+
trait Hello                         trait World
+{                                   {
+    public function sayHello()          public function sayWorld()
+    {                                    {
+        echo 'Hello ';                       echo 'World';
+    }                                    }
+}                                   }
+
+class MyHelloWorld
+{
+    use Hello, World;
+}
+
+$obj = new MyHelloWorld();
+$obj->sayHello();
+$obj->sayWorld();
+
+ +
+

Read more about traits: +http://php.net/manual/en/language.oop5.traits.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Anonymous Functions

+ + +

An anonymous function, also known as lambda function, is a function +defined, and possibly called, without being bound to an identifier.

+
$greet = function ($name) {
+    printf("Hello %s\n", $name);
+};
+
+$greet('World');
+=> Hello World
+
+ +
+

Read more about anonymous functions: +http://php.net/manual/en/functions.anonymous.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Closures

+ + +

A closure is an anonymous function that owns a context.

+
$fibonacci = function ($n) use (&$fibonacci) {
+    if (0 === $n || 1 === $n) {
+        return $n;
+    }
+
+    return $fibonacci($n - 1) + $fibonacci($n - 2);
+};
+
+echo (int) $fibonacci(6);
+=> 8
+
+ +
+

Read more about closures: +http://php.net/manual/en/class.closure.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Magic Methods

+ + +

Starts with __.

+

Two useful methods:

+
__construct() { /* ... */ }
+
+ +

and:

+
__toString() { /* ... */ }
+
+ +

Other methods are not really useful but it's worth knowing them (__get(), __set()).

+
+

Read more about magic methods: +http://php.net/manual/en/language.oop5.magic.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Generators

+ + +

A generator function looks just like a normal function, except that instead +of returning a value, a generator yields as many values as it needs to.

+

The heart of a generator function is the yield keyword.

+
+

Read more about generators:

+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Errors in PHP 7

+ + +

No more Fatal Errors \o/

+

Many fatal and recoverable fatal errors have been converted to exceptions +inheriting from the new Error class, which itself implements the Throwable +interface, i.e. the new base interface all exceptions inherit.

+
+

https://secure.php.net/manual/en/language.errors.php7.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Command Line

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Command Line (1/2)

+ + +

PHP is an interpreted language, no need for a compiler.

+

You can try PHP using the command line:

+
$ php -r 'echo "Hello, World\n"'
+Hello, World
+
+ +
+ +

Help available by running: php -h

+
+ +

PHP also provides an interactive shell:

+
$ php -a
+Interactive Shell
+
+php > echo "Hello, World\n";
+Hello, World
+
+ +
+

The command line is really useful, read more about command line options: +http://php.net/manual/en/features.commandline.options.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Command Line (2/2)

+ + +

Your new best friend is the linter:

+
$ php -l my/script.php
+No syntax errors detected in my/script.php
+
+ +
+ +

A linter is a program that looks for problems in your code + (syntax errors for instance).

+
+ +

Embedded web server:

+
$ php -S localhost:8000
+
+ +
+

Learn more about the built-in, command line web server: +http://php.net/manual/en/features.commandline.webserver.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Writing a CLI program

+ + +
#!/usr/bin/env php
+<?php
+
+if (2 !== $argc) {
+    echo "Usage: php $argv[0] [name]\n";
+    exit(1);
+}
+
+$name = $argv[1];
+echo "Hello, $name!\n";
+
+ +

Run the script:

+
$ ./hello.php
+Usage: ./hello.php [name]
+
+$ php hello.php
+Usage: php hello.php [name]
+
+$ php hello.php World
+Hello, World!
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Client/Server

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Client/Server Basics

+ + +

A typical client/server request follows this pattern:

+

+
    +
  1. Client: Hello server, give me the resource at URI;
  2. +
  3. +

    Server: Here is the resource at URI:

    +

    Content

    +
  4. +
+

For HTTP, a typical client is a web browser, and a server is a web +server.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Unified Resource Identifier (URI)

+ + +

In A Nutshell

+
    +
  • URIs identify resources;
  • +
  • URIs are format independent;
  • +
  • URI "file extensions" != RESTful.
  • +
+

Resources

+
    +
  • /bananas/joe: URI for banana "Joe"
  • +
  • /bananas/henry: URI for banana "Henry"
  • +
+

Collections

+
    +
  • /bananas: collection of all available bananas
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Request

+ + +

Request is made of:

+
    +
  • A Unique Resource Identifier (URI);
  • +
  • An HTTP verb (method) describing the action;
  • +
  • Some headers describing requirements;
  • +
  • A request body to send data.
  • +
+

Here is an example:

+
GET /my/simple/uri?with-query-string HTTP/1.1
+Host: example.org
+Content-Type: text/plain; charset=utf-8
+Content-Length: 17
+
+This is a content
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Verbs

+ + +

An HTTP verb is an action to perform on a resource located at a given +URI:

+
    +
  • GET: retrieve a resource or a collection of resources;
  • +
  • POST: create a new resource;
  • +
  • PUT: update an existing resource or create a new resource at a + given URI;
  • +
  • DELETE: delete a given resource;
  • +
  • PATCH: partial update of a given resource.
  • +
+

Important: this list is not exhaustive.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Response

+ + +

Response is made of:

+
    +
  • Some headers to describe the content;
  • +
  • The response's status code;
  • +
  • The content of the response;
  • +
+

Here is an example:

+
HTTP/1.1 200 OK
+Content-Type: text/html; charset=utf-8
+Content-Length: 76
+
+<!DOCTYPE HTML>
+<html>
+    <head>
+    </head>
+    <body>
+        <h1>Hello world !</h1>
+    </body>
+</html>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Status Codes (1/2)

+ + +

1xx Informational

+

2xx Successful

+
    +
  • 200 OK
  • +
  • 201 Created
  • +
  • 204 No Content
  • +
+

3xx Redirections

+
    +
  • 301 Moved Permanently
  • +
  • 302 Found
  • +
  • 304 Not Modified
  • +
+
+

httpstatus.es

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Status Codes (2/2)

+ + +

4xx Client Error

+
    +
  • 400 Bad Request
  • +
  • 401 Unauthorized
  • +
  • 403 Forbidden
  • +
  • 404 Not Found
  • +
  • 405 Method Not Allowed
  • +
  • 406 Not Acceptable
  • +
  • 409 Conflict
  • +
  • 415 Unsupported Media Type
  • +
  • 451 Unavailable For Legal Reasons
  • +
+

5xx Server Error

+
    +
  • 500 Internal Server Error
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Parameters (1/2)

+ + +

There are two types of parameters, query string and request body.

+

If the request follows the URL Form Encoded format, you can access +parameters through global variables:

+
    +
  • query string: $_GET;
  • +
  • request body: $_POST;
  • +
  • All parameters are available in the $_REQUEST global variable.
  • +
+

You can always use the following, but you need to parse them by yourself:

+
    +
  • query string: $_SERVER['QUERY_STRING'];
  • +
  • request body: $HTTP_RAW_POST_DATA + (deprecated, + do not use).
  • +
+

Note: Don't use $_REQUEST, as there is a collision risk!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Parameters (2/2)

+ + +
GET /my/simple/uri?a=1&id=2 HTTP/1.1
+Host: example.org
+Content-Type: text/plain; charset=utf-8
+Content-Length: 14
+
+b=3&city=paris
+
+ +

Will result in:

+
$_GET     = [ "a" => 1, "id" => 2 ];
+
+$_POST    = [ "b" => 3, "city" => 'paris' ];
+
+$_REQUEST = [ "a" => 1, "id" => 2, "b" => 3, "city" => 'paris' ];
+
+$_SERVER['QUERY_STRING'] = "a=1&id=2";
+
+$HTTP_RAW_POST_DATA      = "b=3&city=paris";
+
+ +

Important: never trust user input, never!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

REST

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

REpresentational State Transfer

+ + +

REST is the underlying architectural principle of the web, formalized as a set +of constraints, described in Roy Fielding's dissertation.

+

An API (i.e. a web service) that adheres to the principles of REST does not +require the client to know anything about the structure of this API. +Rather, the server needs to provide whatever information the client needs to +interact with the service.

+

The key abstraction of information in REST is a resource. Any information +that can be named can be a resource, and is identified by a Unified Resource +Identifier (URI).

+
+

It heavily relies on the HTTP protocol: RFC 2616.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Richardson Maturity Model

+ + +


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 0 - The Swamp of POX

+ + +

In A Nutshell

+
    +
  • HTTP as a tunneling mechanism;
  • +
  • RPC style system (SOAP, XML-RPC).
  • +
+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 1 - Resources

+ + +

In A Nutshell

+
    +
  • Individual resources (URIs);
  • +
  • Notion of object identity.
  • +
+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 2 - HTTP Verbs

+ + +

In A Nutshell

+
    +
  • Client uses specific HTTP verbs;
  • +
  • Server uses HTTP status codes.
  • +
+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 3 - Hypermedia Controls

+ + +

In A Nutshell

+
    +
  • Service discovery via link relations
  • +
  • Hypermedia formats
  • +
+


+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 3 = Content Negotiation + HATEOAS

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Media Types

+ + +

In A Nutshell

+
    +
  • Identifies a representation format;
  • +
  • Custom types use application/vnd.[XYZ];
  • +
  • Used inside the Accept / Content-Type headers.
  • +
+ + + + + + + + + + + +
HeaderDescription
`Content-Type`HTTP message format
`Accept`HTTP response format preference
+ +

Hyper Media Types

+

Hyper Media Types are MIME media types that contain native hyper-linking +semantics that induce application flow: application/hal+json, +application/collection+json, etc.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Content Type Negotiation

+ + +

Content Type Negotiation is the principle of finding appropriate response +formats based on client requirements.

+

No standardized algorithm available, even if the Apache +mod_negotiation +algorithm is documented. This also covers encoding (Accept-Encoding) and +language (Accept-Language) negotiation.

+
Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
+    text/*;q=0.7, */*;q=0.5
+
+ + + + + + + + + + + + + +
PriorityMime Type
`q=1.0``application/json`
`q=0.9``application/xml`
`q=0.8``text/html`
`q=0.7``text/*` (ie. any text)
`q=0.5``*/*` (ie. any media type)
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HATEOAS

+ + +

HATEOAS stands for Hypermedia As The Engine Of +Application State. It means that hypertext should be used to find your +way through the API.

+

It is all about state transitions. Your application is just a big state +machine. +There should be a single endpoint for the resource, and all of the other +actions you would need to undertake should be able to be discovered by +inspecting that resource.

+
<?xml version="1.0" encoding="UTF-8"?>
+<collection page="1" limit="10" pages="1">
+    <user id="123"></user>
+    <user id="456"></user>
+
+    <link rel="self" href="/api/users?page=1&amp;limit=10" />
+    <link rel="first" href="/api/users?page=1&amp;limit=10" />
+    <link rel="last" href="/api/users?page=1&amp;limit=10" />
+</collection>
+
+ +
+

Must read: Haters gonna +HATEOAS.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Autoloading

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Why Is It Necessary?

+ + +

PHP won't magically load your classes by itself.

+

You have to manually include the class declaration:

+
class Octopus
+{
+    public function scream()
+    {
+        echo "o o O O  ° °";
+    }
+}
+
+ +

If you want to create a new Octopus, you will write the following code:

+
$paul = new Octopus();
+$paul->scream();
+
+ +

As the class declaration isn't included, PHP raises a Fatal Error:

+
Fatal error: Class 'Octopus' not found in /path/to/file.php
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The require() Way

+ + +

Include the class definition before instantiating it:

+
require __DIR__ . '../Model/Octopus.php';
+
+$paul = new Octopus();
+$paul->scream();
+
+ +

It works!

+

But, what happens when the class is included again, somewhere else?

+
// somewhere further in your application
+require __DIR__ . '../Model/Octopus.php';
+
+class Squid extends Octopus
+{
+}
+
+ +

PHP raises a Fatal Error:

+
Fatal error: Cannot redeclare class Octopus in /path/to/file.php
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The require_once() Way

+ + +

The require_once() function is identical to require() except that PHP will +check whether the file has already been included:

+
require_once __DIR__ . '../Model/Octopus.php';
+
+$paul = new Octopus();
+$paul->scream();
+
+ +

And somewhere else:

+
// somewhere further in your application
+require_once __DIR__ . '../Model/Octopus.php';
+
+class Squid extends Octopus
+{
+}
+
+ +

It just works!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Working With Multiple Files

+ + +

Multiple require_once() can turn into a nightmare when you deal with more than +a few files:

+
<?php
+
+/**
+ * lib/Model/Location.php
+ */
+require_once __DIR__ . '/../../common.php';
+require_once DOCROOT . '/lib/Model/ModelRepresentation.php';
+require_once DOCROOT . '/lib/Model/User.php';
+require_once DOCROOT . '/lib/Validator/Email.php';
+require_once DOCROOT . '/lib/Validator/Username.php';
+require_once DOCROOT . '/lib/Validator/Url.php';
+
+class Location implements ModelRepresentation
+{
+    /* ... */
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Rethinking The Way You Load Classes

+ + +

PHP 5.2 and upper provides a usable autoloading API with performances close to +the use of require_once() thanks to the following functions:

+

__autoload()

+

Main autoload callback.

+

spl_autoload_register()

+

Register a new autoload callback.

+

spl_autoload_unregister()

+

Unregister an autoload callback.

+

spl_autoload_functions()

+

List all autoload methods.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Examples

+ + +

__autoload()

+
function __autoload($className)
+{
+    require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
+}
+
+ +

spl_autoload_register()

+
function my_autoload($className)
+{
+    require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
+}
+
+spl_autoload_register('my_autoload');
+
+ +

spl_autoload_unregister()

+
spl_autoload_unregister('my_autoload');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Under The Hood

+ + +
new Foo();
+
+ +

The new algorithm in pseudo code:

+
1. Does the 'Foo' class exist?
+    => Yes
+        Go on
+
+    => No
+         Do you have registered autoload functions?
+            => Yes
+                Call each function with 'Foo' as parameter
+                until the class gets included
+
+            => No
+                Is there a `__autoload()` method?
+                    => Yes
+                        Call `__autoload('Foo')`
+
+2. Does the 'Foo' class exist?
+    => Yes
+        Continue
+    => No
+        Fatal Error
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PSR-0 vs PSR-4

+ + +

PSR-0

+
\Zend\Mail\Message
+// => /path/to/project/lib/vendor/Zend/Mail/Message.php
+
+ +

+ +
Zend_Mail_Message
+// => /path/to/project/lib/vendor/Zend/Mail/Message.php
+
+ +

Important: as of 2014-10-21 PSR-0 has been marked as deprecated.

+

PSR-4

+

Like PSR-0, but better:

+
    +
  • more concise folder structure;
  • +
  • remove the remnants of PSR-0 (e.g. PEAR support).
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Leveraging PHP APIs

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Built-in Interfaces

+ + +

ArrayAccess

+

Access properties as an array:

+
$tom = new MyObject();
+$tom['name'] = 'Tom';
+
+ +

Serializable, JsonSerializable

+

Allow the use of serialize() and unserialize(). +Objects implementing JsonSerializable can customize their JSON representation +when encoded with json_encode().

+

Traversable

+

Allow the use of foreach.

+
+

Read more: +http://php.net/manual/en/reserved.interfaces.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Reflection API (1/2)

+ + +

Enable code introspection:

+
/** A comment */
+class MyClass
+{
+    public function hello() { printf("Hello %s", $this->getName()); }
+
+    protected function getName() { return 'foo'; }
+}
+
+$reflClass = new ReflectionClass('MyClass');
+
+// access comments
+var_dump($reflClass->getDocComment());
+// string(16) "/** A comment */"
+
+// get all methods
+$reflClass->getMethods();
+
+// get all public methods
+$reflClass->getMethods(ReflectionMethod::IS_PUBLIC);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Reflection API (2/2)

+ + +

It is even possible to invoke private methods!

+
class MyClass
+{
+    public function hello() { printf("Hello %s", $this->getName()); }
+
+    private function getName() { return 'foo'; }
+}
+
+$reflClass = new ReflectionClass('MyClass');
+
+// access private method
+$method = $reflClass->getMethod('getName');
+$method->setAccessible(true);
+
+$method->invoke(new MyClass());
+// 'foo'
+
+ +
+

Read more: +http://php.net/manual/en/book.reflection.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Standard PHP Library (SPL)

+ + +

Provides a collection of classes and interfaces:

+

Datastructures

+

SplStack, SplQueue, +SplObjectStorage, etc.

+

Named Exceptions

+

LogicException, InvalidArgumentException, OutOfRangeException, +RuntimeException, etc.

+

SPL Functions

+

class_parents(), spl_autoload_register(), spl_autoload_unregister(), etc.

+
+

Read more about the SPL: +http://php.net/manual/en/book.spl.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Observer Pattern (1/2)

+ + +

The SplObserver interface is used alongside SplSubject to implement the +Observer Design Pattern.

+
class Subject implements SplSubject
+{
+    /* ... */
+}
+
+class Observer implements SplObserver
+{
+    /* ... */
+}
+
+$subject = new Subject();
+
+$observer1 = new Observer();
+
+$subject->attach($observer1);
+
+$subject->notify();
+
+ +
+

Read more: http://php.net/manual/en/class.splobserver.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Observer Pattern (2/2)

+ + +

Those interfaces are never used as there is only one default channel for +the notify() method.

+

Symfony2 EventDispatcher +component to the rescue!

+
use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+
+$dispatcher = new EventDispatcher();
+
+$dispatcher->addListener('event_name', function (Event $event) {
+    // ...
+});
+
+$dispatcher->dispatch('event_name');
+
+ +
+

Read more: +http://symfony.com/doc/master/components/event_dispatcher/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Exceptions (1/2)

+ + +

try-catch block with multiple catch statements:

+
try {
+    // ...
+} catch (RuntimeException $e) {
+    // do something
+} catch (Exception $e) {
+    // do something else
+}
+
+ +

Create your own exceptions:

+
class SomethingWentWrong extends RuntimeException
+{
+}
+
+class ErrorWhileDoingSomething extends Exception implements ExceptionInterface
+{
+}
+
+ +

Name your named exceptions!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Exceptions (2/2)

+ + +

try-catch blocks also supports a finally block for code that should be run +regardless of whether an exception has been thrown or not:

+
try {
+    // ..
+} catch (Exception $e) {
+    // do something
+} finally {
+    // the code here will always be executed
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Password Hashing

+ + +

The password hashing API provides an easy to use wrapper around crypt() to +make it easy to create and manage passwords in a secure manner, since PHP 5.5.0.

+

password_hash() and password_verify() are your new friends!

+
$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);
+
+if (password_verify('bad-password', $passwordHash)) {
+    // Correct Password
+} else {
+    // Wrong password
+}
+
+ +
+

Read more about the Password Hashing API: +http://php.net/manual/en/book.password.php.

+
+

+ +
+

A userland implementation exists for PHP >= 5.3.7: +password_compat.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Archive (PHAR)

+ + +

The phar extension provides a way to put entire PHP applications into +a single file called a "phar" (PHP Archive) for easy distribution and +installation.

+

But, the API is hard to use.

+

Solution? Box, a command line for simplifying +the PHAR creation process.

+
+

Read more about PHAR:

+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Management

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Composer

+ + +

There are a ton of PHP libraries, frameworks, and components to choose from. +Most of them have different versions, and don't always work well together.

+

Composer is a tool for dependency management in PHP. It allows you to declare +the dependent libraries your project needs and it will install them in your +project for you.

+

A lot of awesome PHP libraries are +compatible with Composer and listed on Packagist, the +official repository for Composer-compatible PHP libraries.

+
$ curl -sS https://getcomposer.org/installer | php
+
+ +

This will download composer.phar (a PHP binary archive).

+
+

http://getcomposer.org/doc/00-intro.md

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

composer install

+ + +

Create a composer.json file in your project's root directory:

+
{
+    "require": {
+        "willdurand/geocoder": "~2.0"
+    }
+}
+
+ +

You can also require a library by using the require command:

+
$ php composer.phar require willdurand/geocoder
+
+ +

Run the following command to download and install the project dependencies into +a vendor directory:

+
$ php composer.phar install
+
+ +
+

Composer Version +Constraints

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Composer Autoloader

+ + +

Composer automatically generates a PSR-4 +compliant and optimized autoloader for your entire application. Thanks to +Composer, you don't have to take care about how to autoload classes/functions +anymore.

+

Require the generated autoloader in your project as follows:

+
<?php
+
+require 'vendor/autoload.php';
+
+// your PHP code
+
+ +
+

Must read: Composer Primer.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Example

+ + +

Instead of writing a console application by hand, let's use an existing library: +the Symfony2 Console component:

+
{
+    "require": {
+        "symfony/console": "~2.4"
+    }
+}
+
+ +

The structure of your application should look like:

+
console-app
+├── app
+│   └── console
+├── composer.json
+├── src
+├── tests
+└── vendor
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Symfony2 Console Component

+ + +

The easiest way to write strong console applications:

+
    +
  • Create a set of commands;
  • +
  • Add them to a console application.
  • +
+

Your Commands should extend the Command class:

+
class GreetCommand extends Command
+{
+    protected function configure()
+    {
+        // configure the name, arguments, options, etc.
+    }
+
+    protected function execute(InputInterface $in, OutputInterface $out) {
+        // do greet
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Basic Command (1/2)

+ + +

Configuration

+
$this
+    ->setName('demo:greet')
+    ->addArgument(
+        'name',
+        InputArgument::OPTIONAL,
+        'Who do you want to greet?'
+    );
+
+ +

Execution

+
if (null === $name = $input->getArgument('name')) {
+    $name = 'World';
+}
+
+$output->writeln('Hello, ' . $name);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Basic Command (2/2)

+ + +

The Console Application

+
#!/usr/bin/env php
+# app/console
+<?php
+
+// require the Composer autoloader
+require __DIR__ . '/../vendor/autoload.php';
+
+$application = new Application();
+$application->add(new GreetCommand());
+$application->run();
+
+ +

Usage

+
$ app/console demo:greet
+Hello, World
+$ app/console demo:greet William
+Hello, William
+
+ +
+

Read more: +http://symfony.com/doc/current/components/console/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Model View Controller

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

MVC Overview

+ + +

Typical client request process in MVC architecture:

+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Model

+ + +

Model is the layer in charge of data interaction.

+

All data related business logic is embedded here. +Using it should not require to understand internals.

+

Examples:

+
    +
  • Manipulate database records;
  • +
  • Communicate with search engine;
  • +
  • API calls;
  • +
  • etc.
  • +
+
+ +

More on this next week!

+
+ +
+ +

More on this in a few minutes!

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The View

+ + +

PHP is a templating language per se.

+

Never, ever, ever mix HTML and PHP codes or kittens +will die: you have to separate the presentation from the business logic.

+
class PhpTemplateEngine implements TemplateEngine
+{
+    private $templateDir;
+
+    public function __construct($templateDir)
+    {
+        $this->templateDir = $templateDir;
+    }
+
+    public function render($template, array $parameters = [])
+    {
+        extract($parameters);
+
+        ob_start();
+        include $this->templateDir . DIRECTORY_SEPARATOR . $template;
+
+        return ob_get_clean();
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The View

+ + +

Template

+
<!-- my_template.html -->
+<p>Hello, <?php echo $name; ?>!</p>
+
+ +

Even better with PHP 5.4+:

+
<p>Hello, <?= $name ?>!</p>
+
+ +

Usage

+
$engine = new PhpTemplateEngine('/path/to/templates');
+
+echo $engine->render('my_template.html', [
+    'name' => 'World',
+]);
+=> <p>Hello, World!</p>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The View

+ + +

Twig is a modern template engine for PHP. It takes care of escaping for +you and much much more! Read more: +http://twig.sensiolabs.org/.

+

Template

+
{# my_template.html #}
+<p>Hello, {{ name }}!</p>
+
+ +

Usage

+
$loader = new Twig_Loader_Filesystem('/path/to/templates');
+$engine = new Twig_Environment($loader, [
+    'cache' => '/path/to/compilation_cache',
+]);
+
+echo $engine->render('my_template.html', [
+    'name' => 'World',
+]);
+=> <p>Hello, World!</p>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Controller

+ + +

Glue between the Model and the View layers.

+

It should not contain any business logic.

+
class BananaController
+{
+    public function __construct(
+        BananaRepository $repository,
+        TemplateEngine $engine
+    ) {
+        $this->repository = $repository;
+        $this->engine     = $engine;
+    }
+
+    public function listAction()
+    {
+        $bananas = $this->repository->findAll();
+
+        return $this->engine->render('list.html', [
+            'bananas' => $bananas,
+        ]);
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Routing

+ + +

Routing is the process of binding URIs to controllers.

+

Folder organization

+

The simplest kind of routing, but also the hardest one to maintain:

+
web/
+├ trees/
+│ └ pineapple.php
+└ tree.php
+
+ +

Centralized Declaration

+

Modern frameworks provide a routing component such as the Symfony2 Routing +component allowing to define routes in a centralized place, and easing URI +generation.

+

This require a single entry point: the Front Controller.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Front Controller Pattern

+ + +

A controller that handles all requests for a web application:

+

+

This controller dispatches the request to the specialized controllers.

+

It is usually tied to URL rewriting.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Databases

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +
    +
  • Database Design Patterns
  • +
  • Data Access Layer
  • +
  • Object Relational Mapping
  • +
  • Existing Components
  • +
  • A Note About Domain-Driven Design
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Quick note

+ + +

In our context, a database is seen as a server hosting:

+
    +
  • a set of records;
  • +
  • organised through tables or collections;
  • +
  • grouped by databases.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Database Design Patterns

+ + +
    +
  • Row Data Gateway
  • +
  • Table Data Gateway
  • +
  • Active Record
  • +
  • Data Mapper
  • +
  • Identity Map
  • +
  • etc.
  • +
+

Definitions and figures are part of the Catalog of Patterns of Enterprise +Application Architecture +created by Martin Fowler.

+

Don't forget his name! Read his books!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +

An object that acts as a Gateway to a single record (row) in a database. +There is one instance per row.

+
// This is the implementation of `BananaGateway`
+class Banana
+{
+    private $id;
+
+    private $name;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +

Usage

+
$con = new Connection('...');
+
+$banana = new Banana();
+$banana->setName('Super Banana');
+
+// Save the banana
+$banana->insert($con);
+
+// Update it
+$banana->setName('New name for my banana');
+$banana->update($con);
+
+// Delete it
+$banana->delete($con);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +

Under the hood

+
public function insert(Connection $con)
+{
+    // Prepared statement
+    $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
+
+    $stmt->bindValue(':name', $name);
+
+    $stmt->execute();
+
+    // Set the id for this banana
+    //
+    // It becomes easy to know whether the banana is new or not,
+    // you just need to check if id is defined.
+    $this->id = $this->con->lastInsertId();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

An object that acts as a Gateway to a database table. +One instance handles all the rows in the table.

+

It's a Data Access Object.

+
$table = new BananaGateway(new Connection('...'));
+
+// Insert a new record
+$id = $table->insert('My favorite banana');
+
+// Update it
+$table->update($id, 'THE banana');
+
+// Delete it
+$table->delete($id);
+
+ +

CRUD

+

A DAO implements the well-known Create Read Update +Delete methods.

+

Read should not be a single method: Finders to the rescue!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

Implementation

+
class BananaGateway
+{
+    private $con;
+
+    public function __construct(Connection $con)
+    {
+        $this->con = $con;
+    }
+
+    public function insert($name) {}
+
+    public function update($id, $name) {}
+
+    public function delete($id);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

The insert method

+
/**
+ * @param string $name The name of the banana you want to create
+ *
+ * @return int The id of the banana
+ */
+public function insert($name)
+{
+    // Prepared statement
+    $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
+
+    $stmt->bindValue(':name', $name);
+
+    $stmt->execute();
+
+    return $this->con->lastInsertId();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

The update method

+
/**
+ * @param int    $id   The id of the banana to update
+ * @param string $name The new name of the banana
+ *
+ * @return bool Returns `true` on success, `false` otherwise
+ */
+public function update($id, $name)
+{
+    $stmt = $this->con->prepare(<<<SQL
+UPDATE bananas
+SET name = :name
+WHERE id = :id
+SQL
+    );
+
+    $stmt->bindValue(':id', $id);
+    $stmt->bindValue(':name', $name);
+
+    return $stmt->execute();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

The delete method

+
/**
+ * @param int $id The id of the banana to delete
+ *
+ * @return bool Returns `true` on success, `false` otherwise
+ */
+public function delete($id)
+{
+    $stmt = $this->con->prepare('DELETE FROM bananas WHERE id = :id');
+
+    $stmt->bindValue(':id', $id);
+
+    return $stmt->execute();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

Finders

+
// Retrieve all bananas
+$bananas = $table->findAll();
+
+// Find bananas by name matching 'THE %'
+$bananas = $table->findByName('THE %');
+
+// Retrieve a given banana using its id
+$banana = $table->find(123);
+
+// Find one banana by name matching 'THE %'
+$banana = $table->findOneByName('THE %');
+
+ +
+

Use the __call() magic method to create magic finders: +http://www.php.net/manual/en/language.oop5.overloading.php#object.call.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Active Record

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Active Record

+ + +

An object that wraps a row in a database table, encapsulates +the database access, and adds domain logic on that data.

+

Active Record = Row Data Gateway + Domain Logic

+
$con = new Connection('...');
+
+$banana = new Banana();
+$banana->setName('Another banana');
+$banana->save($con);
+
+// Call a method that is part of the domain logic
+// What can a banana do anyway?
+$banana->grow();
+
+// Smart `save()` method
+// use `isNew()` under the hood
+$banana->save($con);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Active Record

+ + +
class Banana
+{
+    private $height = 1;
+
+    public function grow()
+    {
+        $this->height++;
+    }
+
+    public function save(Connection $con)
+    {
+        if ($this->isNew()) {
+            // issue an INSERT query
+        } else {
+            // issue an UPDATE query
+        }
+    }
+
+    public function isNew()
+    {
+        // Yoda style
+        return null === $this->id;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Mapper

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Mapper

+ + +

A layer of Mappers that moves data between objects and a database +while keeping them independent of each other and the mapper itself.

+

Sort of "Man in the Middle".

+
class BananaMapper
+{
+    private $con;
+
+    public function __construct(Connection $con)
+    {
+        $this->con = $con;
+    }
+
+    public function persist(Banana $banana)
+    {
+        // code to save the banana
+    }
+
+    public function remove(Banana $banana)
+    {
+        // code to delete the banana
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Mapper

+ + +

Usage

+
$banana = new Banana();
+$banana->setName('Fantastic Banana');
+
+$con    = new Connection('...');
+$mapper = new BananaMapper($con);
+
+ +

Persist = Save or Update

+
$mapper->persist($banana);
+
+ +

Remove

+
$mapper->remove($banana);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Identity Map

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Identity Map

+ + +

Ensures that each object gets loaded only once by keeping every loaded object in +a map. Looks up objects using the map when referring to them.

+
class Finder
+{
+    private $identityMap = [];
+
+    public function find($id)
+    {
+        if (!isset($this->identityMap[$id])) {
+            // fetch the object for the given id
+            $this->identityMap[$id] = ...;
+        }
+
+        return $this->identityMap[$id];
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer / Data Source Name

+ + +

A Data Access Layer (DAL) is a standard API to manipulate data, +no matter which database server is used. +A Data Source Name (DSN) can be used to determine which database +vendor you are using.

+

PHP Data Object (PDO)

+

A DSN in PHP looks like: <database>:host=<host>;dbname=<dbname> where:

+
    +
  • <database> can be: mysql, sqlite, pgsql, etc;
  • +
  • <host> is the IP address of the database server (e.g. localhost);
  • +
  • <dbname> is your database name.
  • +
+
+

http://www.php.net/manual/en/intro.pdo.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +

PDO usage

+
$dsn = 'mysql:host=localhost;dbname=test';
+
+$con = new PDO($dsn, $user, $password);
+
+// Prepared statement
+$stmt = $con->prepare($query);
+$stmt->execute();
+
+ +

Looks like the Connection class you used before, right?

+
class Connection extends PDO
+{
+}
+
+ +

Usage

+
$con = new Connection($dsn, $user, $password);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +

Refactoring

+

Refactoring is a disciplined technique for restructuring an existing +body of code, altering its internal structure without changing its +external behavior.

+
class Connection extends PDO
+{
+    /**
+     * @param string $query
+     * @param array  $parameters
+     *
+     * @return bool Returns `true` on success, `false` otherwise
+     */
+    public function executeQuery($query, array $parameters = [])
+    {
+        $stmt = $this->prepare($query);
+
+        foreach ($parameters as $name => $value) {
+            $stmt->bindValue(':' . $name, $value);
+        }
+
+        return $stmt->execute();
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +

Usage

+
/**
+ * @param int    $id   The id of the banana to update
+ * @param string $name The new name of the banana
+ *
+ * @return bool Returns `true` on success, `false` otherwise
+ */
+public function update($id, $name)
+{
+    $query = 'UPDATE bananas SET name = :name WHERE id = :id';
+
+    return $this->con->executeQuery($query, [
+        'id'    => $id,
+        'name'  => $name,
+    ]);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (1/4)

+ + +

Introduces the notion of relations between objects:

+
    +
  • One-To-One;
  • +
  • One-To-Many;
  • +
  • Many-To-Many.
  • +
+

An ORM is often considered as a tool that implements some design patterns +seen above, and that eases relationships between objects.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (2/4)

+ + +

One-To-One (1-1)

+

+

Code Snippet

+
$profile = $banana->getProfile();
+
+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (3/4)

+ + +

One-To-Many (1-N)

+

+

Code Snippet

+
$bananas = $bananaTree->getBananas();
+
+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (4/4)

+ + +

Many-To-Many (N-N)

+

+

Code Snippet

+
$roles = [];
+foreach ($banana->getBananaRoles() as $bananaRole) {
+    $roles[] = $bananaRole->getRole();
+}
+
+// Or, better:
+$roles = $banana->getRoles();
+
+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Existing Components

+ + +

Propel ORM

+

An ORM that implements the Table Data Gateway and Row Data Gateway +patterns, often seen as an Active Record approach.

+
+

Documentation: www.propelorm.org.

+
+

Doctrine2 ORM

+

An ORM that implements the Data Mapper pattern.

+
+

Documentation: www.doctrine-project.org.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Note About
Domain-Driven Design

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Entities

+ + +

An object defined primarily by its identity is called an entity:

+
class Customer
+{
+    private $id;
+
+    private $name;
+
+    public function __construct($id, Name $name)
+    {
+        $this->id   = $id;
+        $this->name = $name;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Value Objects

+ + +

An object that represents a descriptive aspect of the domain with no conceptual +identity is called a Value Object:

+
class Name
+{
+    private $firstName;
+
+    private $lastName;
+
+    public function __construct($firstName, $lastName)
+    {
+        $this->firstName = $firstName;
+        $this->lastName  = $lastName;
+    }
+
+    public function getFirstName()
+    {
+        return $this->firstName;
+    }
+
+    public function getLastName()
+    {
+        return $this->lastName;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Repository Pattern

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Repository Pattern

+ + +

A Repository mediates between the domain and data mapping layers, acting +like an in-memory domain object collection.

+
interface CustomerRepository
+{
+    /**
+     * @return Customer
+     */
+    public function find($customerId);
+
+    /**
+     * @return Customer[]
+     */
+    public function findAll();
+
+    public function add(Customer $user);
+
+    public function remove(Customer $user);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Repository Pattern

+ + +

Client objects construct query specifications declaratively and submit them +to Repository for satisfaction.

+

Objects can be added to and removed from the Repository, as they can from +a simple collection of objects, and the mapping code encapsulated by the +Repository will carry out the appropriate operations behind the scenes.

+

Conceptually, a Repository encapsulates the set of objects persisted in a data +store and the operations performed over them, providing a more object-oriented +view of the persistence layer.

+

Repository also supports the objective of achieving a clean separation and +one-way dependency between the domain and data mapping layers.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Specification Pattern

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Specification Pattern

+ + +

The Specification pattern is a way to model business rules as individual +objects. The idea is that a question about an object, is answered by a +isSatisfiedBy() method:

+
interface CustomerSpecification
+{
+    /**
+     * @return boolean
+     */
+    public function isSatisfiedBy(Customer $customer);
+}
+
+ +

+ +
class CustomerIsPremium implements CustomerSpecification
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function isSatisfiedBy(Customer $customer)
+    {
+        // figure out if the customer is indeed premium,
+        // and return true or false.
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Repository ♥ Specification

+ + +

A findSatisfying() method can be added to the CustomerRepository:

+
interface CustomerRepository
+{
+    ...
+
+    /**
+     * @return Customer[]
+     */
+    public function findSatisfying(CustomerSpecification $specification);
+}
+
+ +

Usage

+
$specification = new CustomerIsPremium();
+$customers     = $repository->findSatisfying($specification);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Combine Them!

+ + +
class OrSpecification implements CustomerSpecification
+{
+    public function __construct(
+        CustomerSpecification $s1,
+        CustomerSpecification $s2
+    ) {
+        $this->s1 = $s1;
+        $this->s2 = $s2;
+    }
+
+    public function isSatisfiedBy(Customer $c)
+    {
+        return $this->s1->isSatisfiedBy($c) || $this->s2->isSatisfiedBy($c);
+    }
+}
+
+ +

+ +
class AndSpecification implements CustomerSpecification
+{
+    ...
+
+    public function isSatisfiedBy(Customer $c)
+    {
+        return $this->s1->isSatisfiedBy($c) && $this->s2->isSatisfiedBy($c);
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Combine Them!

+ + +
class NotSpecification implements CustomerSpecification
+{
+    public function __construct(CustomerSpecification $s)
+    {
+        $this->s = $s;
+    }
+
+    public function isSatisfiedBy(Customer $c)
+    {
+        return !$this->s->isSatisfiedBy($c);
+    }
+}
+
+ +

Usage

+
// Find customers who have ordered exactly three times,
+// but who are not premium customers (yet?)
+$specification = new AndSpecification(
+    new CustomerHasOrderedThreeTimes(),
+    new NotSpecification(
+        new CustomerIsPremium()
+    )
+);
+
+$customers = $repository->findSatisfying($specification);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Specification For Business Rules

+ + +

Reuse your specifications in your business layer:

+
class AwesomeOfferSender
+{
+    private $specification;
+
+    public function __construct(CustomerIsPremium $specification)
+    {
+        $this->specification = $specification;
+    }
+
+    public function sendOffersTo(Customer $customer)
+    {
+        if ($this->specification->isSatisfiedBy($customer)) {
+            // send offers
+        }
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Checkout: RulerZ

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Sessions

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Overview

+ + +

Sessions are a way to preserve certain data across subsequent accesses.

+

In A Nutshell

+
    +
  • Unique identifier (session id);
  • +
  • Server side;
  • +
  • Easy to use;
  • +
  • Built-in.
  • +
+

A few use cases:

+
    +
  • Keeping user authentication and roles;
  • +
  • Storing items into a cart;
  • +
  • Storing a flash message between redirections.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Code Please

+ + +
// Initalize session
+session_start();
+
+// Regenerate identifier
+session_regenerate_id();
+
+// Assign "key" to `$value`
+$_SESSION['key'] = $value;
+
+// Retrieve "key"
+$_SESSION['key'];
+
+// Terminate session
+session_destroy();
+
+ +
+

Session should be started before headers are sent! +http://php.net/manual/en/book.session.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Security Concerns

+ + +
    +
  • Prediction (guessing a valid session identifier);
  • +
  • Man in the Middle (capturing a valid session identifier);
  • +
  • Session Fixation (attacker chooses the session identifier);
  • +
  • Session Hijacking (all attacks that attempt to gain access to another user's + session).
  • +
+

Workarounds

+
    +
  • Regenerate ids when authentication changes;
  • +
  • Bind sessions to IP addresses;
  • +
  • Define expiration/timeout;
  • +
  • Don't rely on the default settings;
  • +
  • Use HTTPS.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Session Configuration

+ + +

An example of PHP session configuration that is more secure:

+
; Helps mitigate XSS by telling the browser not to expose the cookie to
+; client side scripting such as JavaScript
+session.cookie_httponly = 1
+
+; Prevents session fixation by making sure that PHP only uses cookies for
+; sessions and disallow session ID passing as a GET parameter
+session.use_only_cookies = 1
+
+; Better entropy source
+; Evades insufficient entropy vulnerabilities
+session.entropy_file = "/dev/urandom"
+; `session.entropy_length` might help too!
+
+; Smaller exploitation window for XSS/CSRF/Clickjacking...
+session.cookie_lifetime = 0
+
+; Ensures session cookies are only sent over secure connections (it requires
+; a valid SSL certificate)
+; Related to OWASP 2013-A6-Sensitive Data Exposure
+session.cookie_secure = 1
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Authentication

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What You Have Right Now

+ + +

No Authentication/Security Layer, anyone can access everything.

+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Big Picture

+ + +


+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Interceptor Pattern

+ + +

The Security Layer, as seen before, has to intercept the process of +converting a request into a response in order to perform some checks.

+

You need a way to hook into this process before invoking the controller: +Interceptor Pattern to the rescue!

+

The Interceptor Pattern allows you to execute some code during the default +application's lifecyle.

+

A way to implement this pattern is to use events. It is more or less like +the Observer/Observable pattern.

+

Event Dispatcher

+

The application notifies a set of listeners to an event. +The listeners can register themselves to a particular event. +An Event Dispatcher manages both the listeners, and the events.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Introducing the Event Dispatcher

+ + +

Simple event dispatcher using a trait:

+
trait EventDispatcherTrait
+{
+    private $events = [];
+
+    public function addListener($name, $callable)
+    {
+        $this->events[$name][] = $callable;
+    }
+
+    public function dispatch($name, array $arguments = [])
+    {
+        foreach ($this->events[$name] as $callable) {
+            call_user_func_array($callable, $arguments);
+        }
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using the EventDispatcherTrait

+ + +

In order to intercept the process described before, you have to notify some +listeners once you enter in the process() method by dispatching the event:

+
class App
+{
+    use EventDispatcherTrait;
+
+    ...
+
+    private function process(Request $request, Route $route)
+    {
+        $this->dispatch('process.before', [ $request ]);
+
+        ...
+    }
+}
+
+ +

The listeners have to listen to this event:

+
$app->addListener('process.before', function (Request $request) {
+    // code to execute
+});
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Firewall (1/2)

+ + +

Now that you can hook into the application's lifecycle, you can write a basic +but powerful Firewall.

+

This firewall needs a whitelist of unsecured routes (i.e. routes that +don't require the user to be authenticated) associated with their allowed HTTP +methods:

+
$allowed = [
+    '/login'     => [ Request::GET, Request::POST ],
+    '/locations' => [ Request::GET ],
+];
+
+ +
+

Never Blacklist; Only +Whitelist

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Firewall (2/2)

+ + +

The Firewall leverages the session to determine whether the user is +authenticated or not:

+
session_start();
+
+if (isset($_SESSION['is_authenticated'])
+    && true === $_SESSION['is_authenticated']) {
+    return;
+}
+
+ +

If authentication fails, the server should return a 401 status code.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Implementing The Firewall

+ + +
$app->addListener('process.before', function(Request $req) use ($app) {
+    session_start();
+
+    $allowed = [
+        '/login' => [ Request::GET, Request::POST ],
+    ];
+
+    if (isset($_SESSION['is_authenticated'])
+        && true === $_SESSION['is_authenticated']) {
+        return;
+    }
+
+    foreach ($allowed as $pattern => $methods) {
+        if (preg_match(sprintf('#^%s$#', $pattern), $req->getUri())
+            && in_array($req->getMethod(), $methods)) {
+            return;
+        }
+    }
+
+    switch ($req->guessBestFormat()) {
+        case 'json':
+            throw new HttpException(401);
+    }
+
+    return $app->redirect('/login');
+});
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Authentication Mechanism

+ + +


+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Adding New Routes

+ + +
$app->get('/login', function () use ($app) {
+    return $app->render('login.php');
+});
+
+$app->post('/login', function (Request $request) use ($app) {
+    $user = $request->getParameter('user');
+    $pass = $request->getParameter('password');
+
+    if ('will' === $user && 'will' === $pass) {
+        $_SESSION['is_authenticated'] = true;
+
+        return $app->redirect('/');
+    }
+
+    return $app->render('login.php', [ 'user' => $user ]);
+});
+
+$app->get('/logout', function (Request $request) use ($app) {
+    session_destroy();
+
+    return $app->redirect('/');
+});
+
+ +
+

UI Terminology: Logon vs +Login.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Stateless Authentication

+ + +

Useful for API authentication.

+

OpenID (in stateless mode)

+

http://openid.net/

+

Basic and Digest Access Authentication

+

http://pretty-rfc.herokuapp.com/RFC2617

+

WSSE Username Token

+

http://www.xml.com/pub/a/2003/12/17/dive.html

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Basic Security Thinking

+ + +
    +
  1. Trust nobody and nothing;
  2. +
  3. Assume a worse-case scenario;
  4. +
  5. Apply Defense-In-Depth;
  6. +
  7. Keep It Simple Stupid (KISS);
  8. +
  9. Principle of Least Privilege;
  10. +
  11. Attackers can smell obscurity;
  12. +
  13. RTFM but never trust it;
  14. +
  15. If it is not tested, it does not work;
  16. +
  17. It is always your fault!
  18. +
+
+

Survive The Deep End: PHP +Security

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Writing Better Code

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +
    +
  • Coding Standards
  • +
  • Programming To The Interface
  • +
  • Dependency Inversion Principle (DIP)
  • +
  • Dependency Injection (DI)
  • +
  • Inversion of Control (IoC)
  • +
  • Dependency Injection Container (DIC)
  • +
  • Component Driven Development
  • +
  • From STUPID to SOLID code!
  • +
  • Object Calisthenics
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Coding Standards

+ + +
<?php
+
+namespace Vendor\Model;
+
+class Foo
+{
+    const VERSION = '1.0';
+
+    public $bar = null;
+
+    protected $opts;
+
+    private $var3 = 123;
+
+    public function __construct(BarInterface $bar, array $opts = [])
+    {
+        $this->bar  = $bar;
+        $this->opts = $opts;
+    }
+}
+
+ +
+

Read more about coding standards with +PSR-1 +and +PSR-2.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Coding Standards Fixer

+ + +

The PHP Coding Standards Fixer tool fixes most issues in your code when +you want to follow the PHP coding standards as defined in the PSR-1 and +PSR-2 documents.

+

Installation

+
$ wget http://cs.sensiolabs.org/get/php-cs-fixer.phar
+
+ +

Usage

+
$ php php-cs-fixer.phar fix /path/to/dir/or/file
+
+ +
+

More information at: +http://cs.sensiolabs.org/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Programming To The Interface

+ + +

Reduces dependency on implementation specifics and makes code more reusable.

+

The BananaController can use either Twig or the raw PHP implementation +as template engine thanks to the TemplateEngine interface:

+
interface TemplateEngine
+{
+    /**
+     * @param string $template
+     * @param array  $parameters
+     *
+     * @return string
+     */
+    public function render($template, array $parameters = []);
+}
+
+ +

You should think about interfaces, not about internal implementation details.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Inversion Principle (DIP)

+ + +

The Dependency Inversion Principle has two parts:

+
    +
  • High-level modules should not depend on low-level modules. Both should depend + on abstractions;
  • +
  • Abstractions should not depend upon details. Details should depend upon + abstractions.
  • +
+

DIP is about the level of the abstraction in the messages sent from your code to +the thing it is calling.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Injection (DI)

+ + +

Dependency Injection is about how one object acquires a dependency.

+
class Foo
+{
+    private $bar;
+
+    // **NOT** DI
+    public function __construct()
+    {
+        $this->bar = new Bar();
+    }
+}
+
+ +

When a dependency is provided externally, then the system is using DI:

+
// DI!
+public function __construct(Bar $bar)
+{
+    $this->bar = $bar;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Injection (Anti) Pattern

+ + +

ServiceLocator

+

The basic idea behind a service locator is to have an object that knows how to +get hold of all of the services that an application might need.

+
class ServiceLocator
+{
+    public static function getBar()
+    {
+        return new Bar();
+    }
+}
+
+ +

+ +
class Foo
+{
+    private $bar;
+
+    public function __construct()
+    {
+        $this->bar = ServiceLocator::getBar();
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Injection Patterns

+ + +

Constructor Injection

+

All dependencies are injected using a constructor:

+
class Foo
+{
+    private $bar;
+
+    public function __construct(BarInterface $bar)
+    {
+        $this->bar = $bar;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Injection Patterns

+ + +

Setter Injection

+

Dependencies are injected through setters:

+
class Foo
+{
+    private $bar;
+
+    public function setBar(BarInterface $bar)
+    {
+        $this->bar = $bar;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Injection Patterns

+ + +

Interface Injection

+

An interface describes the injection:

+
interface BarAware
+{
+    public function setBar(BarInterface $bar);
+}
+
+ +

It needs to be implemented by the class that wants to use a BarInterface:

+
class Foo implements BarAware
+{
+    private $bar;
+
+    public function setBar(BarInterface $bar)
+    {
+        $this->bar = $bar;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Inversion of Control (IoC)

+ + +

Inversion of Control is about who initiates the call. If your code +initiates a call, it is not IoC, if the container/system/library calls back +into code that you provided it, it is IoC.

+

Hollywood Principle: Don't call us, we'll call you.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Injection Container (DIC)

+ + +

A framework or library for building graphs of objects by passing in (injecting) +each object's dependencies. Object lifetimes are handled by the container +instead of by the consuming object.

+

Most of the time, you rely on configuration files to describe your classes +and their dependencies. A class in this context is also known as a +service.

+

You ask this container to retrieve a service, and it is lazy loaded and +dynamically built:

+
// It's an instance of `TemplateEngine`, but you don't know
+// anything about its internal implementation.
+// Is it the raw PHP implementation or Twig?
+$engine = $container->get('template_engine');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Implementations

+ + +

Twittee

+

Twittee is the smallest Dependency Injection +Container written in PHP. +It fits in a tweet (less than 140 characters):

+
class Container
+{
+    protected $s = array();
+
+    function __set($k, $c)
+    {
+        $this->s[$k] = $c;
+    }
+
+    function __get($k)
+    {
+        return $this->s[$k]($this);
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Implementations

+ + +

Pimple

+

Pimple is a small Dependency Injection Container +for PHP 5.3 that consists of just one file and one class.

+

The Symfony2 DependencyInjection Component

+

The DependencyInjection component +allows you to standardize and centralize the way objects are constructed in your +application.

+
+

Read more:

+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Component Driven Development

+ + +

It’s all about Separation of Concerns (SoC).

+

You design components with their own logic, each component does one thing +well, and only one thing.

+

How to manage these components in your application?

+
+

Read more: Component Driven Development: it's like +Lego!

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

From STUPID to SOLID code! (1/2)

+ + +

STUPID

+
    +
  • Singleton
  • +
  • Tight Coupling
  • +
  • Untestability
  • +
  • Premature Optimization
  • +
  • Indescriptive Naming
  • +
  • Duplication
  • +
+
+

Read more: +http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#stupid-code-seriously.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

From STUPID to SOLID code! (2/2)

+ + +

SOLID

+
    +
  • Single Responsibility Principle
  • +
  • Open/Closed Principle
  • +
  • Liskov Substitution Principle
  • +
  • Interface Segregation Principle
  • +
  • Dependency Inversion Principle
  • +
+
+

Read more: +http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#solid-to-the-rescue.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Calisthenics

+ + +

9 rules invented by Jeff Bay in his book The ThoughWorks +Anthology:

+
    +
  1. Only One Level Of Indentation Per Method
  2. +
  3. Don't Use The ELSE Keyword
  4. +
  5. Wrap All Primitives And Strings
  6. +
  7. First Class Collections
  8. +
  9. One Dot Per Line
  10. +
  11. Don't Abbreviate
  12. +
  13. Keep All Entities Small
  14. +
  15. No Classes With More Than Two Instance Variables
  16. +
  17. No Getters/Setters/Properties
  18. +
+
+

Read more: +http://williamdurand.fr/2013/06/03/object-calisthenics/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ + +



+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Testing

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+
+ + +
+

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +
    +
  • Unit Testing
  • +
  • Functional Testing
  • +
  • Behavior Driven Development
  • +
  • Testing Tweet Frameworks
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Unit Testing

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Unit Testing

+ + +

Unit testing is a Method by which individual units of source code are tested to +determine if they are fit for use.

+

PHPUnit is the de-facto standard for +unit testing in PHP projects.

+

Install it using Composer:

+
{
+    "require-dev": {
+        "phpunit/phpunit": "~3.7"
+    }
+}
+
+ +

Or as a PHAR:

+
$ wget http://pear.phpunit.de/get/phpunit.phar
+$ chmod +x phpunit.phar
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHPUnit — The Rules

+ + +

The tests for a class Class go into a class ClassTest.

+

ClassTest should inherit from PHPUnit_Framework_TestCase, but +a common practice is to create a TestCase class for a project, and to inherit +from it:

+
class TestCase extends PHPUnit_Framework_TestCase {}
+
+ +

The tests are public methods that are named test*, but you can also use the +@test annotation:

+
class ClassTest extends TestCase
+{
+    public function testFoo()
+    {
+        $object = new MyClass();
+        $this->assertEquals('foo', $object->foo(), 'optional comment');
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHPUnit — Assertions

+ + +
    +
  • assertEquals()
  • +
  • assertTrue()
  • +
  • assertFalse()
  • +
  • etc.
  • +
+
+

See all assertion methods: +http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Running PHPUnit

+ + +

Running the test suite:

+
$ phpunit
+PHPUnit 3.7.0 by Sebastian Bergmann.
+
+.
+
+Time: 1 seconds, Memory: 5.25Mb
+
+OK (1 test, 1 assertion)
+
+ +

Getting the code coverage:

+
$ phpunit --coverage-text
+
+ +

See also:

+
$ phpunit --log-junit junit_output.xml
+
+$ phpunit --testdox
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Test Double

+ + +
    +
  • Dummy objects are passed around but never actually used. Usually they are + just used to fill parameter lists;
  • +
  • Fake objects actually have working implementations, but usually take some + shortcut which makes them not suitable for production (an in memory database + is a good example);
  • +
  • Stubs provide canned answers to calls made during the test, usually not + responding at all to anything outside what's programmed in for the test. Stubs + may also record information about calls, such as an email gateway stub that + remembers the messages it 'sent', or maybe only how many messages it 'sent' + → State verification;
  • +
  • Mocks are objects pre-programmed with expectations which form a + specification of the calls they are expected to receive → Behavior + verification.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Functional Testing

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Functional Testing

+ + +

Also known as acceptance testing.

+

Use tools to create automated tests that actually use your application rather +than only verifying that individual units of code behave correctly.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Behavior Driven Development

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Behavior Driven Development

+ + +

SpecBDD

+
    +
  • You write specifications that describe how your actual code should + behave;
  • +
  • Focused on technical behavior;
  • +
  • PHPSpec.
  • +
+

StoryBDD

+
    +
  • You write human-readable stories that describe the behavior of your + application;
  • +
  • Business oriented;
  • +
  • Behat.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Behat

+ + +

Install Behat via Composer:

+
{
+    "require-dev": {
+        "behat/behat": "2.4.*@stable"
+    },
+    "config": {
+        "bin-dir": "bin/"
+    }
+}
+
+ +

The bin/behat command is now installed!

+

Or as a PHAR:

+
$ wget https://github.com/downloads/Behat/Behat/behat.phar
+$ chmod +x behat.phar
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using Behat (1/2)

+ + +

Initialize your project:

+
$ bin/behat --init
+
+ +

Define a Feature:

+
# features/your_first_feature.feature
+Feature: Your first feature
+  In order to start using Behat
+  As a manager or developer
+  I need to try
+
+ +

Define a Scenario:

+
Scenario: Successfully describing scenario
+  Given there is something
+  When I do something
+  Then I should see something
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using Behat (2/2)

+ + +

Executing Behat:

+
$ behat
+
+ +

Writing your Step definitions:

+
/**
+ * @Given /^there is something$/
+ */
+ public function thereIsSomething()
+ {
+    throw new PendingException();
+ }
+
+ +
+

Must Read: +https://speakerdeck.com/everzet/behat-by-example.

+
+

+ +
+

Read more about Behat: +http://docs.behat.org/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Testing Tweet Frameworks

+ + +

Tweetest

+
function tweetest($c,$m) {$c=is_callable($c)?$c():$c;echo($c)?'.':"F[{$m}]";}
+
+tweetest($testValue !== 'bar', '$testValue should never equal bar');
+
+ +

TestFrameworkInATweet.php

+
function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n";if(!$p){$GLOBALS['f']=1;}}
+function done(){if(@$GLOBALS['f'])die(1);}
+
+it("should sum two numbers", 1 + 1 == 2);
+it("should display an X for a failing test", 1 + 1 == 3);
+done();
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Awesome Projects

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Assert

+ + +

Assertions and guard methods for input validation (not filtering!) in +business-model, libraries and application low-level code.

+
use Assert\Assertion;
+use Assert\AssertionFailedException;
+
+try {
+    \Assert\that($identifier)->notEmpty()->integer();
+    Assertion::notEmpty($message, 'Message is not specified');
+} catch(AssertionFailedException $e) {
+    $e->getValue();         // the value that caused the failure
+    $e->getConstraints();   // the additional constraints of the assertion
+}
+
+ +
+ +

beberlei/assert

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Carbon

+ + +

A simple API extension for DateTime.

+
$tomorrow            = Carbon::now()->addDay();
+$lastWeek            = (new Carbon())->subWeek();
+$noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London');
+
+if (Carbon::now()->isWeekend()) {
+    echo 'Party!';
+}
+
+Carbon::now()->subDays(5)->diffForHumans();    // 5 days ago
+
+ +

Freezing time:

+
Carbon::setTestNow(Carbon::create(2001, 5, 21, 12));
+
+echo Carbon::now();   // 2001-05-21 12:00:00
+                      // test, test, test!
+
+Carbon::setTestNow(); // clear the mock
+echo Carbon::now();   // 2014-02-02 21:00:00
+
+ +
+ +

briannesbitt/Carbon

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Faker

+ + +

Fake data generator.

+
// use the factory to create a `Faker\Generator` instance
+$faker = Faker\Factory::create();
+
+// generate data by accessing properties
+echo $faker->name;
+// 'Lucy Cechtelar';
+echo $faker->address;
+// "426 Jordy Lodge
+// Cartwrightshire, SC 88120-6700"
+echo $faker->text;
+// Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam
+// beatae sint laudantium consequatur. Magni occaecati itaque sint et sit
+// tempore. Nesciunt amet quidem. Iusto deleniti cum autem ad quia aperiam.
+echo $faker->email;
+// 'tkshlerin@collins.com'
+echo $faker->ipv4;
+// '109.133.32.252'
+echo $faker->creditCardNumber;
+// '4485480221084675'
+
+ +
+ +

fzaninotto/Faker

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Flysystem

+ + +

Filesystem abstraction layer.

+
use League\Flysystem as F
+
+// Adapters: Local, Amazon Web Services - S3, Rackspace Cloud Files,
+//           Dropbox, Ftp, Sftp, Zip, WebDAV
+$filesystem = new F\Filesystem(new F\Adapter\Local(__DIR__.'/path/to/dir'));
+
+// Create a file
+$filesystem->write('path/to/file.txt', 'contents');
+
+// Update a file
+$filesystem->update('file/to/update.ext', 'new contents');
+
+// Write or update a file
+$filesystem->put('filename.txt', 'contents');
+
+// Delete a file
+$filesyste->delete('delete/this/file.md');
+
+// Check if a file exists
+$exists = $filesystem->has('filename.txt');
+
+ +
+ +

thephpleague/flysystem

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Mockery

+ + +
// The PHPUnit Way
+$mock = $this->getMock('SomeClass');
+$mock->expects($this->once())
+    ->method('getName')
+    ->will($this->returnValue('John Doe'));
+
+$mock2 = $this->getMock('AnotherClass');
+$mock2->expects($this->any())
+    ->method('getNumber')
+    ->with(2)
+    ->will($this->returnValue(2));
+$mock2->expects($this->any())
+    ->method('getNumber')
+    ->with(3)
+    ->will($this->returnValue(3));
+
+// The Mockery Way
+$mock = \Mockery::mock('SomeClass');
+$mock->shouldReceive('getName')->once()->andReturn('John Doe');
+
+$mock2 = \Mockery::mock('AnotherClass');
+$mock2->shouldReceive('getNumber')->with(2)->andReturn(2);
+$mock2->shouldReceive('getNumber')->with(3)->andReturn(3);
+
+ +
+ +

padraic/mockery

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Option Type for PHP

+ + +
use PhpOption\Option;
+
+class MyRepository
+{
+    public function findSomeEntity($criteria)
+    {
+        return Option::fromValue($this->em->find(...));
+    }
+}
+
+ +

You always Require an Entity in Calling Code

+
// returns entity, or throws exception
+$entity = $repository->findSomeEntity(...)->get();
+
+ +

Fallback to Default Value If Not Available

+
$entity = $repository->findSomeEntity(...)->getOrElse(new Entity());
+
+ +
+ +

schmittjoh/phpoption

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP-Parser

+ + +

A PHP parser written in PHP, producing Abstract Syntax Trees (AST).

+
<?php
+
+echo 'Hi', 'World';
+
+ +

+ +
array(
+    0: Stmt_Echo(
+        exprs: array(
+            0: Scalar_String(
+                value: Hi
+            )
+            1:
+            Scalar_String(
+                value:
+                World
+            )
+        )
+    )
+)
+
+ +
+ +

nikic/PHP-Parser

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +


React

+ + +

Event-driven, non-blocking I/O with PHP: +http://reactphp.org/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Swiftmailer

+ + +

Comprehensive mailing tools for PHP.

+
// Create the message
+$message = Swift_Message::newInstance()
+    // Give the message a subject
+    ->setSubject('Your subject')
+    // Set the From address with an associative array
+    ->setFrom([ 'john@doe.com' => 'John Doe' ])
+    // Set the To addresses with an associative array
+    ->setTo([ 'receiver@domain.org', 'other@domain.org' => 'A name' ])
+    // Give it a body
+    ->setBody('Here is the message itself')
+    ;
+
+// Create the Transport
+$transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25);
+
+// Create the Mailer using your created Transport
+$mailer = Swift_Mailer::newInstance($transport);
+
+// Send the message
+$result = $mailer->send($message);
+
+ +
+ +

swiftmailer/swiftmailer

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Whoops

+ + +

PHP errors for cool kids.

+

+
+ +

filp/whoops

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Embracing Open Source

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Golden Rules

+ + +
    +
  • Read other people's code is the fastest way to learn;
  • +
  • Think about what you have to do;
  • +
  • Read the code, not the doc;
  • +
  • Never trust the user;
  • +
  • Think again;
  • +
  • Simple is always better than complicated;
  • +
  • Keep your code readable;
  • +
  • Test your code, it eases refactoring;
  • +
  • Keep HTTP protocol in mind.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The End.

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Well... Maybe Not.
PHP Extended

+ + +
+
+

Notes

+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + Fork me on GitHub + + \ No newline at end of file diff --git a/isima-php.cfg b/isima-php.cfg deleted file mode 100644 index ca49b44..0000000 --- a/isima-php.cfg +++ /dev/null @@ -1,14 +0,0 @@ -[landslide] -source = src/first/isima-php.md - src/me.md - src/agenda/isima-php.md - src/common/ - src/isima/ -destination = isima-php.html -theme = themes/avalanche/avalanche -css = css/light.css - css/custom.css - css/isima.css -embed = true -relative = true -linenos = no diff --git a/isima.html b/isima.html new file mode 100644 index 0000000..cbcbe14 --- /dev/null +++ b/isima.html @@ -0,0 +1,10284 @@ + + + + + + + Advanced OOP + + + + + + + + + + + + + + + + + + + + + +
+
+
+ 00:00:00 +
+
+
+
+
+ + +
+
+
+ +

Advanced OOP

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Web Development

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Who Is Speaking?

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

William DURAND

+ + +

PhD / CTO TailorDev

+

Graduated from IUT, ISIMA, Blaise Pascal University. Worked at:

+ +

Open-Source evangelist:

+ +

+ twitter.com/couac +  |  + github.com/willdurand +  |  + williamdurand.fr +

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

edu@drnd.me

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +

Week #1

+

PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, +Client/Server, REST

+

Week #2

+

Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller

+

Week #3

+

Databases, Sessions, Authentication

+

Week #4

+

Security 101

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP: Hypertext Preprocessor

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

History & Numbers

+ + +
    +
  • Created by Rasmus Lerdorf
  • +
  • 4th language on GitHub +(August 2015)
  • +
  • 6th language in the world +(TIOBE January 2016)
  • +
  • 1st language for web development
  • +
  • Running on +75% of all web servers
  • +
  • 20 years old in 2015!
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Linux

+
$ sudo apt-get install php5-common libapache2-mod-php5 php5-cli
+
+ +
+

http://php.net/manual/en/install.unix.debian.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Mac OS X

+
$ curl -s http://php-osx.liip.ch/install.sh | bash -s 7.0
+
+ +

+ +
$ curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6
+
+ +
+

http://php-osx.liip.ch/

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Getting Started

+ + +

Windows

+

+
+

http://php.net/manual/en/install.windows.installer.msi.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HHVM

+ + +

HipHop Virtual Machine for PHP, created by Facebook.

+

HHVM uses a just-in-time compilation approach to achieve superior performance.

+

+
+

http://hhvm.com

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

RTFM: http://php.net

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Syntax

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Primitive Types

+ + +

4 scalar types: boolean, integer, float, string;

+

2 compound types: array, object;

+

2 special types: resource, null;

+

And 3 pseudo types: mixed, number, callback.

+

Note: most of these types have aliases. E.g. double for float.

+
+

Read more about the PHP primitive types: +http://php.net/manual/en/language.types.intro.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Comparison Operators

+ + +
// so, this is a PHP variable
+$a = 5;
+
+// compare value; return true
+var_dump($a == 5);
+
+// compare value (ignore type); return true
+var_dump($a == '5');
+
+// compare type/value (integer vs. integer); return true
+var_dump($a === 5);
+
+// compare type/value (integer vs. string); return false
+var_dump($a === '5');
+
+ +
+

Read more about comparison operators: +http://php.net/manual/en/language.operators.comparison.php.

+
+

Timing Attack Safe String Comparison

+

The hash_equals() +function has been added in PHP 5.6 to compare two strings in constant time.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Operators

+ + +
$a++; // or: ++$a;
+
+$b--; // or: --$b;
+
+$a && $b;   // AND
+$a || $b;   // OR
+
+! $a;       // `true` if $a is not `true`
+
+$a . 'foo'; // concatenation
+
+2 ** 3 = 8 // exponentiation (PHP 5.6+)
+
+ +

But also:

+
$a  = 'foo';
+$a .= 'bar';
+// $a => 'foobar'
+
+$b  = 0;
+$b += 1;    // $b = 1
+$b -= 1;    // $b = 0
+
+$c = 2;
+$c **= 3;   // $c = 8
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

New Operators in PHP 7.0

+ + +

Null Coalescing Operator: ??

+
// Fetches the value of $_GET['user'] and returns 'nobody'
+// if it does not exist.
+$username = $_GET['user'] ?? 'nobody';
+
+// This is equivalent to:
+$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
+
+ +

Spaceship Operator: <=>

+

Returns -1, 0 or 1 when $a is respectively less than, equal to, or +greater than $b:

+
echo 1 <=> 1; // 0
+echo 1 <=> 2; // -1
+echo 2 <=> 1; // 1
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Classes (1/3)

+ + +

Simple class definition

+
class Foo
+{
+}
+
+ +

Important: No class-level visibility in PHP.

+

Abstract class definition

+
abstract class AbstractFoo
+{
+    abstract public function doSomething();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Classes (2/3)

+ + +

Creating an instance:

+
$foo = new Foo();
+// $foo = new Foo;
+
+// can also be done with a variable
+$class = 'Foo';
+$foo   = new $class();
+
+ +

Getting the class name of an instance:

+
echo get_class($foo);
+=> Foo
+
+ +

Useful keyword: instanceof

+
if ($foo instanceof Foo) {
+    // do something
+}
+
+ +
+

http://php.net/manual/en/language.oop5.basic.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Classes (3/3)

+ + +

Anonymous Classes (PHP >= 7.0)

+
new class {
+    public function foo() {
+        return 'foo';
+    }
+}
+
+ +

Anonymous classes behave as traditional classes:

+
interface Logger {
+    public function log($msg);
+}
+
+$logger = new class implements Logger {
+    public function log($msg) {
+        // ...
+    }
+};
+
+$logger->log('Hello, Anonymous Class');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Visibility

+ + +

Keywords

+
    +
  • public
  • +
  • protected
  • +
  • private
  • +
+

The Rules

+

Attribute visibility MUST be set.

+

Method visibility SHOULD be set.

+

Methods without any explicit visibility keyword are defined as public.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Properties

+ + +
class Foo
+{
+    const VALUE = 123;
+    // PHP 5.6+
+    const SENTENCE        = 'The value of VALUE is ' . self::VALUE;
+    const ARRAY_OF_VALUES = ['a', 'b'];
+
+    /**
+     * @var int
+     */
+    public static $count = 0;
+
+    /**
+     * @var Iterator
+     */
+    public $iterator;
+
+    /**
+     * @var array
+     */
+    protected $values = array();
+
+    /**
+     * @var string|null
+     */
+    private $language = null;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (1/4)

+ + +
class Foo
+{
+    public function doSomething()
+    {
+    }
+}
+
+ +

Type Hinting

+

Works with classes, interfaces, arrays, callable, and Closure. You cannot +use scalar types such as int or string with PHP < 7.0:

+
public function doSomething(Foo $foo);
+
+public function doSomething(Traversable $iterator);
+
+public function doSomething(array $values);
+
+public function doSomething(callable $callback);
+
+public function doSomething(Closure $closure);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (2/4)

+ + +

PHP 7 \o/

+

Scalar Type Declarations

+

Works with int, float, string, and bool:

+
function sumOfInts(int ...$ints) {
+    return array_sum($ints);
+}
+
+ +

Return Type Declarations

+
function sumOfInts(int ...$ints) : int {
+    return array_sum($ints);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (3/4)

+ + +

The -> operator is used to call methods on objects.

+

Usage

+
$foo = new Foo();
+$foo->doSomething();
+
+// >= PHP 5.4
+(new Foo())->doSomething();
+
+// can also be done with a variable
+$method = 'doSomething';
+$foo->$method();
+
+$foo->{$method . 'Else'}();
+// will call 'doSomethingElse()'; curly braces are required.
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Methods (4/4)

+ + +
public function doSomething()
+{
+    // method call
+    $this->doSomethingElse();
+
+    // parent method call (inheritance)
+    parent::doSomething();
+
+    // accessing a constant
+    self::VALUE;
+
+    // accessing a constant from another class
+    Bar::ANOTHER_VALUE;
+
+    // accessing an attribute
+    return $this->attribute;
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Static Keyword

+ + +

Attributes/Methods can be defined as static:

+
class Foo
+{
+    public static $value;
+
+    public static function doThings()
+    {
+        // accessing a static attribute
+        // don't forget the dollar sign!
+        self::$value;
+    }
+}
+
+ +

Warning: the static keyword can also be used to define static +variables +and for late static +bindings. +This is different!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Late Static Bindings

+ + +
class A
+{
+    public static function who() { echo __CLASS__; }
+
+    public static function testSelf()
+    {
+        self::who();
+    }
+
+    public static function testStatic()
+    {
+        static::who();
+    }
+}
+
+class B extends A
+{
+    public static function who() { echo __CLASS__; }
+}
+
+ +

+ +
B::testSelf();
+// A
+
+B::testStatic();
+// B
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Static Keyword

+ + +

Usage

+
$foo = new Foo();
+
+// accessing the attribute from an instance
+$foo::$value = 123;
+
+// accessing the attribute directly from the class
+echo Foo::$value;
+=> 123
+
+ +
+

Read more: +http://php.net/manual/en/language.oop5.static.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Variadic Functions

+ + +

New operator ... as of PHP 5.6:

+
function sum(...$numbers)
+{
+    return array_sum($numbers);
+}
+
+echo sum(1, 2);
+// 3
+
+ +

Argument Unpacking

+
$numbers = [ 2, 3 ];
+echo sum(1, ...$numbers);
+// 6
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Interfaces

+ + +

Simple interface definition

+
interface Fooable
+{
+    const VALUE = 123;
+
+    // it's always public anyway
+    public function doSomething();
+}
+
+ +

Inheritance

+
// Interface may extend several other interfaces.
+// This is not possible with class though!
+interface MyTraversable extends Traversable, Countable
+{
+}
+
+ +

Usage

+
// a class may implement several interfaces, but may extend only one class!
+class Foo implements Fooable, MyTraversable {}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Namespaces (1/2)

+ + +

Namespaces prevent naming collisions with identifiers such as function, class, +and interface names:

+
namespace Vendor\Model;
+// ...
+
+ +

Or:

+
namespace MyNamespace {
+    // ...
+}
+
+ +

PSR-0

+

PSR-0 describes a set of rules related to +namespaces for autoloader interoperability:

+
\ns\package\Class_Name      => vendor/ns/package/Class/Name.php
+\ns\package_name\Class_Name => vendor/ns/package_name/Class/Name.php
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Namespaces (2/2)

+ + +

Classes, functions, and constants have to be imported with the use +statement:

+
namespace My\Namespace;
+
+// Pre PHP 7 code
+use some\namespace\ClassA;
+use some\namespace\ClassB;
+
+use function some\namespace\fn_a;
+use function some\namespace\fn_b;
+
+// PHP 7+ code
+use some\namespace\{ClassA, ClassB};
+
+use function some\namespace\{fn_a, fn_b};
+
+class MyClass
+{
+    public function __construct(ClassA $a, ClassB $b) {
+        // ...
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The class Keyword

+ + +

Since PHP 5.5.0, class name resolution is possible via ::class.

+
namespace My\Namespace;
+
+class ClassName
+{
+}
+
+ +

Assuming the class definition above, you can get the Fully Qualified +Class Name (FQCN) by doing:

+
echo ClassName::class;
+// My\namespace\ClassName
+
+ +
+

Read more about the class keyword: +http://php.net/manual/en/language.oop5.basic.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Traits

+ + +

Horizontal Inheritance FTW!

+
trait Hello                         trait World
+{                                   {
+    public function sayHello()          public function sayWorld()
+    {                                    {
+        echo 'Hello ';                       echo 'World';
+    }                                    }
+}                                   }
+
+class MyHelloWorld
+{
+    use Hello, World;
+}
+
+$obj = new MyHelloWorld();
+$obj->sayHello();
+$obj->sayWorld();
+
+ +
+

Read more about traits: +http://php.net/manual/en/language.oop5.traits.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Anonymous Functions

+ + +

An anonymous function, also known as lambda function, is a function +defined, and possibly called, without being bound to an identifier.

+
$greet = function ($name) {
+    printf("Hello %s\n", $name);
+};
+
+$greet('World');
+=> Hello World
+
+ +
+

Read more about anonymous functions: +http://php.net/manual/en/functions.anonymous.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Closures

+ + +

A closure is an anonymous function that owns a context.

+
$fibonacci = function ($n) use (&$fibonacci) {
+    if (0 === $n || 1 === $n) {
+        return $n;
+    }
+
+    return $fibonacci($n - 1) + $fibonacci($n - 2);
+};
+
+echo (int) $fibonacci(6);
+=> 8
+
+ +
+

Read more about closures: +http://php.net/manual/en/class.closure.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Magic Methods

+ + +

Starts with __.

+

Two useful methods:

+
__construct() { /* ... */ }
+
+ +

and:

+
__toString() { /* ... */ }
+
+ +

Other methods are not really useful but it's worth knowing them (__get(), __set()).

+
+

Read more about magic methods: +http://php.net/manual/en/language.oop5.magic.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Generators

+ + +

A generator function looks just like a normal function, except that instead +of returning a value, a generator yields as many values as it needs to.

+

The heart of a generator function is the yield keyword.

+
+

Read more about generators:

+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Errors in PHP 7

+ + +

No more Fatal Errors \o/

+

Many fatal and recoverable fatal errors have been converted to exceptions +inheriting from the new Error class, which itself implements the Throwable +interface, i.e. the new base interface all exceptions inherit.

+
+

https://secure.php.net/manual/en/language.errors.php7.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Command Line

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Command Line (1/2)

+ + +

PHP is an interpreted language, no need for a compiler.

+

You can try PHP using the command line:

+
$ php -r 'echo "Hello, World\n"'
+Hello, World
+
+ +
+ +

Help available by running: php -h

+
+ +

PHP also provides an interactive shell:

+
$ php -a
+Interactive Shell
+
+php > echo "Hello, World\n";
+Hello, World
+
+ +
+

The command line is really useful, read more about command line options: +http://php.net/manual/en/features.commandline.options.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The PHP Command Line (2/2)

+ + +

Your new best friend is the linter:

+
$ php -l my/script.php
+No syntax errors detected in my/script.php
+
+ +
+ +

A linter is a program that looks for problems in your code + (syntax errors for instance).

+
+ +

Embedded web server:

+
$ php -S localhost:8000
+
+ +
+

Learn more about the built-in, command line web server: +http://php.net/manual/en/features.commandline.webserver.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Writing a CLI program

+ + +
#!/usr/bin/env php
+<?php
+
+if (2 !== $argc) {
+    echo "Usage: php $argv[0] [name]\n";
+    exit(1);
+}
+
+$name = $argv[1];
+echo "Hello, $name!\n";
+
+ +

Run the script:

+
$ ./hello.php
+Usage: ./hello.php [name]
+
+$ php hello.php
+Usage: php hello.php [name]
+
+$ php hello.php World
+Hello, World!
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Client/Server

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Client/Server Basics

+ + +

A typical client/server request follows this pattern:

+

+
    +
  1. Client: Hello server, give me the resource at URI;
  2. +
  3. +

    Server: Here is the resource at URI:

    +

    Content

    +
  4. +
+

For HTTP, a typical client is a web browser, and a server is a web +server.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Unified Resource Identifier (URI)

+ + +

In A Nutshell

+
    +
  • URIs identify resources;
  • +
  • URIs are format independent;
  • +
  • URI "file extensions" != RESTful.
  • +
+

Resources

+
    +
  • /bananas/joe: URI for banana "Joe"
  • +
  • /bananas/henry: URI for banana "Henry"
  • +
+

Collections

+
    +
  • /bananas: collection of all available bananas
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Request

+ + +

Request is made of:

+
    +
  • A Unique Resource Identifier (URI);
  • +
  • An HTTP verb (method) describing the action;
  • +
  • Some headers describing requirements;
  • +
  • A request body to send data.
  • +
+

Here is an example:

+
GET /my/simple/uri?with-query-string HTTP/1.1
+Host: example.org
+Content-Type: text/plain; charset=utf-8
+Content-Length: 17
+
+This is a content
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Verbs

+ + +

An HTTP verb is an action to perform on a resource located at a given +URI:

+
    +
  • GET: retrieve a resource or a collection of resources;
  • +
  • POST: create a new resource;
  • +
  • PUT: update an existing resource or create a new resource at a + given URI;
  • +
  • DELETE: delete a given resource;
  • +
  • PATCH: partial update of a given resource.
  • +
+

Important: this list is not exhaustive.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Response

+ + +

Response is made of:

+
    +
  • Some headers to describe the content;
  • +
  • The response's status code;
  • +
  • The content of the response;
  • +
+

Here is an example:

+
HTTP/1.1 200 OK
+Content-Type: text/html; charset=utf-8
+Content-Length: 76
+
+<!DOCTYPE HTML>
+<html>
+    <head>
+    </head>
+    <body>
+        <h1>Hello world !</h1>
+    </body>
+</html>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Status Codes (1/2)

+ + +

1xx Informational

+

2xx Successful

+
    +
  • 200 OK
  • +
  • 201 Created
  • +
  • 204 No Content
  • +
+

3xx Redirections

+
    +
  • 301 Moved Permanently
  • +
  • 302 Found
  • +
  • 304 Not Modified
  • +
+
+

httpstatus.es

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Status Codes (2/2)

+ + +

4xx Client Error

+
    +
  • 400 Bad Request
  • +
  • 401 Unauthorized
  • +
  • 403 Forbidden
  • +
  • 404 Not Found
  • +
  • 405 Method Not Allowed
  • +
  • 406 Not Acceptable
  • +
  • 409 Conflict
  • +
  • 415 Unsupported Media Type
  • +
  • 451 Unavailable For Legal Reasons
  • +
+

5xx Server Error

+
    +
  • 500 Internal Server Error
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Parameters (1/2)

+ + +

There are two types of parameters, query string and request body.

+

If the request follows the URL Form Encoded format, you can access +parameters through global variables:

+
    +
  • query string: $_GET;
  • +
  • request body: $_POST;
  • +
  • All parameters are available in the $_REQUEST global variable.
  • +
+

You can always use the following, but you need to parse them by yourself:

+
    +
  • query string: $_SERVER['QUERY_STRING'];
  • +
  • request body: $HTTP_RAW_POST_DATA + (deprecated, + do not use).
  • +
+

Note: Don't use $_REQUEST, as there is a collision risk!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HTTP Parameters (2/2)

+ + +
GET /my/simple/uri?a=1&id=2 HTTP/1.1
+Host: example.org
+Content-Type: text/plain; charset=utf-8
+Content-Length: 14
+
+b=3&city=paris
+
+ +

Will result in:

+
$_GET     = [ "a" => 1, "id" => 2 ];
+
+$_POST    = [ "b" => 3, "city" => 'paris' ];
+
+$_REQUEST = [ "a" => 1, "id" => 2, "b" => 3, "city" => 'paris' ];
+
+$_SERVER['QUERY_STRING'] = "a=1&id=2";
+
+$HTTP_RAW_POST_DATA      = "b=3&city=paris";
+
+ +

Important: never trust user input, never!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

REST

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

REpresentational State Transfer

+ + +

REST is the underlying architectural principle of the web, formalized as a set +of constraints, described in Roy Fielding's dissertation.

+

An API (i.e. a web service) that adheres to the principles of REST does not +require the client to know anything about the structure of this API. +Rather, the server needs to provide whatever information the client needs to +interact with the service.

+

The key abstraction of information in REST is a resource. Any information +that can be named can be a resource, and is identified by a Unified Resource +Identifier (URI).

+
+

It heavily relies on the HTTP protocol: RFC 2616.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Richardson Maturity Model

+ + +


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 0 - The Swamp of POX

+ + +

In A Nutshell

+
    +
  • HTTP as a tunneling mechanism;
  • +
  • RPC style system (SOAP, XML-RPC).
  • +
+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 1 - Resources

+ + +

In A Nutshell

+
    +
  • Individual resources (URIs);
  • +
  • Notion of object identity.
  • +
+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 2 - HTTP Verbs

+ + +

In A Nutshell

+
    +
  • Client uses specific HTTP verbs;
  • +
  • Server uses HTTP status codes.
  • +
+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 3 - Hypermedia Controls

+ + +

In A Nutshell

+
    +
  • Service discovery via link relations
  • +
  • Hypermedia formats
  • +
+


+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Level 3 = Content Negotiation + HATEOAS

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Media Types

+ + +

In A Nutshell

+
    +
  • Identifies a representation format;
  • +
  • Custom types use application/vnd.[XYZ];
  • +
  • Used inside the Accept / Content-Type headers.
  • +
+ + + + + + + + + + + +
HeaderDescription
`Content-Type`HTTP message format
`Accept`HTTP response format preference
+ +

Hyper Media Types

+

Hyper Media Types are MIME media types that contain native hyper-linking +semantics that induce application flow: application/hal+json, +application/collection+json, etc.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Content Type Negotiation

+ + +

Content Type Negotiation is the principle of finding appropriate response +formats based on client requirements.

+

No standardized algorithm available, even if the Apache +mod_negotiation +algorithm is documented. This also covers encoding (Accept-Encoding) and +language (Accept-Language) negotiation.

+
Accept: application/json, application/xml;q=0.9, text/html;q=0.8,
+    text/*;q=0.7, */*;q=0.5
+
+ + + + + + + + + + + + + +
PriorityMime Type
`q=1.0``application/json`
`q=0.9``application/xml`
`q=0.8``text/html`
`q=0.7``text/*` (ie. any text)
`q=0.5``*/*` (ie. any media type)
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

HATEOAS

+ + +

HATEOAS stands for Hypermedia As The Engine Of +Application State. It means that hypertext should be used to find your +way through the API.

+

It is all about state transitions. Your application is just a big state +machine. +There should be a single endpoint for the resource, and all of the other +actions you would need to undertake should be able to be discovered by +inspecting that resource.

+
<?xml version="1.0" encoding="UTF-8"?>
+<collection page="1" limit="10" pages="1">
+    <user id="123"></user>
+    <user id="456"></user>
+
+    <link rel="self" href="/api/users?page=1&amp;limit=10" />
+    <link rel="first" href="/api/users?page=1&amp;limit=10" />
+    <link rel="last" href="/api/users?page=1&amp;limit=10" />
+</collection>
+
+ +
+

Must read: Haters gonna +HATEOAS.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Autoloading

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Why Is It Necessary?

+ + +

PHP won't magically load your classes by itself.

+

You have to manually include the class declaration:

+
class Octopus
+{
+    public function scream()
+    {
+        echo "o o O O  ° °";
+    }
+}
+
+ +

If you want to create a new Octopus, you will write the following code:

+
$paul = new Octopus();
+$paul->scream();
+
+ +

As the class declaration isn't included, PHP raises a Fatal Error:

+
Fatal error: Class 'Octopus' not found in /path/to/file.php
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The require() Way

+ + +

Include the class definition before instantiating it:

+
require __DIR__ . '../Model/Octopus.php';
+
+$paul = new Octopus();
+$paul->scream();
+
+ +

It works!

+

But, what happens when the class is included again, somewhere else?

+
// somewhere further in your application
+require __DIR__ . '../Model/Octopus.php';
+
+class Squid extends Octopus
+{
+}
+
+ +

PHP raises a Fatal Error:

+
Fatal error: Cannot redeclare class Octopus in /path/to/file.php
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The require_once() Way

+ + +

The require_once() function is identical to require() except that PHP will +check whether the file has already been included:

+
require_once __DIR__ . '../Model/Octopus.php';
+
+$paul = new Octopus();
+$paul->scream();
+
+ +

And somewhere else:

+
// somewhere further in your application
+require_once __DIR__ . '../Model/Octopus.php';
+
+class Squid extends Octopus
+{
+}
+
+ +

It just works!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Working With Multiple Files

+ + +

Multiple require_once() can turn into a nightmare when you deal with more than +a few files:

+
<?php
+
+/**
+ * lib/Model/Location.php
+ */
+require_once __DIR__ . '/../../common.php';
+require_once DOCROOT . '/lib/Model/ModelRepresentation.php';
+require_once DOCROOT . '/lib/Model/User.php';
+require_once DOCROOT . '/lib/Validator/Email.php';
+require_once DOCROOT . '/lib/Validator/Username.php';
+require_once DOCROOT . '/lib/Validator/Url.php';
+
+class Location implements ModelRepresentation
+{
+    /* ... */
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Rethinking The Way You Load Classes

+ + +

PHP 5.2 and upper provides a usable autoloading API with performances close to +the use of require_once() thanks to the following functions:

+

__autoload()

+

Main autoload callback.

+

spl_autoload_register()

+

Register a new autoload callback.

+

spl_autoload_unregister()

+

Unregister an autoload callback.

+

spl_autoload_functions()

+

List all autoload methods.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Examples

+ + +

__autoload()

+
function __autoload($className)
+{
+    require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
+}
+
+ +

spl_autoload_register()

+
function my_autoload($className)
+{
+    require_once __DIR__ . DIRECTORY_SEPARATOR . $className . '.php';
+}
+
+spl_autoload_register('my_autoload');
+
+ +

spl_autoload_unregister()

+
spl_autoload_unregister('my_autoload');
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Under The Hood

+ + +
new Foo();
+
+ +

The new algorithm in pseudo code:

+
1. Does the 'Foo' class exist?
+    => Yes
+        Go on
+
+    => No
+         Do you have registered autoload functions?
+            => Yes
+                Call each function with 'Foo' as parameter
+                until the class gets included
+
+            => No
+                Is there a `__autoload()` method?
+                    => Yes
+                        Call `__autoload('Foo')`
+
+2. Does the 'Foo' class exist?
+    => Yes
+        Continue
+    => No
+        Fatal Error
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PSR-0 vs PSR-4

+ + +

PSR-0

+
\Zend\Mail\Message
+// => /path/to/project/lib/vendor/Zend/Mail/Message.php
+
+ +

+ +
Zend_Mail_Message
+// => /path/to/project/lib/vendor/Zend/Mail/Message.php
+
+ +

Important: as of 2014-10-21 PSR-0 has been marked as deprecated.

+

PSR-4

+

Like PSR-0, but better:

+
    +
  • more concise folder structure;
  • +
  • remove the remnants of PSR-0 (e.g. PEAR support).
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Leveraging PHP APIs

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Built-in Interfaces

+ + +

ArrayAccess

+

Access properties as an array:

+
$tom = new MyObject();
+$tom['name'] = 'Tom';
+
+ +

Serializable, JsonSerializable

+

Allow the use of serialize() and unserialize(). +Objects implementing JsonSerializable can customize their JSON representation +when encoded with json_encode().

+

Traversable

+

Allow the use of foreach.

+
+

Read more: +http://php.net/manual/en/reserved.interfaces.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Reflection API (1/2)

+ + +

Enable code introspection:

+
/** A comment */
+class MyClass
+{
+    public function hello() { printf("Hello %s", $this->getName()); }
+
+    protected function getName() { return 'foo'; }
+}
+
+$reflClass = new ReflectionClass('MyClass');
+
+// access comments
+var_dump($reflClass->getDocComment());
+// string(16) "/** A comment */"
+
+// get all methods
+$reflClass->getMethods();
+
+// get all public methods
+$reflClass->getMethods(ReflectionMethod::IS_PUBLIC);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Reflection API (2/2)

+ + +

It is even possible to invoke private methods!

+
class MyClass
+{
+    public function hello() { printf("Hello %s", $this->getName()); }
+
+    private function getName() { return 'foo'; }
+}
+
+$reflClass = new ReflectionClass('MyClass');
+
+// access private method
+$method = $reflClass->getMethod('getName');
+$method->setAccessible(true);
+
+$method->invoke(new MyClass());
+// 'foo'
+
+ +
+

Read more: +http://php.net/manual/en/book.reflection.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Standard PHP Library (SPL)

+ + +

Provides a collection of classes and interfaces:

+

Datastructures

+

SplStack, SplQueue, +SplObjectStorage, etc.

+

Named Exceptions

+

LogicException, InvalidArgumentException, OutOfRangeException, +RuntimeException, etc.

+

SPL Functions

+

class_parents(), spl_autoload_register(), spl_autoload_unregister(), etc.

+
+

Read more about the SPL: +http://php.net/manual/en/book.spl.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Observer Pattern (1/2)

+ + +

The SplObserver interface is used alongside SplSubject to implement the +Observer Design Pattern.

+
class Subject implements SplSubject
+{
+    /* ... */
+}
+
+class Observer implements SplObserver
+{
+    /* ... */
+}
+
+$subject = new Subject();
+
+$observer1 = new Observer();
+
+$subject->attach($observer1);
+
+$subject->notify();
+
+ +
+

Read more: http://php.net/manual/en/class.splobserver.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Observer Pattern (2/2)

+ + +

Those interfaces are never used as there is only one default channel for +the notify() method.

+

Symfony2 EventDispatcher +component to the rescue!

+
use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\Event;
+
+$dispatcher = new EventDispatcher();
+
+$dispatcher->addListener('event_name', function (Event $event) {
+    // ...
+});
+
+$dispatcher->dispatch('event_name');
+
+ +
+

Read more: +http://symfony.com/doc/master/components/event_dispatcher/.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Exceptions (1/2)

+ + +

try-catch block with multiple catch statements:

+
try {
+    // ...
+} catch (RuntimeException $e) {
+    // do something
+} catch (Exception $e) {
+    // do something else
+}
+
+ +

Create your own exceptions:

+
class SomethingWentWrong extends RuntimeException
+{
+}
+
+class ErrorWhileDoingSomething extends Exception implements ExceptionInterface
+{
+}
+
+ +

Name your named exceptions!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Exceptions (2/2)

+ + +

try-catch blocks also supports a finally block for code that should be run +regardless of whether an exception has been thrown or not:

+
try {
+    // ..
+} catch (Exception $e) {
+    // do something
+} finally {
+    // the code here will always be executed
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Password Hashing

+ + +

The password hashing API provides an easy to use wrapper around crypt() to +make it easy to create and manage passwords in a secure manner, since PHP 5.5.0.

+

password_hash() and password_verify() are your new friends!

+
$passwordHash = password_hash('secret-password', PASSWORD_DEFAULT);
+
+if (password_verify('bad-password', $passwordHash)) {
+    // Correct Password
+} else {
+    // Wrong password
+}
+
+ +
+

Read more about the Password Hashing API: +http://php.net/manual/en/book.password.php.

+
+

+ +
+

A userland implementation exists for PHP >= 5.3.7: +password_compat.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

PHP Archive (PHAR)

+ + +

The phar extension provides a way to put entire PHP applications into +a single file called a "phar" (PHP Archive) for easy distribution and +installation.

+

But, the API is hard to use.

+

Solution? Box, a command line for simplifying +the PHAR creation process.

+
+

Read more about PHAR:

+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Dependency Management

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Composer

+ + +

There are a ton of PHP libraries, frameworks, and components to choose from. +Most of them have different versions, and don't always work well together.

+

Composer is a tool for dependency management in PHP. It allows you to declare +the dependent libraries your project needs and it will install them in your +project for you.

+

A lot of awesome PHP libraries are +compatible with Composer and listed on Packagist, the +official repository for Composer-compatible PHP libraries.

+
$ curl -sS https://getcomposer.org/installer | php
+
+ +

This will download composer.phar (a PHP binary archive).

+
+

http://getcomposer.org/doc/00-intro.md

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

composer install

+ + +

Create a composer.json file in your project's root directory:

+
{
+    "require": {
+        "willdurand/geocoder": "~2.0"
+    }
+}
+
+ +

You can also require a library by using the require command:

+
$ php composer.phar require willdurand/geocoder
+
+ +

Run the following command to download and install the project dependencies into +a vendor directory:

+
$ php composer.phar install
+
+ +
+

Composer Version +Constraints

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Composer Autoloader

+ + +

Composer automatically generates a PSR-4 +compliant and optimized autoloader for your entire application. Thanks to +Composer, you don't have to take care about how to autoload classes/functions +anymore.

+

Require the generated autoloader in your project as follows:

+
<?php
+
+require 'vendor/autoload.php';
+
+// your PHP code
+
+ +
+

Must read: Composer Primer.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Model View Controller

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

MVC Overview

+ + +

Typical client request process in MVC architecture:

+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Model

+ + +

Model is the layer in charge of data interaction.

+

All data related business logic is embedded here. +Using it should not require to understand internals.

+

Examples:

+
    +
  • Manipulate database records;
  • +
  • Communicate with search engine;
  • +
  • API calls;
  • +
  • etc.
  • +
+
+ +

More on this next week!

+
+ +
+ +

More on this in a few minutes!

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The View

+ + +

PHP is a templating language per se.

+

Never, ever, ever mix HTML and PHP codes or kittens +will die: you have to separate the presentation from the business logic.

+
class PhpTemplateEngine implements TemplateEngine
+{
+    private $templateDir;
+
+    public function __construct($templateDir)
+    {
+        $this->templateDir = $templateDir;
+    }
+
+    public function render($template, array $parameters = [])
+    {
+        extract($parameters);
+
+        ob_start();
+        include $this->templateDir . DIRECTORY_SEPARATOR . $template;
+
+        return ob_get_clean();
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The View

+ + +

Template

+
<!-- my_template.html -->
+<p>Hello, <?php echo $name; ?>!</p>
+
+ +

Even better with PHP 5.4+:

+
<p>Hello, <?= $name ?>!</p>
+
+ +

Usage

+
$engine = new PhpTemplateEngine('/path/to/templates');
+
+echo $engine->render('my_template.html', [
+    'name' => 'World',
+]);
+=> <p>Hello, World!</p>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The View

+ + +

Twig is a modern template engine for PHP. It takes care of escaping for +you and much much more! Read more: +http://twig.sensiolabs.org/.

+

Template

+
{# my_template.html #}
+<p>Hello, {{ name }}!</p>
+
+ +

Usage

+
$loader = new Twig_Loader_Filesystem('/path/to/templates');
+$engine = new Twig_Environment($loader, [
+    'cache' => '/path/to/compilation_cache',
+]);
+
+echo $engine->render('my_template.html', [
+    'name' => 'World',
+]);
+=> <p>Hello, World!</p>
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Controller

+ + +

Glue between the Model and the View layers.

+

It should not contain any business logic.

+
class BananaController
+{
+    public function __construct(
+        BananaRepository $repository,
+        TemplateEngine $engine
+    ) {
+        $this->repository = $repository;
+        $this->engine     = $engine;
+    }
+
+    public function listAction()
+    {
+        $bananas = $this->repository->findAll();
+
+        return $this->engine->render('list.html', [
+            'bananas' => $bananas,
+        ]);
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Routing

+ + +

Routing is the process of binding URIs to controllers.

+

Folder organization

+

The simplest kind of routing, but also the hardest one to maintain:

+
web/
+├ trees/
+│ └ pineapple.php
+└ tree.php
+
+ +

Centralized Declaration

+

Modern frameworks provide a routing component such as the Symfony2 Routing +component allowing to define routes in a centralized place, and easing URI +generation.

+

This require a single entry point: the Front Controller.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Front Controller Pattern

+ + +

A controller that handles all requests for a web application:

+

+

This controller dispatches the request to the specialized controllers.

+

It is usually tied to URL rewriting.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Databases

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Agenda

+ + +
    +
  • Database Design Patterns
  • +
  • Data Access Layer
  • +
  • Object Relational Mapping
  • +
  • Existing Components
  • +
  • A Note About Domain-Driven Design
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Quick note

+ + +

In our context, a database is seen as a server hosting:

+
    +
  • a set of records;
  • +
  • organised through tables or collections;
  • +
  • grouped by databases.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Database Design Patterns

+ + +
    +
  • Row Data Gateway
  • +
  • Table Data Gateway
  • +
  • Active Record
  • +
  • Data Mapper
  • +
  • Identity Map
  • +
  • etc.
  • +
+

Definitions and figures are part of the Catalog of Patterns of Enterprise +Application Architecture +created by Martin Fowler.

+

Don't forget his name! Read his books!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +

An object that acts as a Gateway to a single record (row) in a database. +There is one instance per row.

+
// This is the implementation of `BananaGateway`
+class Banana
+{
+    private $id;
+
+    private $name;
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +

Usage

+
$con = new Connection('...');
+
+$banana = new Banana();
+$banana->setName('Super Banana');
+
+// Save the banana
+$banana->insert($con);
+
+// Update it
+$banana->setName('New name for my banana');
+$banana->update($con);
+
+// Delete it
+$banana->delete($con);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Row Data Gateway

+ + +

Under the hood

+
public function insert(Connection $con)
+{
+    // Prepared statement
+    $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
+
+    $stmt->bindValue(':name', $name);
+
+    $stmt->execute();
+
+    // Set the id for this banana
+    //
+    // It becomes easy to know whether the banana is new or not,
+    // you just need to check if id is defined.
+    $this->id = $this->con->lastInsertId();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

An object that acts as a Gateway to a database table. +One instance handles all the rows in the table.

+

It's a Data Access Object.

+
$table = new BananaGateway(new Connection('...'));
+
+// Insert a new record
+$id = $table->insert('My favorite banana');
+
+// Update it
+$table->update($id, 'THE banana');
+
+// Delete it
+$table->delete($id);
+
+ +

CRUD

+

A DAO implements the well-known Create Read Update +Delete methods.

+

Read should not be a single method: Finders to the rescue!

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

Implementation

+
class BananaGateway
+{
+    private $con;
+
+    public function __construct(Connection $con)
+    {
+        $this->con = $con;
+    }
+
+    public function insert($name) {}
+
+    public function update($id, $name) {}
+
+    public function delete($id);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

The insert method

+
/**
+ * @param string $name The name of the banana you want to create
+ *
+ * @return int The id of the banana
+ */
+public function insert($name)
+{
+    // Prepared statement
+    $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)');
+
+    $stmt->bindValue(':name', $name);
+
+    $stmt->execute();
+
+    return $this->con->lastInsertId();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

The update method

+
/**
+ * @param int    $id   The id of the banana to update
+ * @param string $name The new name of the banana
+ *
+ * @return bool Returns `true` on success, `false` otherwise
+ */
+public function update($id, $name)
+{
+    $stmt = $this->con->prepare(<<<SQL
+UPDATE bananas
+SET name = :name
+WHERE id = :id
+SQL
+    );
+
+    $stmt->bindValue(':id', $id);
+    $stmt->bindValue(':name', $name);
+
+    return $stmt->execute();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

The delete method

+
/**
+ * @param int $id The id of the banana to delete
+ *
+ * @return bool Returns `true` on success, `false` otherwise
+ */
+public function delete($id)
+{
+    $stmt = $this->con->prepare('DELETE FROM bananas WHERE id = :id');
+
+    $stmt->bindValue(':id', $id);
+
+    return $stmt->execute();
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Table Data Gateway

+ + +

Finders

+
// Retrieve all bananas
+$bananas = $table->findAll();
+
+// Find bananas by name matching 'THE %'
+$bananas = $table->findByName('THE %');
+
+// Retrieve a given banana using its id
+$banana = $table->find(123);
+
+// Find one banana by name matching 'THE %'
+$banana = $table->findOneByName('THE %');
+
+ +
+

Use the __call() magic method to create magic finders: +http://www.php.net/manual/en/language.oop5.overloading.php#object.call.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Active Record

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Active Record

+ + +

An object that wraps a row in a database table, encapsulates +the database access, and adds domain logic on that data.

+

Active Record = Row Data Gateway + Domain Logic

+
$con = new Connection('...');
+
+$banana = new Banana();
+$banana->setName('Another banana');
+$banana->save($con);
+
+// Call a method that is part of the domain logic
+// What can a banana do anyway?
+$banana->grow();
+
+// Smart `save()` method
+// use `isNew()` under the hood
+$banana->save($con);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Active Record

+ + +
class Banana
+{
+    private $height = 1;
+
+    public function grow()
+    {
+        $this->height++;
+    }
+
+    public function save(Connection $con)
+    {
+        if ($this->isNew()) {
+            // issue an INSERT query
+        } else {
+            // issue an UPDATE query
+        }
+    }
+
+    public function isNew()
+    {
+        // Yoda style
+        return null === $this->id;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Mapper

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Mapper

+ + +

A layer of Mappers that moves data between objects and a database +while keeping them independent of each other and the mapper itself.

+

Sort of "Man in the Middle".

+
class BananaMapper
+{
+    private $con;
+
+    public function __construct(Connection $con)
+    {
+        $this->con = $con;
+    }
+
+    public function persist(Banana $banana)
+    {
+        // code to save the banana
+    }
+
+    public function remove(Banana $banana)
+    {
+        // code to delete the banana
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Mapper

+ + +

Usage

+
$banana = new Banana();
+$banana->setName('Fantastic Banana');
+
+$con    = new Connection('...');
+$mapper = new BananaMapper($con);
+
+ +

Persist = Save or Update

+
$mapper->persist($banana);
+
+ +

Remove

+
$mapper->remove($banana);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Identity Map

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Identity Map

+ + +

Ensures that each object gets loaded only once by keeping every loaded object in +a map. Looks up objects using the map when referring to them.

+
class Finder
+{
+    private $identityMap = [];
+
+    public function find($id)
+    {
+        if (!isset($this->identityMap[$id])) {
+            // fetch the object for the given id
+            $this->identityMap[$id] = ...;
+        }
+
+        return $this->identityMap[$id];
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer / Data Source Name

+ + +

A Data Access Layer (DAL) is a standard API to manipulate data, +no matter which database server is used. +A Data Source Name (DSN) can be used to determine which database +vendor you are using.

+

PHP Data Object (PDO)

+

A DSN in PHP looks like: <database>:host=<host>;dbname=<dbname> where:

+
    +
  • <database> can be: mysql, sqlite, pgsql, etc;
  • +
  • <host> is the IP address of the database server (e.g. localhost);
  • +
  • <dbname> is your database name.
  • +
+
+

http://www.php.net/manual/en/intro.pdo.php

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +

PDO usage

+
$dsn = 'mysql:host=localhost;dbname=test';
+
+$con = new PDO($dsn, $user, $password);
+
+// Prepared statement
+$stmt = $con->prepare($query);
+$stmt->execute();
+
+ +

Looks like the Connection class you used before, right?

+
class Connection extends PDO
+{
+}
+
+ +

Usage

+
$con = new Connection($dsn, $user, $password);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +

Refactoring

+

Refactoring is a disciplined technique for restructuring an existing +body of code, altering its internal structure without changing its +external behavior.

+
class Connection extends PDO
+{
+    /**
+     * @param string $query
+     * @param array  $parameters
+     *
+     * @return bool Returns `true` on success, `false` otherwise
+     */
+    public function executeQuery($query, array $parameters = [])
+    {
+        $stmt = $this->prepare($query);
+
+        foreach ($parameters as $name => $value) {
+            $stmt->bindValue(':' . $name, $value);
+        }
+
+        return $stmt->execute();
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Data Access Layer

+ + +

Usage

+
/**
+ * @param int    $id   The id of the banana to update
+ * @param string $name The new name of the banana
+ *
+ * @return bool Returns `true` on success, `false` otherwise
+ */
+public function update($id, $name)
+{
+    $query = 'UPDATE bananas SET name = :name WHERE id = :id';
+
+    return $this->con->executeQuery($query, [
+        'id'    => $id,
+        'name'  => $name,
+    ]);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (1/4)

+ + +

Introduces the notion of relations between objects:

+
    +
  • One-To-One;
  • +
  • One-To-Many;
  • +
  • Many-To-Many.
  • +
+

An ORM is often considered as a tool that implements some design patterns +seen above, and that eases relationships between objects.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (2/4)

+ + +

One-To-One (1-1)

+

+

Code Snippet

+
$profile = $banana->getProfile();
+
+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (3/4)

+ + +

One-To-Many (1-N)

+

+

Code Snippet

+
$bananas = $bananaTree->getBananas();
+
+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Object Relational Mapping (4/4)

+ + +

Many-To-Many (N-N)

+

+

Code Snippet

+
$roles = [];
+foreach ($banana->getBananaRoles() as $bananaRole) {
+    $roles[] = $bananaRole->getRole();
+}
+
+// Or, better:
+$roles = $banana->getRoles();
+
+ +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Existing Components

+ + +

Propel ORM

+

An ORM that implements the Table Data Gateway and Row Data Gateway +patterns, often seen as an Active Record approach.

+
+

Documentation: www.propelorm.org.

+
+

Doctrine2 ORM

+

An ORM that implements the Data Mapper pattern.

+
+

Documentation: www.doctrine-project.org.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

A Note About
Domain-Driven Design

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Entities

+ + +

An object defined primarily by its identity is called an entity:

+
class Customer
+{
+    private $id;
+
+    private $name;
+
+    public function __construct($id, Name $name)
+    {
+        $this->id   = $id;
+        $this->name = $name;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Value Objects

+ + +

An object that represents a descriptive aspect of the domain with no conceptual +identity is called a Value Object:

+
class Name
+{
+    private $firstName;
+
+    private $lastName;
+
+    public function __construct($firstName, $lastName)
+    {
+        $this->firstName = $firstName;
+        $this->lastName  = $lastName;
+    }
+
+    public function getFirstName()
+    {
+        return $this->firstName;
+    }
+
+    public function getLastName()
+    {
+        return $this->lastName;
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Repository Pattern

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Repository Pattern

+ + +

A Repository mediates between the domain and data mapping layers, acting +like an in-memory domain object collection.

+
interface CustomerRepository
+{
+    /**
+     * @return Customer
+     */
+    public function find($customerId);
+
+    /**
+     * @return Customer[]
+     */
+    public function findAll();
+
+    public function add(Customer $user);
+
+    public function remove(Customer $user);
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Repository Pattern

+ + +

Client objects construct query specifications declaratively and submit them +to Repository for satisfaction.

+

Objects can be added to and removed from the Repository, as they can from +a simple collection of objects, and the mapping code encapsulated by the +Repository will carry out the appropriate operations behind the scenes.

+

Conceptually, a Repository encapsulates the set of objects persisted in a data +store and the operations performed over them, providing a more object-oriented +view of the persistence layer.

+

Repository also supports the objective of achieving a clean separation and +one-way dependency between the domain and data mapping layers.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Specification Pattern

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Specification Pattern

+ + +

The Specification pattern is a way to model business rules as individual +objects. The idea is that a question about an object, is answered by a +isSatisfiedBy() method:

+
interface CustomerSpecification
+{
+    /**
+     * @return boolean
+     */
+    public function isSatisfiedBy(Customer $customer);
+}
+
+ +

+ +
class CustomerIsPremium implements CustomerSpecification
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function isSatisfiedBy(Customer $customer)
+    {
+        // figure out if the customer is indeed premium,
+        // and return true or false.
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Repository ♥ Specification

+ + +

A findSatisfying() method can be added to the CustomerRepository:

+
interface CustomerRepository
+{
+    ...
+
+    /**
+     * @return Customer[]
+     */
+    public function findSatisfying(CustomerSpecification $specification);
+}
+
+ +

Usage

+
$specification = new CustomerIsPremium();
+$customers     = $repository->findSatisfying($specification);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Combine Them!

+ + +
class OrSpecification implements CustomerSpecification
+{
+    public function __construct(
+        CustomerSpecification $s1,
+        CustomerSpecification $s2
+    ) {
+        $this->s1 = $s1;
+        $this->s2 = $s2;
+    }
+
+    public function isSatisfiedBy(Customer $c)
+    {
+        return $this->s1->isSatisfiedBy($c) || $this->s2->isSatisfiedBy($c);
+    }
+}
+
+ +

+ +
class AndSpecification implements CustomerSpecification
+{
+    ...
+
+    public function isSatisfiedBy(Customer $c)
+    {
+        return $this->s1->isSatisfiedBy($c) && $this->s2->isSatisfiedBy($c);
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Combine Them!

+ + +
class NotSpecification implements CustomerSpecification
+{
+    public function __construct(CustomerSpecification $s)
+    {
+        $this->s = $s;
+    }
+
+    public function isSatisfiedBy(Customer $c)
+    {
+        return !$this->s->isSatisfiedBy($c);
+    }
+}
+
+ +

Usage

+
// Find customers who have ordered exactly three times,
+// but who are not premium customers (yet?)
+$specification = new AndSpecification(
+    new CustomerHasOrderedThreeTimes(),
+    new NotSpecification(
+        new CustomerIsPremium()
+    )
+);
+
+$customers = $repository->findSatisfying($specification);
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Specification For Business Rules

+ + +

Reuse your specifications in your business layer:

+
class AwesomeOfferSender
+{
+    private $specification;
+
+    public function __construct(CustomerIsPremium $specification)
+    {
+        $this->specification = $specification;
+    }
+
+    public function sendOffersTo(Customer $customer)
+    {
+        if ($this->specification->isSatisfiedBy($customer)) {
+            // send offers
+        }
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Checkout: RulerZ

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Sessions

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Overview

+ + +

Sessions are a way to preserve certain data across subsequent accesses.

+

In A Nutshell

+
    +
  • Unique identifier (session id);
  • +
  • Server side;
  • +
  • Easy to use;
  • +
  • Built-in.
  • +
+

A few use cases:

+
    +
  • Keeping user authentication and roles;
  • +
  • Storing items into a cart;
  • +
  • Storing a flash message between redirections.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Code Please

+ + +
// Initalize session
+session_start();
+
+// Regenerate identifier
+session_regenerate_id();
+
+// Assign "key" to `$value`
+$_SESSION['key'] = $value;
+
+// Retrieve "key"
+$_SESSION['key'];
+
+// Terminate session
+session_destroy();
+
+ +
+

Session should be started before headers are sent! +http://php.net/manual/en/book.session.php.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Security Concerns

+ + +
    +
  • Prediction (guessing a valid session identifier);
  • +
  • Man in the Middle (capturing a valid session identifier);
  • +
  • Session Fixation (attacker chooses the session identifier);
  • +
  • Session Hijacking (all attacks that attempt to gain access to another user's + session).
  • +
+

Workarounds

+
    +
  • Regenerate ids when authentication changes;
  • +
  • Bind sessions to IP addresses;
  • +
  • Define expiration/timeout;
  • +
  • Don't rely on the default settings;
  • +
  • Use HTTPS.
  • +
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Session Configuration

+ + +

An example of PHP session configuration that is more secure:

+
; Helps mitigate XSS by telling the browser not to expose the cookie to
+; client side scripting such as JavaScript
+session.cookie_httponly = 1
+
+; Prevents session fixation by making sure that PHP only uses cookies for
+; sessions and disallow session ID passing as a GET parameter
+session.use_only_cookies = 1
+
+; Better entropy source
+; Evades insufficient entropy vulnerabilities
+session.entropy_file = "/dev/urandom"
+; `session.entropy_length` might help too!
+
+; Smaller exploitation window for XSS/CSRF/Clickjacking...
+session.cookie_lifetime = 0
+
+; Ensures session cookies are only sent over secure connections (it requires
+; a valid SSL certificate)
+; Related to OWASP 2013-A6-Sensitive Data Exposure
+session.cookie_secure = 1
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Authentication

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

What You Have Right Now

+ + +

No Authentication/Security Layer, anyone can access everything.

+


+
+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Big Picture

+ + +


+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Interceptor Pattern

+ + +

The Security Layer, as seen before, has to intercept the process of +converting a request into a response in order to perform some checks.

+

You need a way to hook into this process before invoking the controller: +Interceptor Pattern to the rescue!

+

The Interceptor Pattern allows you to execute some code during the default +application's lifecyle.

+

A way to implement this pattern is to use events. It is more or less like +the Observer/Observable pattern.

+

Event Dispatcher

+

The application notifies a set of listeners to an event. +The listeners can register themselves to a particular event. +An Event Dispatcher manages both the listeners, and the events.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Introducing the Event Dispatcher

+ + +

Simple event dispatcher using a trait:

+
trait EventDispatcherTrait
+{
+    private $events = [];
+
+    public function addListener($name, $callable)
+    {
+        $this->events[$name][] = $callable;
+    }
+
+    public function dispatch($name, array $arguments = [])
+    {
+        foreach ($this->events[$name] as $callable) {
+            call_user_func_array($callable, $arguments);
+        }
+    }
+}
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Using the EventDispatcherTrait

+ + +

In order to intercept the process described before, you have to notify some +listeners once you enter in the process() method by dispatching the event:

+
class App
+{
+    use EventDispatcherTrait;
+
+    ...
+
+    private function process(Request $request, Route $route)
+    {
+        $this->dispatch('process.before', [ $request ]);
+
+        ...
+    }
+}
+
+ +

The listeners have to listen to this event:

+
$app->addListener('process.before', function (Request $request) {
+    // code to execute
+});
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Firewall (1/2)

+ + +

Now that you can hook into the application's lifecycle, you can write a basic +but powerful Firewall.

+

This firewall needs a whitelist of unsecured routes (i.e. routes that +don't require the user to be authenticated) associated with their allowed HTTP +methods:

+
$allowed = [
+    '/login'     => [ Request::GET, Request::POST ],
+    '/locations' => [ Request::GET ],
+];
+
+ +
+

Never Blacklist; Only +Whitelist

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The Firewall (2/2)

+ + +

The Firewall leverages the session to determine whether the user is +authenticated or not:

+
session_start();
+
+if (isset($_SESSION['is_authenticated'])
+    && true === $_SESSION['is_authenticated']) {
+    return;
+}
+
+ +

If authentication fails, the server should return a 401 status code.

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Implementing The Firewall

+ + +
$app->addListener('process.before', function(Request $req) use ($app) {
+    session_start();
+
+    $allowed = [
+        '/login' => [ Request::GET, Request::POST ],
+    ];
+
+    if (isset($_SESSION['is_authenticated'])
+        && true === $_SESSION['is_authenticated']) {
+        return;
+    }
+
+    foreach ($allowed as $pattern => $methods) {
+        if (preg_match(sprintf('#^%s$#', $pattern), $req->getUri())
+            && in_array($req->getMethod(), $methods)) {
+            return;
+        }
+    }
+
+    switch ($req->guessBestFormat()) {
+        case 'json':
+            throw new HttpException(401);
+    }
+
+    return $app->redirect('/login');
+});
+
+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Authentication Mechanism

+ + +


+

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Adding New Routes

+ + +
$app->get('/login', function () use ($app) {
+    return $app->render('login.php');
+});
+
+$app->post('/login', function (Request $request) use ($app) {
+    $user = $request->getParameter('user');
+    $pass = $request->getParameter('password');
+
+    if ('will' === $user && 'will' === $pass) {
+        $_SESSION['is_authenticated'] = true;
+
+        return $app->redirect('/');
+    }
+
+    return $app->render('login.php', [ 'user' => $user ]);
+});
+
+$app->get('/logout', function (Request $request) use ($app) {
+    session_destroy();
+
+    return $app->redirect('/');
+});
+
+ +
+

UI Terminology: Logon vs +Login.

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Stateless Authentication

+ + +

Useful for API authentication.

+

OpenID (in stateless mode)

+

http://openid.net/

+

Basic and Digest Access Authentication

+

http://pretty-rfc.herokuapp.com/RFC2617

+

WSSE Username Token

+

http://www.xml.com/pub/a/2003/12/17/dive.html

+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Basic Security Thinking

+ + +
    +
  1. Trust nobody and nothing;
  2. +
  3. Assume a worse-case scenario;
  4. +
  5. Apply Defense-In-Depth;
  6. +
  7. Keep It Simple Stupid (KISS);
  8. +
  9. Principle of Least Privilege;
  10. +
  11. Attackers can smell obscurity;
  12. +
  13. RTFM but never trust it;
  14. +
  15. If it is not tested, it does not work;
  16. +
  17. It is always your fault!
  18. +
+
+

Survive The Deep End: PHP +Security

+
+ +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

Next Week:
Web Security 101

+ + +
+
+

Notes

+
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+ +

The End.

+ + +
+
+

Notes

+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + + + Fork me on GitHub + + \ No newline at end of file diff --git a/iut-extended.cfg b/iut-extended.cfg deleted file mode 100644 index 79cf7ea..0000000 --- a/iut-extended.cfg +++ /dev/null @@ -1,24 +0,0 @@ -[landslide] -source = src/first/iut-extended.md - src/me.md - src/agenda/iut-extended.md - src/extended/00_intro.md - src/extended/01_symfony2.md - src/extended/02_symfony2_controllers.md - src/extended/03_symfony2_templating.md - src/extended/04_symfony2_dependency_injection.md - src/extended/05_symfony2_command_line.md - src/extended/06_symfony2_forms.md - src/extended/07_symfony2_validation.md - src/extended/08_symfony2_translation.md - src/extended/09_http_cache.md - src/extended/10_stackphp.md - src/hack/ - src/extended/11_end.md -destination = iut-extended.html -theme = themes/avalanche/avalanche -css = css/solarized.css - css/custom.css -embed = true -relative = true -linenos = no diff --git a/iut-php.cfg b/iut-php.cfg deleted file mode 100644 index ca26343..0000000 --- a/iut-php.cfg +++ /dev/null @@ -1,28 +0,0 @@ -[landslide] -source = src/first/iut-php.md - src/me.md - src/agenda/iut-php.md - src/common/01_php_hypertext_preprocessor.md - src/common/02_the_php_syntax.md - src/common/03_the_php_command_line.md - src/common/04_client_server.md - src/common/05_autoloading.md - src/common/06_leveraging_php_apis.md - src/common/07_dependency_management_in_php.md - src/iut/07_dependency_management_example.md - src/common/08_model_view_controller.md - src/common/09_databases.md - src/common/10_sessions.md - src/common/11_authentication.md - src/iut/12_writing_better_code.md - src/iut/13_testing.md - src/iut/14_awesome_projects.md - src/iut/15_embracing_open_source.md - src/iut/16_end.md -destination = iut-php.html -theme = themes/avalanche/avalanche -css = css/solarized.css - css/custom.css -embed = true -relative = true -linenos = no diff --git a/publish b/publish deleted file mode 100755 index 3bb485f..0000000 --- a/publish +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -TMP_DIR=/tmp/licpro-php-slides - -git stash - -landslide iut-php.cfg -landslide iut-extended.cfg -landslide isima-php.cfg - -rm -rf $TMP_DIR -mkdir $TMP_DIR -mv iut-php.html $TMP_DIR/index.html -mv iut-extended.html $TMP_DIR/extended.html -mv isima-php.html $TMP_DIR/isima.html -cp -R themes/ $TMP_DIR - -git checkout gh-pages - -mv $TMP_DIR/* . -git add index.html extended.html isima.html themes/avalanche/avalanche/ -git commit -m "Publish slides (`date`)" - -git push origin gh-pages -f - -git clean -fdx - -git checkout master - -git stash apply diff --git a/src/agenda/isima-php.md b/src/agenda/isima-php.md deleted file mode 100644 index ff696db..0000000 --- a/src/agenda/isima-php.md +++ /dev/null @@ -1,18 +0,0 @@ -# Agenda - -### [Week #1](#slide10) - -PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, -Client/Server, REST - -### [Week #2](#slide65) - -Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller - -### [Week #3](#slide98) - -Databases, Sessions, Authentication - -### [Week #4](#slide162) - -Security 101 diff --git a/src/agenda/iut-extended.md b/src/agenda/iut-extended.md deleted file mode 100644 index c7aa360..0000000 --- a/src/agenda/iut-extended.md +++ /dev/null @@ -1,13 +0,0 @@ -# Agenda - -* Symfony - * Controllers - * Templating - * Dependency Injection - * Command Line - * Forms - * Validation - * Translation - * HTTP Cache -* Stack PHP -* Hack diff --git a/src/agenda/iut-php.md b/src/agenda/iut-php.md deleted file mode 100644 index 7e50942..0000000 --- a/src/agenda/iut-php.md +++ /dev/null @@ -1,19 +0,0 @@ -# Agenda - -### Week #1 - -PHP: Hypertext Preprocessor, The PHP Syntax, The PHP Command Line, -Client/Server, REST - -### Week #2 - -Autoloading, Leveraging PHP APIs, Dependency Management, Model View Controller - -### Week #3 - -Databases - -### Week #4 - -Sessions, Authentication, Writing Better Code, Testing, Awesome Projects, -Embracing Open Source diff --git a/src/common/01_php_hypertext_preprocessor.md b/src/common/01_php_hypertext_preprocessor.md deleted file mode 100644 index b521753..0000000 --- a/src/common/01_php_hypertext_preprocessor.md +++ /dev/null @@ -1,71 +0,0 @@ -# PHP: Hypertext Preprocessor - ---- - -# History & Numbers - -* Created by Rasmus Lerdorf -* 4th language on GitHub -([August 2015](https://github.com/blog/2047-language-trends-on-github)) -* 6th language in the world -([TIOBE](http://tiobe.com/index.php/content/paperinfo/tpci/index.html) January 2016) -* 1st language for web development -* Running on +75% of all web servers -* 20 years old in 2015! - ---- - -# ![](../images/core-team.jpg) - ---- - -# Getting Started - -## Linux - - !bash - $ sudo apt-get install php5-common libapache2-mod-php5 php5-cli - -> [http://php.net/manual/en/install.unix.debian.php](http://php.net/manual/en/install.unix.debian.php) - ---- - -# Getting Started - -## Mac OS X - - !bash - $ curl -s http://php-osx.liip.ch/install.sh | bash -s 7.0 - -

- - !bash - $ curl -s http://php-osx.liip.ch/install.sh | bash -s 5.6 - -> [http://php-osx.liip.ch/](http://php-osx.liip.ch/) - ---- - -# Getting Started - -## Windows - -![](../images/y-u-no-use-linux.jpg) - -> [http://php.net/manual/en/install.windows.installer.msi.php](http://php.net/manual/en/install.windows.installer.msi.php) - ---- - -# HHVM - -HipHop Virtual Machine for PHP, created by Facebook. - -HHVM uses a just-in-time compilation approach to achieve superior performance. - -![](../images/hhvm.jpg) - -> [http://hhvm.com](http://hhvm.com) - ---- - -# RTFM: [http://php.net](http://php.net) diff --git a/src/common/02_the_php_syntax.md b/src/common/02_the_php_syntax.md deleted file mode 100644 index 01a8c37..0000000 --- a/src/common/02_the_php_syntax.md +++ /dev/null @@ -1,653 +0,0 @@ -# The PHP Syntax - ---- - -# Primitive Types - -4 **scalar** types: `boolean`, `integer`, `float`, `string`; - -2 **compound** types: `array`, `object`; - -2 **special** types: `resource`, `null`; - -And 3 **pseudo** types: `mixed`, `number`, `callback`. - -**Note:** most of these types have aliases. E.g. `double` for `float`. - -> Read more about the **PHP primitive types**: -[http://php.net/manual/en/language.types.intro.php](http://php.net/manual/en/language.types.intro.php). - ---- - -# Comparison Operators - - !php - // so, this is a PHP variable - $a = 5; - - // compare value; return true - var_dump($a == 5); - - // compare value (ignore type); return true - var_dump($a == '5'); - - // compare type/value (integer vs. integer); return true - var_dump($a === 5); - - // compare type/value (integer vs. string); return false - var_dump($a === '5'); - -> Read more about **comparison operators**: -[http://php.net/manual/en/language.operators.comparison.php](http://php.net/manual/en/language.operators.comparison.php). - -### Timing Attack Safe String Comparison - -The [`hash_equals()`](http://php.net/manual/en/function.hash-equals.php) -function has been added in PHP 5.6 to compare two strings in constant time. - ---- - -# Operators - - !php - $a++; // or: ++$a; - - $b--; // or: --$b; - - $a && $b; // AND - $a || $b; // OR - - ! $a; // `true` if $a is not `true` - - $a . 'foo'; // concatenation - - 2 ** 3 = 8 // exponentiation (PHP 5.6+) - -But also: - - !php - $a = 'foo'; - $a .= 'bar'; - // $a => 'foobar' - - $b = 0; - $b += 1; // $b = 1 - $b -= 1; // $b = 0 - - $c = 2; - $c **= 3; // $c = 8 - ---- - -# New Operators in PHP 7.0 - -### Null Coalescing Operator: `??` - - !php - // Fetches the value of $_GET['user'] and returns 'nobody' - // if it does not exist. - $username = $_GET['user'] ?? 'nobody'; - - // This is equivalent to: - $username = isset($_GET['user']) ? $_GET['user'] : 'nobody'; - -### Spaceship Operator: `<=>` - -Returns `-1`, `0` or `1` when `$a` is respectively less than, equal to, or -greater than `$b`: - - !php - echo 1 <=> 1; // 0 - echo 1 <=> 2; // -1 - echo 2 <=> 1; // 1 - ---- - -# Classes (1/3) - -### Simple class definition - - !php - class Foo - { - } - -**Important:** No class-level visibility in PHP. - -### Abstract class definition - - !php - abstract class AbstractFoo - { - abstract public function doSomething(); - } - ---- - -# Classes (2/3) - -Creating an instance: - - !php - $foo = new Foo(); - // $foo = new Foo; - - // can also be done with a variable - $class = 'Foo'; - $foo = new $class(); - -Getting the class name of an instance: - - !php - echo get_class($foo); - => Foo - -Useful keyword: `instanceof` - - !php - if ($foo instanceof Foo) { - // do something - } - -> [http://php.net/manual/en/language.oop5.basic.php](http://php.net/manual/en/language.oop5.basic.php) - ---- - -# Classes (3/3) - -### Anonymous Classes (PHP >= 7.0) - - !php - new class { - public function foo() { - return 'foo'; - } - } - -Anonymous classes behave as traditional classes: - - !php - interface Logger { - public function log($msg); - } - - $logger = new class implements Logger { - public function log($msg) { - // ... - } - }; - - $logger->log('Hello, Anonymous Class'); - ---- - -# Visibility - -### Keywords - -* `public` -* `protected` -* `private` - - -### The Rules - -Attribute visibility MUST be set. - -Method visibility SHOULD be set. - -Methods without any explicit visibility keyword are defined as `public`. - ---- - -# Properties - - !php - class Foo - { - const VALUE = 123; - // PHP 5.6+ - const SENTENCE = 'The value of VALUE is ' . self::VALUE; - const ARRAY_OF_VALUES = ['a', 'b']; - - /** - * @var int - */ - public static $count = 0; - - /** - * @var Iterator - */ - public $iterator; - - /** - * @var array - */ - protected $values = array(); - - /** - * @var string|null - */ - private $language = null; - } - ---- - -# Methods (1/4) - - !php - class Foo - { - public function doSomething() - { - } - } - -### Type Hinting - -Works with classes, interfaces, arrays, `callable`, and `Closure`. You cannot -use scalar types such as `int` or `string` with PHP < 7.0: - - !php - public function doSomething(Foo $foo); - - public function doSomething(Traversable $iterator); - - public function doSomething(array $values); - - public function doSomething(callable $callback); - - public function doSomething(Closure $closure); - ---- - -# Methods (2/4) - -[PHP 7](https://secure.php.net/manual/en/migration70.new-features.php) \o/ - -### Scalar Type Declarations - -Works with `int`, `float`, `string`, and `bool`: - - !php - function sumOfInts(int ...$ints) { - return array_sum($ints); - } - -### Return Type Declarations - - !php - function sumOfInts(int ...$ints) : int { - return array_sum($ints); - } - ---- - -# Methods (3/4) - -The `->` operator is used to call methods on objects. - -### Usage - - !php - $foo = new Foo(); - $foo->doSomething(); - - // >= PHP 5.4 - (new Foo())->doSomething(); - - // can also be done with a variable - $method = 'doSomething'; - $foo->$method(); - - $foo->{$method . 'Else'}(); - // will call 'doSomethingElse()'; curly braces are required. - ---- - -# Methods (4/4) - - !php - public function doSomething() - { - // method call - $this->doSomethingElse(); - - // parent method call (inheritance) - parent::doSomething(); - - // accessing a constant - self::VALUE; - - // accessing a constant from another class - Bar::ANOTHER_VALUE; - - // accessing an attribute - return $this->attribute; - } - ---- - -# Static Keyword - -Attributes/Methods can be defined as `static`: - - !php - class Foo - { - public static $value; - - public static function doThings() - { - // accessing a static attribute - // don't forget the dollar sign! - self::$value; - } - } - -**Warning:** the `static` keyword can also be used to [define static -variables](http://php.net/manual/en/language.variables.scope.php#language.variables.scope.static) -and for [late static -bindings](http://php.net/manual/en/language.oop5.late-static-bindings.php). -This is different! - ---- - -# Late Static Bindings - - !php - class A - { - public static function who() { echo __CLASS__; } - - public static function testSelf() - { - self::who(); - } - - public static function testStatic() - { - static::who(); - } - } - - class B extends A - { - public static function who() { echo __CLASS__; } - } - -

- - !php - B::testSelf(); - // A - - B::testStatic(); - // B - ---- - -# Static Keyword - -### Usage - - !php - $foo = new Foo(); - - // accessing the attribute from an instance - $foo::$value = 123; - - // accessing the attribute directly from the class - echo Foo::$value; - => 123 - -> Read more: -[http://php.net/manual/en/language.oop5.static.php](http://php.net/manual/en/language.oop5.static.php). - ---- - -# Variadic Functions - -New operator `...` as of PHP 5.6: - - !php - function sum(...$numbers) - { - return array_sum($numbers); - } - - echo sum(1, 2); - // 3 - -### Argument Unpacking - - !php - $numbers = [ 2, 3 ]; - echo sum(1, ...$numbers); - // 6 - ---- - -# Interfaces - -### Simple interface definition - - !php - interface Fooable - { - const VALUE = 123; - - // it's always public anyway - public function doSomething(); - } - -### Inheritance - - !php - // Interface may extend several other interfaces. - // This is not possible with class though! - interface MyTraversable extends Traversable, Countable - { - } - -### Usage - - !php - // a class may implement several interfaces, but may extend only one class! - class Foo implements Fooable, MyTraversable {} - ---- - -# Namespaces (1/2) - -Namespaces prevent naming collisions with identifiers such as function, class, -and interface names: - - !php - namespace Vendor\Model; - // ... - -Or: - - !php - namespace MyNamespace { - // ... - } - -### PSR-0 - -[PSR-0](http://php-fig.org/psr/psr-0/) describes a set of rules related to -namespaces for autoloader interoperability: - - !php - \ns\package\Class_Name => vendor/ns/package/Class/Name.php - \ns\package_name\Class_Name => vendor/ns/package_name/Class/Name.php - ---- - -# Namespaces (2/2) - -Classes, functions, and constants have to be **imported** with the `use` -statement: - - !php - namespace My\Namespace; - - // Pre PHP 7 code - use some\namespace\ClassA; - use some\namespace\ClassB; - - use function some\namespace\fn_a; - use function some\namespace\fn_b; - - // PHP 7+ code - use some\namespace\{ClassA, ClassB}; - - use function some\namespace\{fn_a, fn_b}; - - class MyClass - { - public function __construct(ClassA $a, ClassB $b) { - // ... - } - } - ---- - -# The `class` Keyword - -Since PHP 5.5.0, class name resolution is possible via `::class`. - - !php - namespace My\Namespace; - - class ClassName - { - } - -Assuming the class definition above, you can get the **F**ully **Q**ualified -**C**lass **N**ame (FQCN) by doing: - - !php - echo ClassName::class; - // My\namespace\ClassName - -> Read more about the `class` keyword: -[http://php.net/manual/en/language.oop5.basic.php](http://php.net/manual/en/language.oop5.basic.php#language.oop5.basic.class.class). - ---- - -# Traits - -Horizontal Inheritance FTW! - - !php - trait Hello trait World - { { - public function sayHello() public function sayWorld() - { { - echo 'Hello '; echo 'World'; - } } - } } - - class MyHelloWorld - { - use Hello, World; - } - - $obj = new MyHelloWorld(); - $obj->sayHello(); - $obj->sayWorld(); - - -> Read more about **traits**: -[http://php.net/manual/en/language.oop5.traits.php](http://php.net/manual/en/language.oop5.traits.php). - ---- - -# Anonymous Functions - -An **anonymous function**, also known as **lambda** function, is a function -defined, and possibly called, without being bound to an identifier. - - !php - $greet = function ($name) { - printf("Hello %s\n", $name); - }; - - $greet('World'); - => Hello World - -> Read more about **anonymous functions**: -[http://php.net/manual/en/functions.anonymous.php](http://php.net/manual/en/functions.anonymous.php). - ---- - -# Closures - -A **closure** is an anonymous function that owns a context. - - !php - $fibonacci = function ($n) use (&$fibonacci) { - if (0 === $n || 1 === $n) { - return $n; - } - - return $fibonacci($n - 1) + $fibonacci($n - 2); - }; - - echo (int) $fibonacci(6); - => 8 - -> Read more about **closures**: -[http://php.net/manual/en/class.closure.php](http://php.net/manual/en/class.closure.php). - ---- - -# Magic Methods - -Starts with `__`. - -Two useful methods: - - !php - __construct() { /* ... */ } - -and: - - !php - __toString() { /* ... */ } - -Other methods are not really useful but it's worth knowing them (`__get()`, `__set()`). - -> Read more about **magic methods**: -[http://php.net/manual/en/language.oop5.magic.php](http://php.net/manual/en/language.oop5.magic.php). - ---- - -# Generators - -A **generator function** looks just like a normal function, except that instead -of returning a value, a generator **yields** as many values as it needs to. - -The heart of a generator function is the `yield` keyword. - -> Read more about **generators**: -> -> * [http://php.net/manual/en/language.generators.php](http://php.net/manual/en/language.generators.php); -> * [What Generators Can Do For -You](http://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html); -> * [https://github.com/nikic/iter](https://github.com/nikic/iter) (examples). - ---- - -# Errors in PHP 7 - -No more **Fatal Errors** \o/ - -Many fatal and recoverable fatal errors have been converted to exceptions -inheriting from the new `Error` class, which itself implements the `Throwable` -interface, i.e. the new base interface all exceptions inherit. - -> -[https://secure.php.net/manual/en/language.errors.php7.php](https://secure.php.net/manual/en/language.errors.php7.php) diff --git a/src/common/03_the_php_command_line.md b/src/common/03_the_php_command_line.md deleted file mode 100644 index db11a53..0000000 --- a/src/common/03_the_php_command_line.md +++ /dev/null @@ -1,82 +0,0 @@ -# The PHP Command Line - ---- - -# The PHP Command Line (1/2) - -PHP is an interpreted language, no need for a compiler. - -You can try PHP using the command line: - - !bash - $ php -r 'echo "Hello, World\n"' - Hello, World - -
- -

Help available by running: php -h

-
- -PHP also provides an interactive shell: - - !bash - $ php -a - Interactive Shell - - php > echo "Hello, World\n"; - Hello, World - -> The command line is really useful, read more about command line options: -[http://php.net/manual/en/features.commandline.options.php](http://php.net/manual/en/features.commandline.options.php). - ---- - -# The PHP Command Line (2/2) - -Your new best friend is the _linter_: - - !bash - $ php -l my/script.php - No syntax errors detected in my/script.php - -
- -

A linter is a program that looks for problems in your code - (syntax errors for instance).

-
- -Embedded web server: - - !bash - $ php -S localhost:8000 - -> Learn more about the built-in, command line web server: -[http://php.net/manual/en/features.commandline.webserver.php](http://php.net/manual/en/features.commandline.webserver.php). - ---- - -# Writing a CLI program - - !php - #!/usr/bin/env php - - - - - -

Hello world !

- - - ---- - -# Status Codes (1/2) - -### `1xx` Informational - -### `2xx` Successful - -* `200` OK -* `201` Created -* `204` No Content - -### `3xx` Redirections - -* `301` Moved Permanently -* `302` Found -* `304` Not Modified - -> [httpstatus.es](http://httpstatus.es/) - ---- - -# Status Codes (2/2) - -### `4xx` Client Error - -* `400` Bad Request -* `401` Unauthorized -* `403` Forbidden -* `404` Not Found -* `405` Method Not Allowed -* `406` Not Acceptable -* `409` Conflict -* `415` Unsupported Media Type -* `451` Unavailable For Legal Reasons - -### `5xx` Server Error - -* `500` Internal Server Error - ---- - -# HTTP Parameters (1/2) - -There are two types of parameters, **query string** and **request body**. - -If the request follows the **URL Form Encoded** format, you can access -parameters through global variables: - -* **query string**: `$_GET`; -* **request body**: `$_POST`; -* All parameters are available in the `$_REQUEST` global variable. - -You can always use the following, but you need to parse them by yourself: - -* **query string**: `$_SERVER['QUERY_STRING']`; -* **request body**: `$HTTP_RAW_POST_DATA` - ([deprecated](https://github.com/php/php-src/blob/8648f76bac2f78391a1539253f21d62f53d83022/NEWS#L19-L22), - do not use). - -**Note**: Don't use `$_REQUEST`, as there is a collision risk! - ---- - -# HTTP Parameters (2/2) - - !http - GET /my/simple/uri?a=1&id=2 HTTP/1.1 - Host: example.org - Content-Type: text/plain; charset=utf-8 - Content-Length: 14 - - b=3&city=paris - -Will result in: - - !php - $_GET = [ "a" => 1, "id" => 2 ]; - - $_POST = [ "b" => 3, "city" => 'paris' ]; - - $_REQUEST = [ "a" => 1, "id" => 2, "b" => 3, "city" => 'paris' ]; - - $_SERVER['QUERY_STRING'] = "a=1&id=2"; - - $HTTP_RAW_POST_DATA = "b=3&city=paris"; - - -**Important:** **never trust user input**, **never!** - ---- - -# REST - ---- - -# REpresentational State Transfer - -REST is the underlying architectural principle of the web, formalized as a set -of **constraints**, described in Roy Fielding's dissertation. - -An API (i.e. a web service) that adheres to the principles of REST does not -require the client to know anything about the structure of this API. -Rather, the server needs to provide whatever information the client needs to -interact with the service. - -The key abstraction of information in REST is a **resource**. Any information -that can be named can be a resource, and is identified by a **Unified Resource -Identifier** (URI). - -
-

It heavily relies on the HTTP protocol: RFC 2616.

-
- ---- - -# Richardson Maturity Model - -
-
-![](../images/rmm.png) - ---- - -# Level 0 - The Swamp of POX - -### In A Nutshell - -* HTTP as a tunneling mechanism; -* RPC style system (SOAP, XML-RPC). - -
-
-![](../images/rmm0.png) - ---- - -# Level 1 - Resources - -### In A Nutshell - -* Individual resources (URIs); -* Notion of object identity. - -
-
-![](../images/rmm1.png) - ---- - -# Level 2 - HTTP Verbs - -### In A Nutshell - -* Client uses specific HTTP verbs; -* Server uses HTTP status codes. - -
-
-![](../images/rmm2.png) - ---- - -# Level 3 - Hypermedia Controls - -### In A Nutshell - -* Service discovery via link relations -* Hypermedia formats - -
-![](../images/rmm3.png) - ---- - -# Level 3 = Content Negotiation + HATEOAS - ---- - -# Media Types - -### In A Nutshell - -* Identifies a representation format; -* Custom types use `application/vnd.[XYZ]`; -* Used inside the `Accept` / `Content-Type` headers. - - - - - - - - - - - - -
HeaderDescription
`Content-Type`HTTP message format
`Accept`HTTP response format preference
- -### Hyper Media Types - -Hyper Media Types are MIME media types that contain **native hyper-linking -semantics** that induce application flow: `application/hal+json`, -`application/collection+json`, etc. - ---- - -# Content Type Negotiation - -Content Type Negotiation is the principle of finding appropriate response -formats based on client requirements. - -No standardized algorithm available, even if the Apache -[mod_negotiation](http://httpd.apache.org/docs/2.4/content-negotiation.html) -algorithm is documented. This also covers encoding (`Accept-Encoding`) and -language (`Accept-Language`) negotiation. - - !text - Accept: application/json, application/xml;q=0.9, text/html;q=0.8, - text/*;q=0.7, */*;q=0.5 - - - - - - - - - - - - - -
PriorityMime Type
`q=1.0``application/json`
`q=0.9``application/xml`
`q=0.8``text/html`
`q=0.7``text/*` (ie. any text)
`q=0.5``*/*` (ie. any media type)
- ---- - -# HATEOAS - -**HATEOAS** stands for **H**ypermedia **A**s **T**he **E**ngine **O**f -**A**pplication **S**tate. It means that hypertext should be used to find your -way through the API. - -It is all about **state transitions**. Your application is just a big **state -machine**. -There should be a single endpoint for the resource, and **all of the other -actions** you would need to undertake **should be able to be discovered by -inspecting that resource**. - - !xml - - - - - - - - - - -> Must read: [Haters gonna -HATEOAS](http://timelessrepo.com/haters-gonna-hateoas). diff --git a/src/common/05_autoloading.md b/src/common/05_autoloading.md deleted file mode 100644 index ed3d81c..0000000 --- a/src/common/05_autoloading.md +++ /dev/null @@ -1,215 +0,0 @@ -# PHP Autoloading - ---- - -# Why Is It Necessary? - -PHP won't magically load your classes by itself. - -You have to manually include the class declaration: - - !php - class Octopus - { - public function scream() - { - echo "o o O O ° °"; - } - } - -If you want to create a new `Octopus`, you will write the following code: - - !php - $paul = new Octopus(); - $paul->scream(); - -As the class declaration isn't included, PHP raises a `Fatal Error`: - - !bash - Fatal error: Class 'Octopus' not found in /path/to/file.php - ---- - -# The `require()` Way - -Include the class definition before instantiating it: - - !php - require __DIR__ . '../Model/Octopus.php'; - - $paul = new Octopus(); - $paul->scream(); - -It works! - -But, what happens when the class is included again, somewhere else? - - !php - // somewhere further in your application - require __DIR__ . '../Model/Octopus.php'; - - class Squid extends Octopus - { - } - -PHP raises a `Fatal Error`: - - !bash - Fatal error: Cannot redeclare class Octopus in /path/to/file.php - ---- - -# The `require_once()` Way - -The `require_once()` function is identical to `require()` except that PHP will -check whether the file has already been included: - - !php - require_once __DIR__ . '../Model/Octopus.php'; - - $paul = new Octopus(); - $paul->scream(); - -And somewhere else: - - !php - // somewhere further in your application - require_once __DIR__ . '../Model/Octopus.php'; - - class Squid extends Octopus - { - } - -It just works! - ---- - -# Working With Multiple Files - -Multiple `require_once()` can turn into a nightmare when you deal with more than -a few files: - - !php - Yes - Go on - - => No - Do you have registered autoload functions? - => Yes - Call each function with 'Foo' as parameter - until the class gets included - - => No - Is there a `__autoload()` method? - => Yes - Call `__autoload('Foo')` - - 2. Does the 'Foo' class exist? - => Yes - Continue - => No - Fatal Error - ---- - -# PSR-0 vs PSR-4 - - -### PSR-0 - - !php - \Zend\Mail\Message - // => /path/to/project/lib/vendor/Zend/Mail/Message.php - -

- - !php - Zend_Mail_Message - // => /path/to/project/lib/vendor/Zend/Mail/Message.php - -**Important:** as of 2014-10-21 PSR-0 has been marked as deprecated. - -### PSR-4 - -Like [PSR-0](http://www.php-fig.org/psr/psr-0/), but **better**: - -* more concise folder structure; -* remove the remnants of PSR-0 (e.g. PEAR support). diff --git a/src/common/06_leveraging_php_apis.md b/src/common/06_leveraging_php_apis.md deleted file mode 100644 index 6c1533c..0000000 --- a/src/common/06_leveraging_php_apis.md +++ /dev/null @@ -1,246 +0,0 @@ -# Leveraging PHP APIs - ---- - -# Built-in Interfaces - -### ArrayAccess - -Access properties as an array: - - !php - $tom = new MyObject(); - $tom['name'] = 'Tom'; - -### Serializable, JsonSerializable - -Allow the use of `serialize()` and `unserialize()`. -Objects implementing `JsonSerializable` can customize their JSON representation -when encoded with `json_encode()`. - -### Traversable - -Allow the use of `foreach`. - -> Read more: -[http://php.net/manual/en/reserved.interfaces.php](http://php.net/manual/en/reserved.interfaces.php). - ---- - -# The Reflection API (1/2) - -Enable code introspection: - - !php - /** A comment */ - class MyClass - { - public function hello() { printf("Hello %s", $this->getName()); } - - protected function getName() { return 'foo'; } - } - - $reflClass = new ReflectionClass('MyClass'); - - // access comments - var_dump($reflClass->getDocComment()); - // string(16) "/** A comment */" - - // get all methods - $reflClass->getMethods(); - - // get all public methods - $reflClass->getMethods(ReflectionMethod::IS_PUBLIC); - - ---- - -# The Reflection API (2/2) - -It is even possible to invoke private methods! - - !php - class MyClass - { - public function hello() { printf("Hello %s", $this->getName()); } - - private function getName() { return 'foo'; } - } - - $reflClass = new ReflectionClass('MyClass'); - - // access private method - $method = $reflClass->getMethod('getName'); - $method->setAccessible(true); - - $method->invoke(new MyClass()); - // 'foo' - - -> Read more: -[http://php.net/manual/en/book.reflection.php](http://php.net/manual/en/book.reflection.php). - ---- - -# The Standard PHP Library (SPL) - -Provides a collection of classes and interfaces: - -### Datastructures - -`SplStack`, `SplQueue`, -[`SplObjectStorage`](http://php.net/manual/en/class.splobjectstorage.php), etc. - -### Named Exceptions - -`LogicException`, `InvalidArgumentException`, `OutOfRangeException`, -`RuntimeException`, etc. - -### SPL Functions - -`class_parents()`, `spl_autoload_register()`, `spl_autoload_unregister()`, etc. - -> Read more about the **SPL**: -[http://php.net/manual/en/book.spl.php](http://php.net/manual/en/book.spl.php). - ---- - -# Observer Pattern (1/2) - -The `SplObserver` interface is used alongside `SplSubject` to implement the -**Observer** Design Pattern. - - !php - class Subject implements SplSubject - { - /* ... */ - } - - class Observer implements SplObserver - { - /* ... */ - } - - $subject = new Subject(); - - $observer1 = new Observer(); - - $subject->attach($observer1); - - $subject->notify(); - -> Read more: [http://php.net/manual/en/class.splobserver.php](http://php.net/manual/en/class.splobserver.php). - ---- - -# Observer Pattern (2/2) - -Those interfaces are **never** used as there is only one default **channel** for -the `notify()` method. - -Symfony2 [EventDispatcher](https://github.com/symfony/EventDispatcher) -component to the rescue! - - !php - use Symfony\Component\EventDispatcher\EventDispatcher; - use Symfony\Component\EventDispatcher\Event; - - $dispatcher = new EventDispatcher(); - - $dispatcher->addListener('event_name', function (Event $event) { - // ... - }); - - $dispatcher->dispatch('event_name'); - -> Read more: -[http://symfony.com/doc/master/components/event_dispatcher/](http://symfony.com/doc/master/components/event_dispatcher/index.html). - ---- - -# Exceptions (1/2) - -`try`-`catch` block with multiple `catch` statements: - - !php - try { - // ... - } catch (RuntimeException $e) { - // do something - } catch (Exception $e) { - // do something else - } - -Create your own exceptions: - - !php - class SomethingWentWrong extends RuntimeException - { - } - - class ErrorWhileDoingSomething extends Exception implements ExceptionInterface - { - } - -**Name** your named exceptions! - ---- - -# Exceptions (2/2) - -`try`-`catch` blocks also supports a `finally` block for code that should be run -regardless of whether an exception has been thrown or not: - - !php - try { - // .. - } catch (Exception $e) { - // do something - } finally { - // the code here will always be executed - } - ---- - -# Password Hashing - -The password hashing API provides an easy to use wrapper around `crypt()` to -make it easy to create and manage passwords in a secure manner, since PHP 5.5.0. - -`password_hash()` and `password_verify()` are your new friends! - - !php - $passwordHash = password_hash('secret-password', PASSWORD_DEFAULT); - - if (password_verify('bad-password', $passwordHash)) { - // Correct Password - } else { - // Wrong password - } - -> Read more about the **Password Hashing API**: -[http://php.net/manual/en/book.password.php](http://php.net/manual/en/book.password.php). - -

- -> A **userland implementation** exists for PHP >= 5.3.7: -[password_compat](https://github.com/ircmaxell/password_compat). - ---- - -# PHP Archive (PHAR) - -The phar extension provides a way to put entire PHP applications into -a single file called a "phar" (PHP Archive) for easy distribution and -installation. - -But, the API is **hard** to use. - -Solution? [Box](http://box-project.org/), a command line for simplifying -the PHAR creation process. - -> Read more about **PHAR**: -> -> * [http://php.net/manual/en/intro.phar.php](http://php.net/manual/en/intro.phar.php); -> * [http://blog.pascal-martin.fr/post/php-5.3-phar-php-archive](http://blog.pascal-martin.fr/post/php-5.3-phar-php-archive) -> * [https://mwop.net/blog/2015-12-14-secure-phar-automation.html](https://mwop.net/blog/2015-12-14-secure-phar-automation.html). diff --git a/src/common/07_dependency_management_in_php.md b/src/common/07_dependency_management_in_php.md deleted file mode 100644 index 092885f..0000000 --- a/src/common/07_dependency_management_in_php.md +++ /dev/null @@ -1,70 +0,0 @@ -# Dependency Management - ---- - -# Composer - -There are a ton of PHP libraries, frameworks, and components to choose from. -Most of them have different versions, and don't always work well together. - -**Composer** is a tool for dependency management in PHP. It allows you to declare -the dependent libraries your project needs and it will install them in your -project for you. - -A lot of [awesome PHP libraries](https://github.com/ziadoz/awesome-php) are -compatible with Composer and listed on [Packagist](http://packagist.org/), the -official repository for Composer-compatible PHP libraries. - - !bash - $ curl -sS https://getcomposer.org/installer | php - -This will download `composer.phar` (a PHP binary archive). - -> [http://getcomposer.org/doc/00-intro.md](http://getcomposer.org/doc/00-intro.md) - ---- - -# `composer install` - -Create a `composer.json` file in your project's root directory: - - !javascript - { - "require": { - "willdurand/geocoder": "~2.0" - } - } - -You can also require a library by using the `require` command: - - !bash - $ php composer.phar require willdurand/geocoder - -Run the following command to download and install the project dependencies into -a `vendor` directory: - - !bash - $ php composer.phar install - -> [Composer Version -Constraints](https://igor.io/2013/01/07/composer-versioning.html) - ---- - -# Composer Autoloader - -Composer automatically generates a [PSR-4](http://www.php-fig.org/psr/psr-4/) -compliant and optimized autoloader for your entire application. Thanks to -Composer, you don't have to take care about how to autoload classes/functions -anymore. - -Require the generated autoloader in your project as follows: - - !php - Must read: [Composer Primer](http://daylerees.com/composer-primer). diff --git a/src/common/08_model_view_controller.md b/src/common/08_model_view_controller.md deleted file mode 100644 index 548652f..0000000 --- a/src/common/08_model_view_controller.md +++ /dev/null @@ -1,183 +0,0 @@ -# Model View Controller ---- - -# MVC Overview - -Typical client request process in MVC architecture: - -![](../images/mvc.png) - ---- - -# The Model - -**Model** is the layer in charge of data interaction. - -All **data related business logic** is embedded here. -Using it should not require to understand internals. - -Examples: - -* Manipulate **database** records; -* Communicate with **search engine**; -* **API** calls; -* etc. - -
- -

More on this next week!

-
- -
- -

More on this in a few minutes!

-
- ---- - -# The View - -PHP is a templating language per se. - -**Never**, **ever**, **ever** mix HTML and PHP codes or kittens -will die: you have to separate the presentation from the business logic. - - !php - class PhpTemplateEngine implements TemplateEngine - { - private $templateDir; - - public function __construct($templateDir) - { - $this->templateDir = $templateDir; - } - - public function render($template, array $parameters = []) - { - extract($parameters); - - ob_start(); - include $this->templateDir . DIRECTORY_SEPARATOR . $template; - - return ob_get_clean(); - } - } - ---- - -# The View - -### Template - - !html - -

Hello, !

- -Even better with PHP 5.4+: - - !html -

Hello, !

- - -### Usage - - !php - $engine = new PhpTemplateEngine('/path/to/templates'); - - echo $engine->render('my_template.html', [ - 'name' => 'World', - ]); - =>

Hello, World!

- ---- - -# The View - -**Twig** is a modern template engine for PHP. It takes care of escaping for -you and much much more! Read more: -[http://twig.sensiolabs.org/](http://twig.sensiolabs.org/). - -### Template - - !html+django - {# my_template.html #} -

Hello, {{ name }}!

- - -### Usage - - !php - $loader = new Twig_Loader_Filesystem('/path/to/templates'); - $engine = new Twig_Environment($loader, [ - 'cache' => '/path/to/compilation_cache', - ]); - - echo $engine->render('my_template.html', [ - 'name' => 'World', - ]); - =>

Hello, World!

- ---- - -# The Controller - -**Glue** between the **Model** and the **View** layers. - -It **should not** contain any business logic. - - !php - class BananaController - { - public function __construct( - BananaRepository $repository, - TemplateEngine $engine - ) { - $this->repository = $repository; - $this->engine = $engine; - } - - public function listAction() - { - $bananas = $this->repository->findAll(); - - return $this->engine->render('list.html', [ - 'bananas' => $bananas, - ]); - } - } - ---- - -# Routing - -Routing is the process of binding `URI`s to controllers. - -## Folder organization - -The simplest kind of routing, but also the hardest one to maintain: - - !text - web/ - ├ trees/ - │ └ pineapple.php - └ tree.php - -## Centralized Declaration - -Modern frameworks provide a routing component such as the **Symfony2 Routing** -component allowing to define routes in a centralized place, and easing `URI` -generation. - -This require a single entry point: the **Front Controller**. - ---- - -# Front Controller Pattern - -A controller that handles all requests for a web application: - -![](../images/front-controller.png) - -This controller dispatches the request to the **specialized controllers**. - -It is usually tied to URL rewriting. diff --git a/src/common/09_databases.md b/src/common/09_databases.md deleted file mode 100644 index f72d16d..0000000 --- a/src/common/09_databases.md +++ /dev/null @@ -1,865 +0,0 @@ -# Databases - ---- - -# Agenda - -* Database Design Patterns -* Data Access Layer -* Object Relational Mapping -* Existing Components -* A Note About Domain-Driven Design - ---- - -# Quick note - -In our context, a **database** is seen as a server hosting: - -* a set of `records`; -* organised through `tables` or `collections`; -* grouped by `databases`. - ---- - -# Database Design Patterns - -* Row Data Gateway -* Table Data Gateway -* Active Record -* Data Mapper -* Identity Map -* etc. - -Definitions and figures are part of the [Catalog of Patterns of Enterprise -Application Architecture](http://martinfowler.com/eaaCatalog/index.html) -created by **Martin Fowler**. - -Don't forget his name! Read his books! - ---- - -# Row Data Gateway ![](../images/row-data-gateway.png) - ---- - -# Row Data Gateway - -An object that acts as a Gateway to a single record (row) in a database. -There is one instance per row. - - !php - // This is the implementation of `BananaGateway` - class Banana - { - private $id; - - private $name; - - public function getId() - { - return $this->id; - } - - public function getName() - { - return $this->name; - } - - public function setName($name) - { - $this->name = $name; - } - } - ---- - -# Row Data Gateway - -### Usage - - !php - $con = new Connection('...'); - - $banana = new Banana(); - $banana->setName('Super Banana'); - - // Save the banana - $banana->insert($con); - - // Update it - $banana->setName('New name for my banana'); - $banana->update($con); - - // Delete it - $banana->delete($con); - ---- - -# Row Data Gateway - -### Under the hood - - !php - public function insert(Connection $con) - { - // Prepared statement - $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)'); - - $stmt->bindValue(':name', $name); - - $stmt->execute(); - - // Set the id for this banana - // - // It becomes easy to know whether the banana is new or not, - // you just need to check if id is defined. - $this->id = $this->con->lastInsertId(); - } - ---- - -# Table Data Gateway ![](../images/table-data-gateway.png) - ---- - -# Table Data Gateway - -An object that acts as a _Gateway_ to a database table. -One instance handles all the rows in the table. - -It's a **D**ata **A**ccess **O**bject. - - !php - $table = new BananaGateway(new Connection('...')); - - // Insert a new record - $id = $table->insert('My favorite banana'); - - // Update it - $table->update($id, 'THE banana'); - - // Delete it - $table->delete($id); - - -### CRUD - -A DAO implements the well-known **C**reate **R**ead **U**pdate -**D**elete methods. - -**R**ead should not be a single method: **Finders** to the rescue! - ---- - -# Table Data Gateway - -### Implementation - - !php - class BananaGateway - { - private $con; - - public function __construct(Connection $con) - { - $this->con = $con; - } - - public function insert($name) {} - - public function update($id, $name) {} - - public function delete($id); - } - ---- - -# Table Data Gateway - -### The insert method - - !php - /** - * @param string $name The name of the banana you want to create - * - * @return int The id of the banana - */ - public function insert($name) - { - // Prepared statement - $stmt = $this->con->prepare('INSERT INTO bananas VALUES (:name)'); - - $stmt->bindValue(':name', $name); - - $stmt->execute(); - - return $this->con->lastInsertId(); - } - ---- - -# Table Data Gateway - -### The update method - - !php - /** - * @param int $id The id of the banana to update - * @param string $name The new name of the banana - * - * @return bool Returns `true` on success, `false` otherwise - */ - public function update($id, $name) - { - $stmt = $this->con->prepare(<<bindValue(':id', $id); - $stmt->bindValue(':name', $name); - - return $stmt->execute(); - } - ---- - -# Table Data Gateway - -### The delete method - - !php - /** - * @param int $id The id of the banana to delete - * - * @return bool Returns `true` on success, `false` otherwise - */ - public function delete($id) - { - $stmt = $this->con->prepare('DELETE FROM bananas WHERE id = :id'); - - $stmt->bindValue(':id', $id); - - return $stmt->execute(); - } - ---- - -# Table Data Gateway - -### Finders - - !php - // Retrieve all bananas - $bananas = $table->findAll(); - - // Find bananas by name matching 'THE %' - $bananas = $table->findByName('THE %'); - - // Retrieve a given banana using its id - $banana = $table->find(123); - - // Find one banana by name matching 'THE %' - $banana = $table->findOneByName('THE %'); - -> Use the `__call()` magic method to create magic finders: -[http://www.php.net/manual/en/language.oop5.overloading.php#object.call](http://www.php.net/manual/en/language.oop5.overloading.php#object.call). - ---- - -# Active Record ![](../images/active-record.png) - ---- - -# Active Record - -An object that wraps a row in a database table, encapsulates -the database access, and adds domain logic on that data. - -## Active Record = Row Data Gateway + Domain Logic - - !php - $con = new Connection('...'); - - $banana = new Banana(); - $banana->setName('Another banana'); - $banana->save($con); - - // Call a method that is part of the domain logic - // What can a banana do anyway? - $banana->grow(); - - // Smart `save()` method - // use `isNew()` under the hood - $banana->save($con); - ---- - -# Active Record - - !php - class Banana - { - private $height = 1; - - public function grow() - { - $this->height++; - } - - public function save(Connection $con) - { - if ($this->isNew()) { - // issue an INSERT query - } else { - // issue an UPDATE query - } - } - - public function isNew() - { - // Yoda style - return null === $this->id; - } - } - ---- - -# Data Mapper ![](../images/data-mapper.png) - ---- - -# Data Mapper - -A layer of Mappers that moves data between objects and a database -while keeping them independent of each other and the mapper itself. - -Sort of _"Man in the Middle"_. - - !php - class BananaMapper - { - private $con; - - public function __construct(Connection $con) - { - $this->con = $con; - } - - public function persist(Banana $banana) - { - // code to save the banana - } - - public function remove(Banana $banana) - { - // code to delete the banana - } - } - ---- - -# Data Mapper - -### Usage - - !php - $banana = new Banana(); - $banana->setName('Fantastic Banana'); - - $con = new Connection('...'); - $mapper = new BananaMapper($con); - -### Persist = Save or Update - - !php - $mapper->persist($banana); - -### Remove - - !php - $mapper->remove($banana); - ---- - -# Identity Map ![](../images/identity-map.png) - ---- - -# Identity Map - -Ensures that each object gets loaded only once by keeping every loaded object in -a map. Looks up objects using the map when referring to them. - - !php - class Finder - { - private $identityMap = []; - - public function find($id) - { - if (!isset($this->identityMap[$id])) { - // fetch the object for the given id - $this->identityMap[$id] = ...; - } - - return $this->identityMap[$id]; - } - } - ---- - -# Data Access Layer - ---- - -# Data Access Layer / Data Source Name - -A **D**ata **A**ccess **L**ayer (DAL) is a standard API to manipulate data, -no matter which database server is used. -A **D**ata **S**ource **N**ame (DSN) can be used to determine which database -vendor you are using. - -### PHP Data Object (PDO) - -A DSN in PHP looks like: `:host=;dbname=` where: - -* `` can be: `mysql`, `sqlite`, `pgsql`, etc; -* `` is the IP address of the database server (e.g. `localhost`); -* `` is your database name. - -> [http://www.php.net/manual/en/intro.pdo.php](http://www.php.net/manual/en/intro.pdo.php) - ---- - -# Data Access Layer - -### PDO usage - - !php - $dsn = 'mysql:host=localhost;dbname=test'; - - $con = new PDO($dsn, $user, $password); - - // Prepared statement - $stmt = $con->prepare($query); - $stmt->execute(); - -Looks like the `Connection` class you used before, right? - - !php - class Connection extends PDO - { - } - -### Usage - - !php - $con = new Connection($dsn, $user, $password); - ---- - -# Data Access Layer - -### Refactoring - -Refactoring is a disciplined technique for restructuring an existing -body of code, altering its internal structure without changing its -external behavior. - - !php - class Connection extends PDO - { - /** - * @param string $query - * @param array $parameters - * - * @return bool Returns `true` on success, `false` otherwise - */ - public function executeQuery($query, array $parameters = []) - { - $stmt = $this->prepare($query); - - foreach ($parameters as $name => $value) { - $stmt->bindValue(':' . $name, $value); - } - - return $stmt->execute(); - } - } - ---- - -# Data Access Layer - -### Usage - - !php - /** - * @param int $id The id of the banana to update - * @param string $name The new name of the banana - * - * @return bool Returns `true` on success, `false` otherwise - */ - public function update($id, $name) - { - $query = 'UPDATE bananas SET name = :name WHERE id = :id'; - - return $this->con->executeQuery($query, [ - 'id' => $id, - 'name' => $name, - ]); - } - ---- - -# Object Relational Mapping - ---- - -# Object Relational Mapping (1/4) - -Introduces the notion of **relations** between objects: - -* One-To-One; -* One-To-Many; -* Many-To-Many. - -An **ORM** is often considered as a _tool_ that implements some design patterns -seen above, and that eases relationships between objects. - ---- - -# Object Relational Mapping (2/4) - -### One-To-One (1-1) - -![](../images/one-to-one.png) - -### Code Snippet - - !php - $profile = $banana->getProfile(); - -.fx: no-border - ---- - -# Object Relational Mapping (3/4) - -### One-To-Many (1-N) - -![](../images/one-to-many.png) - -### Code Snippet - - !php - $bananas = $bananaTree->getBananas(); - -.fx: no-border - ---- - -# Object Relational Mapping (4/4) - -### Many-To-Many (N-N) - -![](../images/many-to-many.png) - -### Code Snippet - - !php - $roles = []; - foreach ($banana->getBananaRoles() as $bananaRole) { - $roles[] = $bananaRole->getRole(); - } - - // Or, better: - $roles = $banana->getRoles(); - -.fx: no-border - ---- - -# Existing Components - -### Propel ORM - -An ORM that implements the **Table Data Gateway** and **Row Data Gateway** -patterns, often seen as an **Active Record** approach. - -> Documentation: [www.propelorm.org](http://www.propelorm.org). - -### Doctrine2 ORM - -An ORM that implements the **Data Mapper** pattern. - -> Documentation: [www.doctrine-project.org](http://www.doctrine-project.org/). - ---- - -# A Note About
Domain-Driven Design - ---- - -# Entities - -An object defined primarily by its identity is called an **entity**: - - !php - class Customer - { - private $id; - - private $name; - - public function __construct($id, Name $name) - { - $this->id = $id; - $this->name = $name; - } - - public function getId() - { - return $this->id; - } - - public function getName() - { - return $this->name; - } - } - ---- - -# Value Objects - -An object that represents a descriptive aspect of the domain with no conceptual -identity is called a **Value Object**: - - !php - class Name - { - private $firstName; - - private $lastName; - - public function __construct($firstName, $lastName) - { - $this->firstName = $firstName; - $this->lastName = $lastName; - } - - public function getFirstName() - { - return $this->firstName; - } - - public function getLastName() - { - return $this->lastName; - } - } - ---- - -# The Repository Pattern ![](../images/repository.gif) - ---- - -# The Repository Pattern - -A Repository **mediates between the domain and data mapping layers**, acting -like an **in-memory domain object collection**. - - !php - interface CustomerRepository - { - /** - * @return Customer - */ - public function find($customerId); - - /** - * @return Customer[] - */ - public function findAll(); - - public function add(Customer $user); - - public function remove(Customer $user); - } - ---- - -# The Repository Pattern - -Client objects construct **query specifications** declaratively and submit them -to Repository for satisfaction. - -**Objects can be added to and removed** from the Repository, **as they can from -a simple collection of objects**, and the mapping code encapsulated by the -Repository will carry out the appropriate operations behind the scenes. - -Conceptually, a Repository encapsulates the set of objects persisted in a data -store and the operations performed over them, providing a more object-oriented -view of the persistence layer. - -Repository also supports the objective of achieving a **clean separation and -one-way dependency between the domain and data mapping layers**. - ---- - -# The Specification Pattern ![](../images/specification.png) - ---- - -# The Specification Pattern - -The Specification pattern is a way to model business rules as individual -objects. The idea is that a question about an object, is answered by a -`isSatisfiedBy()` method: - - !php - interface CustomerSpecification - { - /** - * @return boolean - */ - public function isSatisfiedBy(Customer $customer); - } - -

- - !php - class CustomerIsPremium implements CustomerSpecification - { - /** - * {@inheritDoc} - */ - public function isSatisfiedBy(Customer $customer) - { - // figure out if the customer is indeed premium, - // and return true or false. - } - } - ---- - -# Repository ♥ Specification - -A `findSatisfying()` method can be added to the `CustomerRepository`: - - !php - interface CustomerRepository - { - ... - - /** - * @return Customer[] - */ - public function findSatisfying(CustomerSpecification $specification); - } - -### Usage - - !php - $specification = new CustomerIsPremium(); - $customers = $repository->findSatisfying($specification); - ---- - -# Combine Them! - - !php - class OrSpecification implements CustomerSpecification - { - public function __construct( - CustomerSpecification $s1, - CustomerSpecification $s2 - ) { - $this->s1 = $s1; - $this->s2 = $s2; - } - - public function isSatisfiedBy(Customer $c) - { - return $this->s1->isSatisfiedBy($c) || $this->s2->isSatisfiedBy($c); - } - } - -

- - !php - class AndSpecification implements CustomerSpecification - { - ... - - public function isSatisfiedBy(Customer $c) - { - return $this->s1->isSatisfiedBy($c) && $this->s2->isSatisfiedBy($c); - } - } - ---- - -# Combine Them! - - !php - class NotSpecification implements CustomerSpecification - { - public function __construct(CustomerSpecification $s) - { - $this->s = $s; - } - - public function isSatisfiedBy(Customer $c) - { - return !$this->s->isSatisfiedBy($c); - } - } - -### Usage - - !php - // Find customers who have ordered exactly three times, - // but who are not premium customers (yet?) - $specification = new AndSpecification( - new CustomerHasOrderedThreeTimes(), - new NotSpecification( - new CustomerIsPremium() - ) - ); - - $customers = $repository->findSatisfying($specification); - ---- - -# Specification For Business Rules - -Reuse your specifications in your business layer: - - !php - class AwesomeOfferSender - { - private $specification; - - public function __construct(CustomerIsPremium $specification) - { - $this->specification = $specification; - } - - public function sendOffersTo(Customer $customer) - { - if ($this->specification->isSatisfiedBy($customer)) { - // send offers - } - } - } - ---- - -# Checkout: [RulerZ](https://github.com/K-Phoen/rulerz) diff --git a/src/common/10_sessions.md b/src/common/10_sessions.md deleted file mode 100644 index c13a82d..0000000 --- a/src/common/10_sessions.md +++ /dev/null @@ -1,90 +0,0 @@ -# Sessions - ---- - -# Overview - -Sessions are a way to preserve certain data across subsequent accesses. - -### In A Nutshell - -* Unique identifier (session id); -* Server side; -* Easy to use; -* Built-in. - -### A few use cases: - -* Keeping user authentication and roles; -* Storing items into a cart; -* Storing a flash message between redirections. - ---- - -# Code Please - - !php - // Initalize session - session_start(); - - // Regenerate identifier - session_regenerate_id(); - - // Assign "key" to `$value` - $_SESSION['key'] = $value; - - // Retrieve "key" - $_SESSION['key']; - - // Terminate session - session_destroy(); - - -> Session should be started before headers are sent! -> [http://php.net/manual/en/book.session.php](http://php.net/manual/en/book.session.php). - ---- - -# Security Concerns - -* Prediction (guessing a valid session identifier); -* Man in the Middle (capturing a valid session identifier); -* Session Fixation (attacker chooses the session identifier); -* Session Hijacking (all attacks that attempt to gain access to another user's - session). - -### Workarounds - -* Regenerate ids when authentication changes; -* Bind sessions to IP addresses; -* Define expiration/timeout; -* Don't rely on the default settings; -* Use HTTPS. - ---- - -# Session Configuration - -An example of PHP session configuration that is more secure: - - !ini - ; Helps mitigate XSS by telling the browser not to expose the cookie to - ; client side scripting such as JavaScript - session.cookie_httponly = 1 - - ; Prevents session fixation by making sure that PHP only uses cookies for - ; sessions and disallow session ID passing as a GET parameter - session.use_only_cookies = 1 - - ; Better entropy source - ; Evades insufficient entropy vulnerabilities - session.entropy_file = "/dev/urandom" - ; `session.entropy_length` might help too! - - ; Smaller exploitation window for XSS/CSRF/Clickjacking... - session.cookie_lifetime = 0 - - ; Ensures session cookies are only sent over secure connections (it requires - ; a valid SSL certificate) - ; Related to OWASP 2013-A6-Sensitive Data Exposure - session.cookie_secure = 1 diff --git a/src/common/11_authentication.md b/src/common/11_authentication.md deleted file mode 100644 index 492dda1..0000000 --- a/src/common/11_authentication.md +++ /dev/null @@ -1,235 +0,0 @@ -# Authentication - ---- - -# What You Have Right Now - -No **Authentication**/**Security Layer**, anyone can access everything. - -
-
-![](../images/client_server_without_auth.png) - ---- - -# The Big Picture - -
-![](../images/client_server_with_auth.png) - ---- - -# The Interceptor Pattern - -The **Security Layer**, as seen before, has to **intercept** the process of -converting a request into a response in order to perform some checks. - -You need a way to hook into this process before invoking the controller: -**Interceptor Pattern** to the rescue! - -The **Interceptor Pattern** allows you to execute some code during the default -application's lifecyle. - -A way to implement this pattern is to use **events**. It is more or less like -the **Observer**/**Observable** pattern. - -### Event Dispatcher - -The application notifies a set of listeners to an event. -The listeners can register themselves to a particular event. -An **Event Dispatcher** manages both the listeners, and the events. - ---- - -# Introducing the Event Dispatcher - -Simple event dispatcher using a **trait**: - - !php - trait EventDispatcherTrait - { - private $events = []; - - public function addListener($name, $callable) - { - $this->events[$name][] = $callable; - } - - public function dispatch($name, array $arguments = []) - { - foreach ($this->events[$name] as $callable) { - call_user_func_array($callable, $arguments); - } - } - } - ---- - -# Using the EventDispatcherTrait - -In order to intercept the process described before, you have to **notify** some -listeners once you enter in the `process()` method by **dispatching** the event: - - !php - class App - { - use EventDispatcherTrait; - - ... - - private function process(Request $request, Route $route) - { - $this->dispatch('process.before', [ $request ]); - - ... - } - } - -The **listeners** have to listen to this event: - - !php - $app->addListener('process.before', function (Request $request) { - // code to execute - }); - ---- - -# The Firewall (1/2) - -Now that you can hook into the application's lifecycle, you can write a basic -but powerful **Firewall**. - -This firewall needs a **whitelist** of unsecured routes (i.e. routes that -don't require the user to be authenticated) associated with their allowed HTTP -methods: - - !php - $allowed = [ - '/login' => [ Request::GET, Request::POST ], - '/locations' => [ Request::GET ], - ]; - -> [Never Blacklist; Only -Whitelist](http://phpsecurity.readthedocs.org/en/latest/Input-Validation.html#never-blacklist-only-whitelist) - ---- - -# The Firewall (2/2) - -The **Firewall** leverages the **session** to determine whether the user is -authenticated or not: - - !php - session_start(); - - if (isset($_SESSION['is_authenticated']) - && true === $_SESSION['is_authenticated']) { - return; - } - -If authentication fails, the server should return a `401` status code. - ---- - -# Implementing The Firewall - - !php - $app->addListener('process.before', function(Request $req) use ($app) { - session_start(); - - $allowed = [ - '/login' => [ Request::GET, Request::POST ], - ]; - - if (isset($_SESSION['is_authenticated']) - && true === $_SESSION['is_authenticated']) { - return; - } - - foreach ($allowed as $pattern => $methods) { - if (preg_match(sprintf('#^%s$#', $pattern), $req->getUri()) - && in_array($req->getMethod(), $methods)) { - return; - } - } - - switch ($req->guessBestFormat()) { - case 'json': - throw new HttpException(401); - } - - return $app->redirect('/login'); - }); - ---- - -# Authentication Mechanism - -
-![](../images/authentication_mechanism.png) - ---- - -# Adding New Routes - - !php - $app->get('/login', function () use ($app) { - return $app->render('login.php'); - }); - - $app->post('/login', function (Request $request) use ($app) { - $user = $request->getParameter('user'); - $pass = $request->getParameter('password'); - - if ('will' === $user && 'will' === $pass) { - $_SESSION['is_authenticated'] = true; - - return $app->redirect('/'); - } - - return $app->render('login.php', [ 'user' => $user ]); - }); - - $app->get('/logout', function (Request $request) use ($app) { - session_destroy(); - - return $app->redirect('/'); - }); - -> [UI Terminology: Logon vs -Login](http://stackoverflow.com/questions/406016/ui-terminology-logon-vs-login). - ---- - -# Stateless Authentication - -Useful for API authentication. - -### OpenID (in stateless mode) - -[http://openid.net/](http://openid.net/) - -### Basic and Digest Access Authentication - -[http://pretty-rfc.herokuapp.com/RFC2617](http://pretty-rfc.herokuapp.com/RFC2617) - -### WSSE Username Token - -[http://www.xml.com/pub/a/2003/12/17/dive.html](http://www.xml.com/pub/a/2003/12/17/dive.html) - ---- - -# Basic Security Thinking - -1. Trust nobody and nothing; -2. Assume a worse-case scenario; -3. Apply Defense-In-Depth; -4. Keep It Simple Stupid (KISS); -5. Principle of Least Privilege; -6. Attackers can smell obscurity; -7. RTFM but never trust it; -8. If it is not tested, it does not work; -9. It is always your fault! - -> [Survive The Deep End: PHP -Security](http://phpsecurity.readthedocs.org/en/latest/index.html) diff --git a/src/extended/00_intro.md b/src/extended/00_intro.md deleted file mode 100644 index c94fa73..0000000 --- a/src/extended/00_intro.md +++ /dev/null @@ -1,18 +0,0 @@ -# A Framework To Simplify Developments - -A **framework helps you work better** by structuring developments, -and **faster** by reusing generic modules. - -A framework **facilitates long-term maintenance** and **scalability** by -complying with standard development rules. - -Compliance with development standards also simplifies integrating and -interfacing the application with the rest of the information system. - -In other words, it works as a tool to make the development process -easier and more productive. - -Most of the time, a framework implements many kinds of **design patterns**. - -> Read more: [Symfony explained to a -> Developer](https://symfony.com/symfony-explained-to-a-developer). diff --git a/src/extended/01_symfony2.md b/src/extended/01_symfony2.md deleted file mode 100644 index a2bb0c9..0000000 --- a/src/extended/01_symfony2.md +++ /dev/null @@ -1,655 +0,0 @@ -# ![](../images/symfony.png) - -.fx: no-border - ---- - -# What Is Symfony? - -First of all: - -
-

Symfony is a reusable set of standalone, decoupled, and cohesive PHP - components that solve common web development problems.

-
- -Then, based on these components: - -
-

Symfony is also a full-stack web framework.

-
- -_Fabien Potencier, -[http://fabien.potencier.org/article/49/what-is-symfony2](http://fabien.potencier.org/article/49/what-is-symfony2)._ - ---- - -# Is Symfony A MVC Framework? - ---- - -# NO! - -.fx: color-red - ---- - -# Why You Should Use Symfony - -Symfony is built on powerful concepts: - -* **Separation of Concerns**; -* **Pragmatism**; -* **Best Practices**. - -![](../images/symfony-github.png) - -It has been written by [~1502 developers](http://symfony.com/contributors/code). - -Open Source, **MIT** licensed. - ---- - -# The Symfony Components - -The Components implement **common features** needed to develop websites. - -They are the **foundation of the Symfony full-stack framework**, but they can -also be used **standalone** even if you don't use the framework as they don't -have any mandatory dependencies. - -There are ~30 components, including: - - !text - BrowserKit EventDispatcher OptionsResolver Templating - ClassLoader ExpressionLanguage Process Translation - Config Filesystem PropertyAccess VarDumper - Console Finder PropertyInfo Yaml - CssSelector Form Routing - Debug HttpFoundation Security - DependencyInjection HttpKernel Serializer - DomCrawler Intl Stopwatch - ---- - -# Getting Ready With Components - -Say you want to play with YAML files, start by requiring the `symfony/yaml` -component into your `composer.json` file: - - !yaml - { - "require": { - "symfony/yaml": "~3.0" - } - } - -Install it by running `php composer.phar install`, and use it: - - !php - require __DIR__ . '/vendor/autoload.php'; - - use Symfony\Component\Yaml\Yaml; - - $yaml = Yaml::parse('/path/to/file.yml'); - -> [http://symfony.com/doc/current/components/yaml/introduction.html](http://symfony.com/doc/current/components/yaml/introduction.html) - ---- - -# Full-Stack Framework - -The **Symfony Framework** accomplishes two distinct tasks: - -* Provides a selection of components; -* Provides sensible configuration and a "glue" library that ties all of these - pieces together. - -The goal of the framework is **to integrate many independent tools** in order to -provide a consistent experience for the developer. Even **the framework itself is -a Symfony bundle** (i.e. a plugin) that can be configured or replaced entirely. - -Symfony **provides a powerful set of tools for rapidly developing web -applications** without imposing on your application. - -> [http://symfony.com/doc/current/book/index.html](http://symfony.com/doc/current/book/index.html) - ---- - -# Overall Architecture - ---- - -# The Symfony Request - - !php - use Symfony\Component\HttpFoundation\Request; - - $request = Request::createFromGlobals(); - - // the URI being requested (e.g. /about) minus any query parameters - $request->getPathInfo(); - - // the HTTP verb - $request->getMethod(); - - // GET variables - $request->query->get('foo'); - - // POST variables - $request->request->get('bar'); - - // SERVER variables - $request->server->get('HTTP_HOST'); - - // retrieve an HTTP request header, with normalized, lowercase keys - $request->headers->get('host'); - ---- - -# The Symfony Response - - !php - use Symfony\Component\HttpFoundation\Response; - - $response = new Response(); - - $response->setContent(<< - -

Hello world!

- - - HTML - ); - - $response->setStatusCode(200); - - $response->headers->set('Content-Type', 'text/html'); - - // prints the HTTP headers followed by the content - $response->send(); - ---- - -# The Simplest Front Controller Ever - - !php - // index.php - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Response; - - $request = Request::createFromGlobals(); - $path = $request->getPathInfo(); - - if (in_array($path, ['', '/'])) { - $response = new Response('Welcome to the homepage.'); - } elseif ('/hello' === $path) { - $response = new Response('hello, World!'); - } else { - $response = new Response('Page not found.', 404); - } - - $response->send(); - ---- - -# The Symfony Application Flow - -It's all about transforming a **Request** into a **Response**: -

- -![](http://symfony.com/doc/current/_images/request-flow.png) - ---- - -# Routing Definition - -The routing system determines which PHP function should be executed based on -information from the request and routing configuration you've created. - - !yaml - # app/config/routing.yml - hello: - path: /hello - defaults: { _controller: AppBundle:Main:hello } - -The `AppBundle:Main:hello` string is a short syntax that points to a -specific PHP method named `helloAction()` inside a class called -`MainController`. - - -
-

- This example uses YAML to define the routing configuration. - Routing configuration can also be written in other formats such as XML or PHP. -

-
- ---- - -# Your First Controller - -In Symfony, a method in a controller is called an **action**. The convention is -to suffix each method with `Action`. - -Also, each controller should be suffixed with `Controller`. - - !php - // src/AppBundle/Controller/MainController.php - namespace AppBundle\Controller; - - use Symfony\Component\HttpFoundation\Response; - - class MainController - { - public function helloAction() - { - return new Response('

Hello, World!

'); - } - } - ---- - -# A Symfony Project (1/2) - -**Recommended** structure of a Symfony (3.x) project: - - !text - path/to/project/ - app/ - config/ - Resources/ - views/ - bin/ - console - src/ - ... - tests/ - ... - var/ - cache/ - logs/ - sessions/ - vendor/ - ... - web/ - app.php - ... - ---- - -# A Symfony Project (2/2) - -Each directory has its own purpose (and set of files): - -* `app/` contains the application kernel, views, and the configuration; -* `src/` contains your **bundles**; -* `tests/` contains your tests; -* `var/` contains files that change often (like in Unix systems); -* `vendor/` contains your dependencies; -* `web/` contains your front controllers and your assets. - ---- - -# Application Kernel - -This is the **central part** of your application: - - !php - // app/AppKernel.php - use Symfony\Component\HttpKernel\Kernel; - - class AppKernel extends Kernel - { - public function registerBundles() - { - $bundles = [ - new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - // ... - ]; - - if (in_array($this->getEnvironment(), ['dev', 'test'])) { - $bundles[] = // dev bundle; - } - - return $bundles; - } - - // ... - } - ---- - -# Application Configuration - -An application consists of a collection of "bundles" representing all of the -features and capabilities of your application. - -Each "bundle" can be customized via configuration files written in `YAML`, `XML` -or `PHP`. - -By default, the main configuration file lives in the `app/config/` -directory and is called either `config.yml`, `config.xml` or `config.php` -depending on which format you prefer. - -Symfony is all about configuring everything, and you can do pretty much -everything you want. That's why people agreed on some conventions, but then -again, a convention is just **A** way to do things, not **THE** way to do them. - ---- - -# YAML Configuration - -### Example: - - !yaml - # app/config/config.yml - imports: - - { resource: parameters.yml } - - { resource: security.yml } - - framework: - secret: '%secret%' - router: { resource: '%kernel.root_dir%/config/routing.yml' } - # ... - - # Twig Configuration - twig: - debug: '%kernel.debug%' - strict_variables: '%kernel.debug%' - - # ... - ---- - -# XML Configuration - -### Example: - - !xml - - - - - - - - - - - - - - - - ---- - -# PHP Configuration - -### Example: - - !php - $this->import('parameters.yml'); - $this->import('security.yml'); - - $container->loadFromExtension('framework', [ - 'secret' => '%secret%', - 'router' => [ - 'resource' => '%kernel.root_dir%/config/routing.php' - ], - // ... - ]); - - // Twig Configuration - $container->loadFromExtension('twig', [ - 'debug' => '%kernel.debug%', - 'strict_variables' => '%kernel.debug%', - ]); - - // ... - ---- - -# The Rules (Well... My Rules) - -The **main configuration** MUST be written in `YAML`: - - !yaml - # app/config/config.yml - # ... - twig: - debug: '%kernel.debug%' - strict_variables: '%kernel.debug%' - -The **routing definition** MUST be written in `YAML`: - - !yaml - # app/config/routing.yml - hello: - path: /hello - defaults: { _controller: AppBundle:Main:hello } - -The **DI Container configuration** MUST be written in `XML`: - - !xml - - - - ---- - -# Environments - -An application can run in various environments. The different environments -**share the same PHP code**, but use different configuration. - -A Symfony project generally uses three environments: `dev`, `test` and `prod`. - - !php - // web/app.php - - // ... - $kernel = new AppKernel('prod', false); - -The `AppKernel` class is responsible for actually loading the configuration file -of your choice: - - !php - // app/AppKernel.php - public function registerContainerConfiguration(LoaderInterface $loader) - { - $loader->load( - __DIR__ . '/config/config_' . $this->getEnvironment() . '.yml' - ); - } - ---- - -# What Is A Bundle? - -_A **Bundle** is a directory containing a set of files (PHP files, stylesheets, -JavaScripts, images, ...) that implement a **single feature** (a blog, a forum, -etc)._ - -It should be **reusable**, so that you don't reinvent the wheel each time you -need a common feature. In Symfony, (almost) everything lives inside a bundle. - -In order to use a bundle in your application, you need to register it in the -`AppKernel`, using the `registerBundles()` method: - - !php - public function registerBundles() - { - $bundles = array( - // ... - - new My\AwesomeBundle\MyAwesomeBundle(), - ); - - // ... - } - ---- - -# Bundle: Directory Structure - -Recommended structure for a bundle: - - !text - XXX/... - DemoBundle/ - DemoBundle.php - Controller/ - Resources/ - config/ - doc/ - index.rst - translations/ - views/ - public/ - Tests/ - LICENSE - -The `DemoBundle` class is mandatory, and both `LICENSE` and -`Resources/doc/index.rst` files should be present. - -The `XXX` directory(ies) reflects the namespace structure of the bundle. - ---- - -# Bundle: Where To Put Your Classes? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TypeDirectory
CommandsCommand/
ControllersController/
Service Container ExtensionsDependencyInjection/
Event ListenersEventListener/
ConfigurationResources/config/
Web ResourcesResources/public/
Translation filesResources/translations/
TemplatesResources/views/
Unit and Functional TestsTests/
- ---- - -# Creating a Bundle - -A **bundle** has to extend the `Symfony\Component\HttpKernel\Bundle\Bundle` -class: - - !php - // src/Acme/MyFirstBundle/AcmeMyFirstBundle.php - namespace Acme\MyFirstBundle; - - use Symfony\Component\HttpKernel\Bundle\Bundle; - - class AcmeMyFirstBundle extends Bundle - { - } - -Then, you can register your bundle: - - !php - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - new Acme\MyFirstBundle\AcmeMyFirstBundle(), - ); - - return $bundles; - } - ---- - -# The Web Directory - -The web root directory is the **home of all public and static files** including -images, stylesheets, and JavaScript files. It is also where each front -controller lives: - - !php - // web/app.php - require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../app/AppKernel.php'; - - use Symfony\Component\HttpFoundation\Request; - - $kernel = new AppKernel('prod', false); - $request = Request::createFromGlobals(); - $response = $kernel->handle($request); - $response->send(); - -The front controller file (`app.php` in this example) is the actual PHP file -that's executed when using a Symfony application and its job is to **use a -Kernel** class, `AppKernel`, to **bootstrap the application**, for a given -**environment**. - ---- - -# Summary - -Creating a page is a three-step process involving a _route_, a _controller_, and -(optionally) a _template_. - -Each project contains just a few main directories: `web/` (web assets and the -front controllers), `app/` (configuration), `src/` (your bundles), and `vendor/` -(third-party code). - -Each feature in Symfony (including the Symfony framework core) is organized -into a **bundle**, which is a structured set of files for that feature. - -The configuration for each bundle lives in the `Resources/config` directory of the -bundle and can be specified in `YAML`, `XML` or `PHP`. - -The global application configuration lives in the `app/config/` directory. - -Each environment is accessible via a different front controller (e.g. `app.php` -and `app_dev.php`) and loads a different configuration file. - ---- - -# Read The [Best Practices](https://symfony.com/doc/current/best_practices/index.html)! diff --git a/src/extended/02_symfony2_controllers.md b/src/extended/02_symfony2_controllers.md deleted file mode 100644 index e7dadd3..0000000 --- a/src/extended/02_symfony2_controllers.md +++ /dev/null @@ -1,313 +0,0 @@ -# Controllers - ---- - -# Request, Controller, Response - -A controller is a **PHP function** you create that takes information from the -**HTTP request** and constructs and returns an **HTTP response**. - -Every request handled by a Symfony project goes through the same lifecycle: - -1. Each request is handled by a single front controller file (e.g. `app.php` or -`app_dev.php`) that bootstraps the application; -2. The **Router** reads information from the request (e.g. the URI), finds a route -that matches that information, and reads the `_controller` parameter from the -route; -3. The **controller** from the matched route is executed and the code inside the -controller creates and returns a `Response` object; -4. The HTTP headers and content of the `Response` object are sent back to the -client. - ---- - -# The Simplest Page Ever - -### Routing Definition - - !yaml - # app/config/routing.yml - homepage: - path: / - defaults: { _controller: AppBundle:Hello:index } - -### Controller Implementation - - !php - // src/AppBundle/Controller/HelloController.php - namespace AppBundle\Controller; - - use Symfony\Component\HttpFoundation\Response; - - class HelloController - { - public function indexAction() - { - return new Response('Home, Sweet Home!'); - } - } - ---- - -# Controller Naming Pattern - -Every route must have a `_controller` parameter, which dictates **which controller -should be executed when that route is matched**. - -This parameter uses a simple string pattern called the **logical controller name**. -The pattern has three parts, each separated by a colon: `bundle:controller:action`. - -For example, a `_controller` value of `AcmeBlogBundle:Blog:show` means: - -* Bundle: `AcmeBlogBundle`; -* Controller Class: `BlogController`; -* Method Name: `showAction`. - -Notice that Symfony adds the string `Controller` to the class name (`Blog` => -`BlogController`) and `Action` to the method name (`show` => `showAction`). - ---- - -# Route Params as Controller Args - -### Routing Definition - - !yaml - # src/AppBundle/Resources/config/routing.yml - app.hello_hello: - path: /hello/{name} - defaults: { _controller: AppBundle:Hello:hello } - requirements: - _method: GET - - -### Controller Implementation - - !php - // src/AppBundle/Controller/HelloController.php - - class HelloController - { - // ... - - public function helloAction($name) - { - return new Response(sprintf('Home, Sweet %s!', $name)); - } - } - ---- - -# The Request as a Controller Argument - -For convenience, you can also have Symfony pass you the Request object as an -argument to your controller: - - !php - use Symfony\Component\HttpFoundation\Request; - - class HelloController - { - // ... - - public function updateAction(Request $request) - { - // do something useful with $request - } - } - -This is useful when you are working with forms. - ---- - -# The Base Controller Class - -Symfony comes with a base `Controller` class that assists with some of the most -common controller tasks and gives your controller class access to any resource -it might need: - - !php - use Symfony\Bundle\FrameworkBundle\Controller\Controller - - class HelloController extends Controller - { - // ... - } - -### Redirecting - - !php - $this->redirect($this->generateUrl('homepage')); - - -### Rendering Templates - - !php - return $this->render( - 'hello/hello.html.twig', array('name' => $name) - ); - ---- - -# The Response - -The only **requirement** for a controller is to return a `Response` object. - -Create a simple `Response` with a `200` status code: - - !php - use Symfony\Component\HttpFoundation\Response; - - $response = new Response('Hello, ' . $name, 200); - -Create a JSON response with a `200` status code: - - !php - $response = new Response(json_encode(array('name' => $name))); - $response->headers->set('Content-Type', 'application/json'); - -Or: - - !php - use Symfony\Component\HttpFoundation\JsonResponse; - - $response = new JsonResponse(array('name' => $name)); - ---- - -# Routing - ---- - -# Basic Route Configuration - -The Symfony router lets you define URLs that you map to different areas of -your application. - -A _route_ is a map from a URL path to a controller. Each route is named, and -maps a `path` to a `_controller`: - - !yaml - # app/config/routing.yml - homepage: - path: / - defaults: { _controller: AppBundle:Hello:index } - -This route matches the homepage (`/`) and maps it to the -`AppBundle:Hello:index` controller. - -> [http://symfony.com/doc/master/book/routing.html](http://symfony.com/doc/master/book/routing.html) - ---- - -# Routing with Placeholders (1/2) - -### Required Placeholders - - !yaml - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index } - -The path will match anything that looks like `/blog/*`. - -Even better, the value matching the `{page}` **placeholder** will be available -inside your controller. - -`/blog` will **not** match. - ---- - -# Routing with Placeholders (2/2) - -### Optional Placeholders - - !yaml - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } - -By adding `page` to the **defaults** key, `{page}` is **no longer required**. - -`/blog` will match this route and the value of the `page` parameter will be -set to `1`. `/blog/2` will also match, giving the `page` parameter a value of `2`. - ---- - -# Requirements - - !yaml - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } - requirements: - page: \d+ - -The `\d+` requirement is a **regular expression** that says that the value of -the `{page}` parameter must be a digit (i.e. a number). - -### HTTP Method Requirements - - !yaml - # src/AppBundle/Resources/config/routing.yml - app.hello_hello: - path: /hello/{name} - defaults: { _controller: AppBundle:Hello:hello } - methods: [ GET ] - # methods: [ GET, POST ] - ---- - -# Including External Routing Resources - -All routes are loaded via a single configuration file, most of the time it will -be `app/config/routing.yml`. - -In order to respect the "bundle" principle, the routing configuration should be -located in the bundle itself, and you should just require it: - - !yaml - # app/config/routing.yml - appa: - resource: '@AppBundle/Resources/config/routing.yml' - - -### Prefixing Imported Routes - - !yaml - # app/config/routing.yml - app: - resource: '@AppBundle/Resources/config/routing.yml' - prefix: /demo - -The string `/demo` now be prepended to the path of each route loaded from -the new routing resource. - ---- - -# Generating URLs - -The `Router` is able to generate both relative and absolute URLs. - - !php - $router = $this->get('router'); - -### Relative URLs - - !php - $router->generate('app.hello_hello', [ 'name' => 'will' ]); - // /hello/will - -### Absolute URLs - - !php - $router->generate('app.hello_hello', [ 'name' => 'will' ], true); - // http://example.com/hello/will - -### Query String - - !php - $router->generate('app.hello_hello', [ - 'name' => 'will', 'some' => 'thing' - ]); - // /hello/will?some=thing diff --git a/src/extended/03_symfony2_templating.md b/src/extended/03_symfony2_templating.md deleted file mode 100644 index 4df8423..0000000 --- a/src/extended/03_symfony2_templating.md +++ /dev/null @@ -1,401 +0,0 @@ -# Templating - ---- - -# ![](../images/twig_homepage.png) - -.fx: no-border - ---- - -# Why Twig? - -**Fast**, **Secure**, **Flexible**. - -#### Before - - !php - - -#### After - - !jinja - - ---- - -# Getting Familiar With Twig - -### Delimiters - -* `{{ ... }}`: prints a **variable** or the result of an expression; -* `{% ... %}`: controls the **logic** of the template; it is used to execute for - loops and if statements, for example; -* `{# ... #}`: **comments**. - -> [http://twig.sensiolabs.org/](http://twig.sensiolabs.org/) - ---- - -# Accessing Variables - - !jinja - {# array('name' => 'Fabien') #} - {{ name }} - - {# array('user' => array('name' => 'Fabien')) #} - {{ user.name }} - - {# force array lookup #} - {{ user['name'] }} - - {# array('user' => new User('Fabien')) #} - {{ user.name }} - {{ user.getName }} - - {# force method name lookup #} - {{ user.name() }} - {{ user.getName() }} - - {# pass arguments to a method #} - {{ user.date('Y-m-d') }} - ---- - -# Control Structure - -### Conditions - - !jinja - {% if user.isSuperAdmin() %} - ... - {% elseif user.isMember() %} - ... - {% else %} - ... - {% endif %} - -### Loops - - !jinja -
    - {% for user in users if user.active %} -
  • {{ user.username }}
  • - {% else %} -
  • No users found
  • - {% endfor %} -
- ---- - -# Filters - -Filters are used to modify Twig variables. - -You can use **inline filters** by using the `|` symbol: - - !jinja - {{ 'hello'|upper }} - -But you can also use the **block syntax**: - - !jinja - {% filter upper %} - hello - {% endfilter %} - -Filters can be parametrized: - - !jinja - {{ post.createdAt|date('Y-m-d') }} - -> [http://twig.sensiolabs.org/doc/filters/index.html](http://twig.sensiolabs.org/doc/filters/index.html) - - ---- - -# Including Other Templates - -The `include` tag is useful to include a template and return the rendered -content of that template into the current one: - - !jinja - {% include 'sidebar.html' %} - -### Example - -Given the following template: - - !jinja - {% for user in users %} - {% include "render_user.html" %} - {% endfor %} - -with `render_user.html`: - - !jinja -

{{ user.username }}

- -

- - !html -

William D.

-

Julien M.

- ---- - -# Template Inheritance (1/2) - -Let's define a base template, `base.html`, which defines a simple HTML skeleton: - - !jinja - {# app/Resources/views/base.html.twig #} - - - - {% block title %}Test Application{% endblock %} - - - - -
- {% block body %}{% endblock %} -
- - - ---- - -# Template Inheritance (2/2) - -The key to template inheritance is the `{% extends %}` tag. - -A **child template** might look like this: - - !jinja - {# app/Resources/views/Blog/index.html.twig #} - {% extends 'base.html.twig' %} - - {% block title %}My cool blog posts{% endblock %} - - {% block body %} - {% for entry in blog_entries %} -

{{ entry.title }}

-

{{ entry.body }}

- {% endfor %} - {% endblock %} - -If you need to get the content of a block from the **parent template**, you can -use the `{{ parent() }}` function. - ---- - -# Template Naming and Locations (1/2) - -By default, templates can live in two different locations: - -* `app/Resources/views/`: The applications views directory can contain - application-wide **base templates** (i.e. your application's layouts), - templates specific to your app as well as **templates that override bundle - templates**; -* `path/to/bundle/Resources/views/`: Each **(public) bundle houses its templates** in its - `Resources/views` directory (and subdirectories). - -Symfony uses a `bundle:controller:template` string syntax for templates. - -You can skip the `controller` string: `bundle::template`. The `template` -file would live in `Resources/views/`. - -You can also skip the `bundle` string. It refers to an application-wide base -template or layout. This means that the template is not located in any bundle, -but instead in the root `app/Resources/views/` directory. - ---- - -# Template Naming and Locations (2/2) - -### Example - - !text - AcmeBlogBundle:Blog:index.html.twig - -* `AcmeBlogBundle`: (bundle) the template lives inside the `AcmeBlogBundle` (e.g. -`src/Acme/BlogBundle`); -* `Blog`: (controller) indicates that the template lives inside the `Blog` -subdirectory of `Resources/views`; -* `index.html.twig`: (template) the actual name of the file is `index.html.twig`. - -Assuming that the `AcmeBlogBundle` lives at `src/Acme/BlogBundle`, the final -path to the layout would be: - - !text - src/Acme/BlogBundle/Resources/views/Blog/index.html.twig - ---- - -# Overriding Bundle Templates - -Once you use a third-party bundle, you'll likely need to override and customize -one or more of its templates. - -When the `FooBarBundle:Bar:index.html.twig` is rendered, Symfony actually -looks in two different locations for the template: - -* `app/Resources/FooBarBundle/views/Bar/index.html.twig`; -* `src/Foo/BarBundle/Resources/views/Bar/index.html.twig`. - -In order to override the bundle template, copy the `index.html.twig` template -from the bundle to: `app/Resources/FooBarBundle/views/Bar/index.html.twig`. - ---- - -# Overriding Core Templates - -The core **TwigBundle** contains a number of different templates that can be -overridden by copying each from the `Resources/views/` directory of the -**TwigBundle** to the `app/Resources/TwigBundle/views/` directory. - ---- - -# Twig Into Symfony - ---- - -# Rendering A Template - -### Using The Base Controller - - !php - public function listAction() - { - // ... - - return $this->render('blog/index.html.twig', array( - 'posts' => $posts, - )); - } - -### Using the Templating Service - - !php - $engine = $this->container->get('templating'); - $content = $engine->render('blog/index.html.twig', array( - 'posts' => $posts, - )); - - return new Response($content); - ---- - -# Linking to Pages - -Assuming the following routing definition: - - !yaml - homepage: - path: / - defaults: { _controller: AppBundle:Hello:index } - - acme_blog.post_show: - path: /posts/{slug} - defaults: { _controller: AcmeBlogBundle:Post:show } - -You can create a **relative URL** using `path()`: - - !jinja - Home - -You can create an **absolute URL** using `url()`: - - !jinja - Home - -The second argument is used to pass parameters: - - !jinja - - ---- - -# Linking to Assets - - !jinja - - - - - Symfony! - -### Cache Busting - -**Cache busting** is the process of **forcing browsers** or proxy servers **to -update their cache**, for instance, JavaScript and CSS files or images. - - !yaml - # app/config/config.yml - framework: - # ... - templating: { engines: ['twig'], assets_version: v2 } - -The `asset_version` parameter is used to **bust the cache on assets** by globally -adding a query parameter to all rendered asset paths: - - !text - /images/logo.png?v2 - ---- - -# Linking To Pages In JavaScript - -The [FOSJsRoutingBundle](https://github.com/FriendsOfSymfony/FOSJsRoutingBundle) -allows you to **expose your routing in your JavaScript code**. That means you'll -be able to generate URL with given parameters like you can do with the _Router_ -component provided by Symfony. - - !yaml - # app/config/routing.yml - my_route_to_expose: - path: /foo/{id}/bar - defaults: { _controller: FooBarBundle:Foo:bar } - options: - expose: true - -According to the routing definition above, you can write the following -JavaScript code to generate URLs: - - !javascript - Routing.generate('my_route_to_expose', { id: 10 }); - // /foo/10/bar - - Routing.generate('my_route_to_expose', { id: 10 }, true); - // http://example.org/foo/10/bar - ---- - -# Global Template Variables - -* `app.security`: the **security** context; -* `app.user`: the **current user** object; -* `app.request`: the **request** object; -* `app.session`: the **session** object; -* `app.environment`: the current environment (`dev`, `prod`, etc); -* `app.debug`: `true` if in debug mode. `false` otherwise. diff --git a/src/extended/04_symfony2_dependency_injection.md b/src/extended/04_symfony2_dependency_injection.md deleted file mode 100644 index d34c611..0000000 --- a/src/extended/04_symfony2_dependency_injection.md +++ /dev/null @@ -1,293 +0,0 @@ -# Service Container - ---- - -# What Is A Service? - -A **Service** is a generic term for any PHP object that performs a specific task. - -A service is usually used **globally**, such as a database connection object or an -object that delivers email messages. - -In Symfony, services are often **configured and retrieved from the service -container**. - -An application that has many decoupled services is said to follow a -**Service-Oriented Architecture** (SOA). - ---- - -# What Is A Service Container? - -A **Service Container**, also known as a **Dependency Injection Container** -(DIC), is a special object that **manages the instantiation of services** inside -an application. - -The service container takes care of **lazily instantiating** and **injecting -dependent services**. - ---- - -# Creating A Service - - !php - class Foo - { - private $bar; - private $debug; - - public function __construct(Bar $bar = null, $debug = false) - { - $this->bar = $bar; - $this->debug = $debug; - } - } - -The **service definition** for the class described above is: - - !xml - - - - -This service is now available in the container, and you can access it by -**asking** the service from the container: - - !php - $foo = $this->container->get('foo'); - ---- - -# Service Parameters - -The service definition described before is not flexible enough. For instance, -`$debug` argument is never configured. - -**Parameters** make defining services more **organized** and **flexible**: - - !xml - - My\Bundle\Foo - - - - - - %kernel.debug% - - - -In the definition above, `kernel.debug` is a parameter defined by the framework -itself. The `foo` service is now **parametrized**. - -Also, it becomes easy to change the implementation of this service by simply -overriding the `my_bundle.foo.class` parameter. - ---- - -# Injecting Services - -As you may noticed, the `Foo` class takes an instance of `Bar` as first -argument. You can **inject** this instance in your `foo` service by -**referencing** the `bar` service: - - !xml - - My\Bundle\Foo - My\Bundle\Bar - - - - - - - - %kernel.debug% - - - -### Optional Dependencies: Setter Injection - - !xml - - - - ---- - -# Importing Configuration Resources - -### The `imports` Way - - !yaml - # app/config/config.yml - imports: - - { resource: "@AcmeDemoBundle/Resources/config/services.xml" } - -### Container Extensions - -A **service container extension** is a PHP class to accomplish two things: - -* **import** all service container resources needed to configure the services for - the bundle; -* **provide semantic**, straightforward **configuration** so that the bundle can - be configured without interacting with the flat parameters of the bundle's - service container configuration. - ---- - -# Creating an Extension Class - -An extension class should live in the `DependencyInjection` directory of your -bundle and its name should be constructed by replacing the `Bundle` suffix of -the Bundle class name with `Extension`. - - !php - // Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php - namespace Acme\DemoBundle\DependencyInjection; - - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; - use Symfony\Component\HttpKernel\DependencyInjection\Extension; - - class AcmeDemoExtension extends Extension - { - public function load(array $configs, ContainerBuilder $container) - { - $loader = new XmlFileLoader($container, new FileLocator( - __DIR__ . '/../Resources/config' - )); - $loader->load('services.xml'); - } - } - ---- - -# Dealing With Configuration (1/2) - -The presence of the previous class means that you can now define an -`acme_demo` configuration namespace in any configuration file: - - !yaml - # app/config/config.yml - acme_demo: ~ - -Take the following configuration: - - !yaml - acme_demo: - foo: fooValue - bar: barValue - -The array passed to your `load()` method will look like this: - - !php - array( - array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - ) - ) - ---- - -# Dealing With Configuration (2/2) - -The `$configs` argument is an **array of arrays**, not just a single flat array -of the configuration values. - -It's your job to decide how these configurations should be merged together. - -You might, for example, have later values override previous values or -somehow merge them together: - - !php - public function load(array $configs, ContainerBuilder $container) - { - $config = array(); - foreach ($configs as $subConfig) { - $config = array_merge($config, $subConfig); - } - - // ... now use the flat $config array - } - - ---- - -# The Configuration Class (1/2) - -### Definition - - !php - // src/Acme/DemoBundle/DependencyInjection/Configuration.php - namespace Acme\DemoBundle\DependencyInjection; - - use Symfony\Component\Config\Definition\Builder\TreeBuilder; - use Symfony\Component\Config\Definition\ConfigurationInterface; - - class Configuration implements ConfigurationInterface - { - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('acme_demo'); - - $rootNode - ->children() - ->scalarNode('my_type')->defaultValue('bar')->end() - ->end(); - - return $treeBuilder; - } - } - ---- - -# The Configuration Class (2/2) - -### Usage - - !php - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - - $config = $this->processConfiguration($configuration, $configs); - - // ... - } - -The `processConfiguration()` method uses the **configuration tree** you've defined -in the `Configuration` class to **validate**, **normalize** and **merge** all of -the configuration arrays together. - -> Read more on How to expose a Semantic Configuration for a Bundle: -[http://symfony.com/doc/master/cookbook/bundles/extension.html](http://symfony.com/doc/master/cookbook/bundles/extension.html). - ---- - -# More On The Service Container - -### Tags - -In the service container, a **tag** implies that the service is meant to be used -for a specific purpose. - - !xml - - - - -Twig finds all services tagged with `twig.extension` and automatically registers -them as extensions. - -### Debugging Services - - !bash - $ php bin/console debug:container - - $ php bin/console debug:container foo - -> [http://symfony.com/doc/master/book/service_container.html](http://symfony.com/doc/master/book/service_container.html) diff --git a/src/extended/05_symfony2_command_line.md b/src/extended/05_symfony2_command_line.md deleted file mode 100644 index ea28e2e..0000000 --- a/src/extended/05_symfony2_command_line.md +++ /dev/null @@ -1,202 +0,0 @@ -# Symfony Commands - ---- - -# Built-in Commands (1/2) - - !bash - $ php bin/console - - -### Global Options - -You can get **help** information: - - !bash - $ php bin/console help cmd - $ php bin/console cmd --help - $ php bin/console cmd -h - -You can get more verbose messages: - - !bash - $ php bin/console cmd --verbose - $ php bin/console cmd -v [-vv] [-vvv] - -You can suppress output: - - !bash - $ php bin/console cmd --quiet - $ php bin/console cmd -q - ---- - -# Built-in Commands (2/2) - - !text - assets - assets:install Installs bundles web assets under a public - web directory - cache - cache:clear Clears the cache - cache:warmup Warms up an empty cache - config - config:dump-reference Dumps default configuration for an extension - container - container:debug Displays current services for an application - debug - debug:container Displays current services for an application - debug:router Displays current routes for an application - router - router:match Helps debug routes by simulating a path info - match - server - server:run Runs PHP built-in web server - translation - translation:update Updates the translation file - lint - lint:twig Lints a template and outputs encountered - errors - ---- - -# Creating Commands - -Create a `Command` directory inside your bundle and create a php file suffixed -with `Command.php` for each command that you want to provide: - - !php - // src/AppBundle/Command/GreetCommand.php - namespace AppBundle\Command; - - use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; - - class GreetCommand extends ContainerAwareCommand - { - protected function configure() - { - $this->setName('demo:greet'); - } - - protected function execute( - InputInterface $input, - OutputInterface $output - ) { - // code ... - } - } - ---- - -# Command Arguments - -**Arguments** are the strings, separated by spaces, that come after the command -name itself. They are ordered, and can be **optional** or **required**. - - !php - protected function configure() - { - $this - // ... - ->addArgument( - 'name', - InputArgument::REQUIRED, - 'Who do you want to greet?' - ) - ->addArgument( - 'last_name', - InputArgument::OPTIONAL, - 'Your last name?' - ); - } - -### Usage - - !php - $input->getArgument('last_name'); - ---- - -# Command Options (1/2) - -Unlike arguments, **options are not ordered**, **always optional**, and can be -setup to accept a value or simply as a boolean flag without a value. - - !php - protected function configure() - { - $this - // ... - ->addOption( - 'yell', - null, - InputOption::VALUE_NONE, - 'If set, the task will yell in uppercase letters' - ); - } - -### Usage - - !php - // php bin/console demo:greet --yell - - if ($input->getOption('yell')) { - // ... - } - ---- - -# Command Options (2/2) - - !php - protected function configure() - { - $this - // ... - ->addOption( - 'iterations', - null, - InputOption::VALUE_REQUIRED, - 'How many times should the message be printed?', - 1 - ); - } - -### Usage - - !php - // php bin/console demo:greet --iterations=10 - - for ($i = 0; $i < $input->getOption('iterations'); $i++) { - } - ---- - -# More On Commands - -### Getting Services from the Service Container - - !php - protected function execute( - InputInterface $input, - OutputInterface $output - ) { - $translator = $this->getContainer()->get('translator'); - // ... - } - -### Calling an existing Command - - !php - $command = $this->getApplication()->find('demo:greet'); - $arguments = array( - 'command' => 'demo:greet', - 'name' => 'Fabien', - 'yell' => true, - ); - - $returnCode = $command->run(new ArrayInput($arguments), $output); - -> [http://symfony.com/doc/master/cookbook/console/index.html](http://symfony.com/doc/master/cookbook/console/index.html) diff --git a/src/extended/06_symfony2_forms.md b/src/extended/06_symfony2_forms.md deleted file mode 100644 index dab75fe..0000000 --- a/src/extended/06_symfony2_forms.md +++ /dev/null @@ -1,252 +0,0 @@ -# Forms - ---- - -# Building Your First Form - - !php - public function newAction(Request $request) - { - $form = $this->createFormBuilder() - ->add('name') - ->add('bio', 'textarea') - ->add('birthday', 'date') - ->getForm(); - - return $this->render('default/new.html.twig', [ - 'form' => $form->createView(), - ]); - } - -In order to display the _Form_, you need to pass a special _view_ object to the -View layer. It's achieved through the `createView()` method. - ---- - -# Rendering The Form - -![](../images/sf2_simple_form.png) -
- - !jinja - {# src/AppBundle/Resources/views/Default/new.html.twig #} -
- {{ form_widget(form) }} - - -
- ---- - -# Handling Forms: The Right Way - -1. When initially loading the page in a browser, the **request method is GET** -and **the form is simply created and rendered**; - -2. When the user submits the form (i.e. the method is POST) with **invalid -data**, the **form is bound and then rendered**, this time **displaying all -validation errors**; - -3. When the user submits the form with valid data, the form is bound and you have -the **opportunity to perform some actions before redirecting the user** to some -other page (e.g. a "success" page). - -Redirecting a user after a successful form submission prevents the user from -being able to hit "refresh" and re-post the data. - ---- - -# Handling Form Submissions - - !php - public function newAction(Request $request) - { - $form = $this->createFormBuilder() - ->add('name') - ->add('bio', 'textarea') - ->add('birthday', 'date') - ->getForm(); - - if ($form->handleRequest($request)->isValid()) { - $data = $form->getData(); - // do something ... - - return $this->redirect($this->generateUrl('success')); - } - - // ... - } - ---- - -# Built-in Form Types - -Everything is a **Type**! - -![](../images/symfony_built_in_form_types.png) - ---- - -# Creating A Custom Type (Form Class) - - !php - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class PersonType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('name') - ->add('bio', 'textarea') - ->add('birthday', 'date'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults([ - 'data_class' => My\Person::class, - ]); - } - - public function getName() - { - return 'person'; - } - } - ---- - -# Dealing With Objects - - !php - public function newAction(Request $request) - { - $person = new Person(); - $form = $this->createForm(PersonType::class, $person); - - if ($form->handleRequest($request)->isValid()) { - $person->save(); // insert a new `person` - - return $this->redirect($this->generateUrl('success')); - } - - // ... - } - -Placing the form logic into its own class means that the form **can be easily -reused elsewhere** in your project. - -This is the **best way to create forms**, but the choice is up to you! - ---- - -# The `processForm()` Method (1/2) - -Saving or updating an object is pretty much the same thing. In order to avoid -code duplication, you can use a `processForm()` method that can be used in both -the `newAction()` and the `updateAction()`: - - !php - /** - * Create a new Person - */ - public function newAction(Request $request) - { - return $this->processForm($request, new Person()); - } - - /** - * Update an existing Person - */ - public function updateAction(Request $request, $id) - { - $person = ...; // get a `Person` by its $id - - return $this->processForm($request, $person); - } - ---- - -# The `processForm()` Method (2/2) - - !php - /** - * @param Request $request - * @param Person $person - * - * @return Response - */ - private function processForm(Request $request, Person $person) - { - $form = $this->createForm(PersonType::class, $person); - - if ($form->handleRequest($request)->isValid()) { - $person->save(); - - return $this->redirect($this->generateUrl('success')); - } - - return $this->render('default/new.html.twig', [ - 'form' => $form->createView(), - ]); - } - ---- - -# Cross-Site Request Forgery Protection - -CSRF is a method by which a malicious user attempts to make your legitimate -users unknowingly **submit data that they don't intend to submit**. Fortunately, -CSRF attacks **can be prevented by using a CSRF token inside your forms**. - -CSRF protection works by **adding a hidden field to your form**, called `_token` -by default that **contains a value that only you and your user knows**. - -This ensures that the user is submitting the given data. Symfony automatically -validates the presence and accuracy of this token. - -The `_token` field is a hidden field and will be automatically rendered if you -include the `form_rest()` function in your template, which ensures that all -un-rendered fields are output. - ---- - -# Rendering a Form in a Template (1/2) - - !jinja -
- {{ form_errors(form) }} - - {{ form_row(form.name) }} - - {{ form_row(form.bio) }} - - {{ form_row(form.birthday) }} - - {{ form_rest(form) }} - - -
- -> Read more: -[http://symfony.com/doc/master/book/forms.html#rendering-a-form-in-a-template](http://symfony.com/doc/master/book/forms.html#rendering-a-form-in-a-template). - ---- - -# Rendering a Form in a Template (2/2) - -* `form_enctype(form)`: if at least one field is a file upload field, this renders - the obligatory `enctype="multipart/form-data"`; - -* `form_errors(form)`: renders any errors global to the whole form (field-specific - errors are displayed next to each field); - -* `form_row(form.name)`: renders the label, any errors, and the HTML form widget - for the given field inside, by default, a div element; - -* `form_rest(form)`: renders any fields that have not yet been rendered. It's - usually a good idea to place a call to this helper at the bottom of each form. - This helper is also useful for taking advantage of the automatic CSRF Protection. diff --git a/src/extended/07_symfony2_validation.md b/src/extended/07_symfony2_validation.md deleted file mode 100644 index 0b38ddc..0000000 --- a/src/extended/07_symfony2_validation.md +++ /dev/null @@ -1,226 +0,0 @@ -# Validation - ---- - -# About Form Validation - -In the previous section, you learned how a form can be submitted with valid or -invalid data. In Symfony, **validation is applied to the underlying object**. - -In other words, the question isn't whether the "form" is valid, but whether the -object is valid after the form has applied the submitted data to it. - -Calling `$form->isValid()` is a shortcut that **asks the object whether it has -valid data** using a Validation layer. - -Validation is done by adding a set of rules (called **constraints**) to a class. - ---- - -# The Validator Component - -This component is based on the [JSR303 Bean Validation -specification](http://jcp.org/en/jsr/detail?id=303). - -### Example - -Given the following class: - - !php - namespace AppBundle\Entity; - - class Author - { - public $name; - } - -You can configure a set of **constraints** on it: - - !yaml - # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\Author: - properties: - name: - - NotBlank: ~ - ---- - -# Using the `validator` Service - - !php - $author = new Author(); - // ... do something to the $author object - - $validator = $this->get('validator'); - $errors = $validator->validate($author); - - if (count($errors) > 0) { - // Ooops, errors! - } else { - // Everything is ok :-) - } - -If the `$name` property is empty, you will see the following error message: - - !text - AppBundle\Author.name: - This value should not be blank - -Most of the time, you won't interact directly with the validator service or need -to worry about printing out the errors. You will rather use validation -indirectly when handling submitted form data. - ---- - -# Constraints - -![](../images/symfony_validation_constraints.jpg) - -> [http://symfony.com/doc/master/book/validation.html#constraints](http://symfony.com/doc/master/book/validation.html#constraints) - ---- - -# Constraint Targets (1/2) - -Constraints can be **applied to a class property** or a **public getter method** -(e.g. `getFullName()`). The first is the most common and easy to use, but the -second allows you to specify more complex validation rules. - -### Properties - -Validating class properties is the most basic validation technique. Symfony -**allows you to validate private, protected or public properties**. - - !yaml - # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\Author: - properties: - firstName: - - NotBlank: ~ - -### Classes - -Some constraints apply to the entire class being validated. For example, the -`Callback` constraint is a generic constraint that's applied to the class -itself. - ---- - -# Constraint Targets (2/2) - -### Getters - -Constraints **can also be applied to the return value of a method**. Symfony -allows you to add a constraint to any public method whose name starts with -`get` or `is`. - - !yaml - # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\Author: - getters: - passwordLegal: - - "False": - message: "The password cannot match your first name" - -With the following code in the `Author` class: - - !php - public function isPasswordLegal() - { - return ($this->firstName !== $this->password); - } - ---- - -# Validation Groups (1/2) - -In some cases, you will **need to validate an object against only some of the -constraints on that class**. - -You can organize each constraint into one or more **validation groups**, and -then apply validation against just one group of constraints. - -### Example - - !yaml - # src/AppBundle/Resources/config/validation.yml - AppBundle\Entity\User: - properties: - email: - - Email: { groups: [ registration ] } - password: - - NotBlank: { groups: [ registration ] } - - Length: { groups: [ registration ], min: 7 } - city: - - Length: - min: 2 - ---- - -# Validation Groups (2/2) - -With the configuration seen before, there are **two validation groups**: - -* **Default**: contains the constraints not assigned to any other group; -* **registration**: contains the constraints on the email and password fields only. - -To tell the validator to use a specific group, pass one or more group names as -the second argument to the `validate()` method: - - !php - $errors = $validator->validate($author, [ 'registration' ]); - ---- - -# Using Validation Groups In Forms - -If your object takes advantage of validation groups, you'll need to specify -which validation group(s) your form should use: - - !php - $form = $this - ->createFormBuilder($users, [ - 'validation_groups' => [ 'registration' ], - ]) - ->add(...); - -If you're creating **form classes**, then you'll need to add the following to -the `setDefaultOptions()` method: - - !php - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults([ - 'validation_groups' => [ 'registration' ], - ]); - } - ---- - -# Validating Values and Arrays - - !php - use Symfony\Component\Validator\Constraints\Email; - - $emailConstraint = new Email(); - $emailConstraint->message = 'Invalid email address'; - - $errorList = $this->get('validator')->validateValue( - $email, $emailConstraint - ); - - if (0 !== count($errorList)) { - // this is *not* a valid email address - $errorMessage = $errorList[0]->getMessage(); - } - -By calling `validateValue()` on the validator, you can pass in a raw value and -the constraint object that you want to validate that value against. - -The `validateValue()` method returns a `ConstraintViolationList` object, which -acts just like an array of errors. - -Each error in the collection is a `ConstraintViolation` object, which holds the -error message on its `getMessage()` method. diff --git a/src/extended/08_symfony2_translation.md b/src/extended/08_symfony2_translation.md deleted file mode 100644 index 1f45d6a..0000000 --- a/src/extended/08_symfony2_translation.md +++ /dev/null @@ -1,166 +0,0 @@ -# Translations - ---- - -# Definitions - -### Internationalization - -The term **internationalization** (often abbreviated **i18n**) refers to the -process of **abstracting strings and other locale-specific pieces** out of your -application and **into a layer where they can be translated** and converted -**based on the user's locale** (i.e. language and country). - -### Localization - -The act of creating translation files is an important part of **localization** -(often abbreviated **l10n**). It is the **process of adapting a product or -service to a particular language**, **culture**, and desired local -**look-and-feel**. - ---- - -# Using the `translator` Service - - !yaml - # messages.fr.yml - Symfony is great: J'aime Symfony - 'Hello %name%': Bonjour %name% - -When the following code is executed, Symfony will attempt to translate the -message `Symfony is great` based on the locale of the user: - - !php - echo $this->get('translator')->trans('Symfony is great'); - -Now, if the language of the user's locale is French (e.g. `fr_FR` or `fr_BE`), -the message will be translated into `J'aime Symfony`. - -### Message Placeholders - - !php - echo $this->get('translator')->trans('Hello %name%', [ - '%name%' => 'Will' - ]); - - // French: Bonjour Will - // Default: Hello Will - ---- - -# The Translation Process - -To translate the message, Symfony uses a simple process: - -1. The locale of the current user, which is stored on the request (or stored as -`_locale` on the session), is determined; - -2. A catalog of translated messages is loaded from translation resources defined -for the locale (e.g. `fr_FR`). Messages from the fallback locale are also loaded -and added to the catalog if they don't already exist. The end result is a large -"dictionary" of translations; - -3. If the message is located in the catalog, the translation is returned. If not, -the translator returns the original message. - -When using the `trans()` method, Symfony looks for the exact string inside the -appropriate message catalog and returns it (if it exists). - ---- - -# Locations and Naming Conventions - -Symfony looks for message files (i.e. translations) in the following locations: - -* the `/Resources/translations` directory; -* the `/Resources//translations` directory; -* the `Resources/translations/` directory of the bundle. - -The filename of the translations is also important as Symfony uses a convention -to determine details about the translations. Each message file must be named -according to the following path: `domain.locale.loader`: - -* `domain`: an optional way to organize messages into groups; -* `locale`: the locale that the translations are for (`en_GB`, `en`, etc); -* `loader`: how Symfony should load and parse the file (`xliff`, `php` or `yml`). - ---- - -# Pluralization - -When a translation has different forms due to pluralization, you can provide all -the forms as a string separated by a pipe (`|`): - - !jinja - 'There is one apple|There are %count% apples' - -To translate pluralized messages, use the `transChoice()` method: - - !php - $t = $this->get('translator')->transChoice( - 'There is one apple|There are %count% apples', - 10, - array('%count%' => 10) - ); - -The second argument (`10` in this example), is the **number of objects being -described** and is used to determine which translation to use and also to -populate the `%count%` placeholder. - ---- - -# Explicit Interval Pluralization - -Sometimes, you'll need more control or want a different translation for specific -cases (for 0, or when the count is negative, for example). For such cases, you -can use explicit math intervals: - - !jinja - '{0} There are no apples|{1} There is one apple|]1,19] There are - %count% apples|[20,Inf[ There are many apples' - -The intervals follow the [ISO 31-11](http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals) -notation. - -An Interval can represent a finite set of numbers: - - !jinja - {1,2,3,4} - -Or numbers between two other numbers: - - !jinja - [1, +Inf[ - ]-1,2[ - ---- - -# BazingaJsTranslationBundle - - !yaml - # app/Resources/translations/Hello.fr.yml - ba: - bar: Bonjour. - place.holder: Bonjour %username%! - plural: Il y a %count% pomme|Il y a %count% pommes - -

- - !html+jinja - - -A `Translator` object is now available in your JavaScript: - - !javascript - Translator.trans('ba.bar', {}, 'Hello', 'fr'); - // "Bonjour." - - Translator.trans('place.holder', { "username" : "Will" }, 'Hello'); - // "Bonjour Will!" - - Translator.transChoice('plural', 1, { "count": 1 }, 'Hello'); - // "Il y a 1 pomme" - - Translator.transChoice('plural', 10, { "count": 10 }, 'Hello'); - // "Il y a 10 pommes" diff --git a/src/extended/09_http_cache.md b/src/extended/09_http_cache.md deleted file mode 100644 index 58e37a2..0000000 --- a/src/extended/09_http_cache.md +++ /dev/null @@ -1,306 +0,0 @@ -# HTTP Cache - ---- - -# HTTP Cache - -The nature of rich web applications means that they're dynamic. No matter how -efficient your application, **each request will always contain more overhead -than serving a static file**. - -But as your site grows, that overhead can become a problem. The processing -that's normally performed on every request should be done only once. - -This is exactly what **caching** aims to accomplish! - ---- - -# Terminology (1/2) - -### Gateway Cache - -A **gateway cache**, or **reverse proxy**, is an independent layer that sits in -front of your application. - -The **reverse proxy** caches responses as they are returned from your application -and answers requests with cached responses before they hit your application. - -Symfony provides its own reverse proxy, but any reverse proxy can be used. - -### HTTP Cache - -**HTTP cache headers** are used to communicate with the gateway cache and any -other caches between your application and the client. - -Symfony provides sensible defaults and a powerful interface for interacting -with the cache headers. - ---- - -# Terminology (2/2) - -### HTTP Expiration - -**HTTP expiration** and **validation** are the two models used for determining -whether cached content is fresh (can be reused from the cache) or stale (should -be regenerated by the application). - -### Edge Side Includes - -**Edge Side Includes** (ESI) allow HTTP cache to be used to **cache page -fragments** (even nested fragments) independently. With ESI, you can even cache -an entire page for 60 minutes, but an embedded sidebar for only 5 minutes. - ---- - -# Caching with a Gateway Cache - -When caching with HTTP, the **cache is separated from your application** entirely -and sits **between your application and the client** making the request. - -The job of the cache is to **accept requests from the client and pass them back -to your application**. - -The cache will also receive responses back from your application and forward -them on to the client. The cache is the **middle-man** of the request-response -communication between the client and your application. - -Along the way, the cache will store each response that is deemed **cacheable**. -If the same resource is requested again, the cache sends the cached response to -the client, ignoring your application entirely. - -This type of cache is known as a **HTTP gateway cache** and many exist such as -**Varnish**, **Squid** in reverse proxy mode, and the **Symfony reverse -proxy**. - ---- - -# Types of Caches - -The HTTP cache headers sent by your application are consumed and interpreted by -up to three different types of caches: - -* **Browser Caches**: every browser comes with its own local cache that is mainly -useful for when you hit "back" or for images and other assets. The browser cache -is a **private cache** as cached resources aren't shared with anyone else; -* **Proxy Caches**: a proxy is a shared cache as many people can be behind a -single one. It's usually installed by large corporations and ISPs to reduce -latency and network traffic; -* **Gateway Caches**: like a **proxy**, it's also a shared cache but on the -server side. Installed by network administrators, it makes websites more -scalable, reliable and performant. - ---- - -# HTTP Caching - -HTTP specifies four response cache headers that are looked at here: - -* `Cache-Control` -* `Expires` -* `ETag` -* `Last-Modified` - ---- - -# Public vs Private Responses - -Both **gateway and proxy caches are considered shared caches** as the cached -content is shared by more than one user. - -If a user-specific response were ever mistakenly stored by a shared cache, it -might be returned later to any number of different users. Imagine if your -account information were cached and then returned to every subsequent user who -asked for their account page! - -To handle this situation, every response may be set to be public or private: - -* **public**: indicates that the response may be cached by both private and -shared caches; -* **private**: indicates that all or part of the response message is intended for -a single user and must not be cached by a shared cache. - ---- - -# Safe Methods - -**HTTP caching only works for safe HTTP methods** (like `GET` and `HEAD`). Being -safe means that **you never change the application's state on the server when -serving the request**. - -This has two very reasonable consequences: - -* You should **never change the state of your application when responding to a -`GET` or `HEAD` request**. Even if you don't use a gateway cache, the presence -of proxy caches mean that any `GET` or `HEAD` request may or may not actually -hit your server; -* Don't expect `PUT`, `POST` or `DELETE` methods to cache. These methods are -meant to be used when **mutating the state of your application**. Caching them -would prevent certain requests from hitting and mutating your application. - ---- - -# Expiration - -The expiration model is the more efficient and straightforward of the two -caching models and should be used whenever possible. - -When a response is cached with an expiration, the cache will store the response -and return it directly without hitting the application until it expires. - -The expiration model can be accomplished using one of two HTTP headers: - -* `Cache-Control` -* `Expires` - ---- - -# The Cache-Control Header (1/2) - -The `Cache-Control` header is **unique** in that it contains not one, but -various pieces of information about the cacheability of a response. - -Each piece of information is separated by a comma: - - !text - Cache-Control: private, max-age=0, must-revalidate - Cache-Control: max-age=3600, must-revalidate - ---- - -# The Cache-Control Header (2/2) - -Symfony provides an **abstraction** around the `Cache-Control` header: - - !php - use Symfony\Component\HttpFoundation\Response; - - $response = new Response(); - - // mark the response as either public or private - $response->setPublic(); - $response->setPrivate(); - - // set the private or shared max age - $response->setMaxAge(600); - $response->setSharedMaxAge(600); - - // set a custom Cache-Control directive - $response->headers->addCacheControlDirective('must-revalidate', true); - ---- - -# The Expires Header - -The `Expires` header can be set with the `setExpires()` Response method. It takes -a `DateTime` instance as an argument: - - !php - $date = new DateTime(); - $date->modify('+600 seconds'); - - $response->setExpires($date); - -The resulting HTTP header will look like this: - - !text - Expires: Thu, 01 Mar 2013 10:00:00 GMT - -The `setExpires()` method automatically converts the date to the GMT timezone as -required by the specification. - ---- - -# Validation - -With the expiration model, the application won't be asked to return the updated -response until the cache finally becomes stale. It is not good! - -The validation model addresses this issue. - -Under this model, the cache continues to store responses. The difference is -that, for each request, the **cache asks the application whether or not the -cached response is still valid**. - -If the cache is still valid, your application should return a `304` status code -and no content. This tells the cache that it's ok to return the cached response. - ---- - -# The ETag Header - -The `ETag` header is a **string header** called **entity-tag** that uniquely -**identifies one representation of the target resource**. It's entirely -**generated and set by your application**. - -`ETag`s are similar to **fingerprints** and they can be quickly compared to -determine if two versions of a resource are the same or not. - - !php - public function indexAction() - { - $response = $this->render('main/index.html.twig'); - $response->setETag(md5($response->getContent())); - $response->setPublic(); // make sure the response is public/cacheable - $response->isNotModified($this->getRequest()); - - return $response; - } - -The `isNotModified()` method compares the `ETag` sent with the `Request` with -the one set on the `Response`. If the two match, the method automatically sets -the `Response` status code to `304 Not Modified`. - ---- - -# The Last-Modified Header (1/2) - -According to the HTTP specification, _the Last-Modified header field indicates -the date and time at which the origin server believes the representation was -last modified_. - -In other words, the application decides **whether or not the cached content has -been updated** based on whether or not it's been updated since the response was -cached. - ---- - -# The Last-Modified Header (2/2) - - !php - public function showAction($articleSlug) - { - // ... - - $articleDate = new \DateTime($article->getUpdatedAt()); - $authorDate = new \DateTime($author->getUpdatedAt()); - - $date = $authorDate > $articleDate ? $authorDate : $articleDate; - - $response->setLastModified($date); - // Set response as public. Otherwise it will be private by default - $response->setPublic(); - - if ($response->isNotModified($this->getRequest())) { - return $response; - } - - // ... do more work to populate the response - // with the full content - - return $response; - } - ---- - -# Edge Side Includes (ESI) - -**E**dge **S**ide **I**ncludes or ESI is a **small markup language** for dynamic -web content assembly at the reverse proxy level. The reverse proxy analyses the -HTML code, parses ESI specific markup and assembles the final result before -flushing it to the client. - -![](../images/esi.png) - - !html - diff --git a/src/extended/10_stackphp.md b/src/extended/10_stackphp.md deleted file mode 100644 index 288eb24..0000000 --- a/src/extended/10_stackphp.md +++ /dev/null @@ -1,54 +0,0 @@ -# Stack PHP - ---- - -# HttpKernel - -
-
-![](../images/http-kernel.png) - -.fx: no-border - ---- - -# What Is Stack? - -A convention for composing HttpKernelInterface middlewares: - -![](../images/onion.png) - -> [http://stackphp.com/](http://stackphp.com/) - -.fx: no-border - ---- - -# Implementation - - !php - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpKernel\HttpKernelInterface; - - class MyStackMiddleware implements HttpKernelInterface - { - private $app; - - public function __construct(HttpKernelInterface $app) - { - $this->app = $app; - } - - /** - * {@inheritDoc} - */ - public function handle( - Request $request, - $type = HttpKernelInterface::MASTER_REQUEST, - $catch = true - ) { - // do something awesome - - return $this->app->handle($request, $type, $catch); - } - } diff --git a/src/extended/11_end.md b/src/extended/11_end.md deleted file mode 100644 index c0d0a21..0000000 --- a/src/extended/11_end.md +++ /dev/null @@ -1 +0,0 @@ -# The End. diff --git a/src/first/isima-php.md b/src/first/isima-php.md deleted file mode 100644 index 5d3fd81..0000000 --- a/src/first/isima-php.md +++ /dev/null @@ -1,13 +0,0 @@ -# Advanced OOP - ---- - -# ![](../images/haha.jpg) - ---- - -# Web Development - ---- - -# PHP diff --git a/src/first/iut-extended.md b/src/first/iut-extended.md deleted file mode 100644 index 4a48644..0000000 --- a/src/first/iut-extended.md +++ /dev/null @@ -1,5 +0,0 @@ -# PHP Extended - ---- - -# Let's Do Professional Development Now! diff --git a/src/first/iut-php.md b/src/first/iut-php.md deleted file mode 100644 index 9cc4de6..0000000 --- a/src/first/iut-php.md +++ /dev/null @@ -1 +0,0 @@ -# PHP diff --git a/src/hack/00_intro.md b/src/hack/00_intro.md deleted file mode 100644 index 2ac5b58..0000000 --- a/src/hack/00_intro.md +++ /dev/null @@ -1,132 +0,0 @@ -# Hack - ---- - -# What Is Hack? - -
-![](../images/hack.png) -
- -* 2004 - Facebook was initially built with PHP; -* 2009 - A PHP compiler called HipHop was released; -* 2010 - Minor changes to the PHP language were introduced by the HPHP team to - improve development time and provide basic type safety. The changes were XHP - and parameter type constraints; -* 2012 - Facebook engineering teams started exploring the idea of annotating - return types. And the Hack language was born... - ---- - -# What Is Hack? - -**Hack** is a **programming language** for HHVM that interoperates seamlessly -with PHP. It has been created by Facebook. In general, anything you can write in -PHP, you can also write in Hack. - -Hack reconciles the fast development cycle of PHP with the discipline provided by -**static typing**, while **adding many features** commonly found in other modern -programming languages such as -[**generics**](http://docs.hhvm.com/manual/en/hack.generics.php), -[**collections**](http://docs.hhvm.com/manual/en/hack.collections.php), and -[**nullable**](http://docs.hhvm.com/manual/en/hack.nullable.php). - -It also provides built-in [asynchronous -programming](http://docs.hhvm.com/manual/en/hack.async.php). - -> Official website: [hacklang.org](http://hacklang.org/) - ---- - -# Getting Started - -Use `hello(); - } - ---- - -# Type Annotations - - !php - [http://docs.hhvm.com/manual/en/hack.annotations.php](http://docs.hhvm.com/manual/en/hack.annotations.php) - ---- - -# Generics - - !php - { - public T $value; - - public function __construct(T $v) { - $this->value = $v; - } - } - -> [http://docs.hhvm.com/manual/en/hack.generics.php](http://docs.hhvm.com/manual/en/hack.generics.php) - ---- - -# Collections - -Hack provides a unified collections framework including: `Vector`, `Map`, `Set`, `Pair`. - -> [http://docs.hhvm.com/manual/en/hack.collections.php](http://docs.hhvm.com/manual/en/hack.collections.php) - ---- - -# Nullable Types - -`Nullable` allows any type to have `null` assigned and checked on it: - - !php - [http://docs.hhvm.com/manual/en/hack.nullable.php](http://docs.hhvm.com/manual/en/hack.nullable.php) diff --git a/src/images/11-steps.png b/src/images/11-steps.png deleted file mode 100644 index fb0594b..0000000 Binary files a/src/images/11-steps.png and /dev/null differ diff --git a/src/images/MVC.png b/src/images/MVC.png deleted file mode 100644 index 6fa5c2d..0000000 Binary files a/src/images/MVC.png and /dev/null differ diff --git a/src/images/active-record.png b/src/images/active-record.png deleted file mode 100644 index 774ef6e..0000000 Binary files a/src/images/active-record.png and /dev/null differ diff --git a/src/images/authentication_mechanism.png b/src/images/authentication_mechanism.png deleted file mode 100644 index b59071a..0000000 Binary files a/src/images/authentication_mechanism.png and /dev/null differ diff --git a/src/images/clermontech.png b/src/images/clermontech.png deleted file mode 100644 index 3251f09..0000000 Binary files a/src/images/clermontech.png and /dev/null differ diff --git a/src/images/client-server.png b/src/images/client-server.png deleted file mode 100644 index 36bcbea..0000000 Binary files a/src/images/client-server.png and /dev/null differ diff --git a/src/images/client_server_with_auth.png b/src/images/client_server_with_auth.png deleted file mode 100644 index 11c3f03..0000000 Binary files a/src/images/client_server_with_auth.png and /dev/null differ diff --git a/src/images/client_server_without_auth.png b/src/images/client_server_without_auth.png deleted file mode 100644 index bfdf59c..0000000 Binary files a/src/images/client_server_without_auth.png and /dev/null differ diff --git a/src/images/core-team.jpg b/src/images/core-team.jpg deleted file mode 100644 index c014b56..0000000 Binary files a/src/images/core-team.jpg and /dev/null differ diff --git a/src/images/data-mapper.png b/src/images/data-mapper.png deleted file mode 100644 index e4bbaf4..0000000 Binary files a/src/images/data-mapper.png and /dev/null differ diff --git a/src/images/esi.png b/src/images/esi.png deleted file mode 100644 index 11f602f..0000000 Binary files a/src/images/esi.png and /dev/null differ diff --git a/src/images/front-controller.png b/src/images/front-controller.png deleted file mode 100644 index 298c8d1..0000000 Binary files a/src/images/front-controller.png and /dev/null differ diff --git a/src/images/gifts.jpg b/src/images/gifts.jpg deleted file mode 100644 index fe8e310..0000000 Binary files a/src/images/gifts.jpg and /dev/null differ diff --git a/src/images/github.png b/src/images/github.png deleted file mode 100644 index 8750571..0000000 Binary files a/src/images/github.png and /dev/null differ diff --git a/src/images/hack.png b/src/images/hack.png deleted file mode 100644 index 72e51cb..0000000 Binary files a/src/images/hack.png and /dev/null differ diff --git a/src/images/haha.jpg b/src/images/haha.jpg deleted file mode 100644 index 7e66502..0000000 Binary files a/src/images/haha.jpg and /dev/null differ diff --git a/src/images/hhvm.jpg b/src/images/hhvm.jpg deleted file mode 100644 index 50334c1..0000000 Binary files a/src/images/hhvm.jpg and /dev/null differ diff --git a/src/images/http-kernel.png b/src/images/http-kernel.png deleted file mode 100644 index cea959c..0000000 Binary files a/src/images/http-kernel.png and /dev/null differ diff --git a/src/images/identity-map.png b/src/images/identity-map.png deleted file mode 100644 index 998fe61..0000000 Binary files a/src/images/identity-map.png and /dev/null differ diff --git a/src/images/many-to-many.png b/src/images/many-to-many.png deleted file mode 100644 index 8a5b91c..0000000 Binary files a/src/images/many-to-many.png and /dev/null differ diff --git a/src/images/one-to-many.png b/src/images/one-to-many.png deleted file mode 100644 index eb3eae3..0000000 Binary files a/src/images/one-to-many.png and /dev/null differ diff --git a/src/images/one-to-one.png b/src/images/one-to-one.png deleted file mode 100644 index 50b4752..0000000 Binary files a/src/images/one-to-one.png and /dev/null differ diff --git a/src/images/onion.png b/src/images/onion.png deleted file mode 100644 index bd33eba..0000000 Binary files a/src/images/onion.png and /dev/null differ diff --git a/src/images/react.png b/src/images/react.png deleted file mode 100644 index d63baf6..0000000 Binary files a/src/images/react.png and /dev/null differ diff --git a/src/images/repository.gif b/src/images/repository.gif deleted file mode 100644 index dbdf153..0000000 Binary files a/src/images/repository.gif and /dev/null differ diff --git a/src/images/rmm.png b/src/images/rmm.png deleted file mode 100644 index f6c1e1d..0000000 Binary files a/src/images/rmm.png and /dev/null differ diff --git a/src/images/rmm0.png b/src/images/rmm0.png deleted file mode 100644 index b1f1fbd..0000000 Binary files a/src/images/rmm0.png and /dev/null differ diff --git a/src/images/rmm1.png b/src/images/rmm1.png deleted file mode 100644 index 0da1342..0000000 Binary files a/src/images/rmm1.png and /dev/null differ diff --git a/src/images/rmm2.png b/src/images/rmm2.png deleted file mode 100644 index 2a8c0a1..0000000 Binary files a/src/images/rmm2.png and /dev/null differ diff --git a/src/images/rmm3.png b/src/images/rmm3.png deleted file mode 100644 index c4ea6f1..0000000 Binary files a/src/images/rmm3.png and /dev/null differ diff --git a/src/images/row-data-gateway.png b/src/images/row-data-gateway.png deleted file mode 100644 index d9a4619..0000000 Binary files a/src/images/row-data-gateway.png and /dev/null differ diff --git a/src/images/sf2_simple_form.png b/src/images/sf2_simple_form.png deleted file mode 100644 index 9d727d4..0000000 Binary files a/src/images/sf2_simple_form.png and /dev/null differ diff --git a/src/images/specification.png b/src/images/specification.png deleted file mode 100644 index 4eff966..0000000 Binary files a/src/images/specification.png and /dev/null differ diff --git a/src/images/symfony-github.png b/src/images/symfony-github.png deleted file mode 100644 index 4790e6a..0000000 Binary files a/src/images/symfony-github.png and /dev/null differ diff --git a/src/images/symfony.png b/src/images/symfony.png deleted file mode 100644 index d0a0d09..0000000 Binary files a/src/images/symfony.png and /dev/null differ diff --git a/src/images/symfony_built_in_form_types.png b/src/images/symfony_built_in_form_types.png deleted file mode 100644 index bdc31f7..0000000 Binary files a/src/images/symfony_built_in_form_types.png and /dev/null differ diff --git a/src/images/symfony_validation_constraints.jpg b/src/images/symfony_validation_constraints.jpg deleted file mode 100644 index 0820edf..0000000 Binary files a/src/images/symfony_validation_constraints.jpg and /dev/null differ diff --git a/src/images/table-data-gateway.png b/src/images/table-data-gateway.png deleted file mode 100644 index fe17547..0000000 Binary files a/src/images/table-data-gateway.png and /dev/null differ diff --git a/src/images/travis-ci.png b/src/images/travis-ci.png deleted file mode 100644 index 6694b18..0000000 Binary files a/src/images/travis-ci.png and /dev/null differ diff --git a/src/images/twig_homepage.png b/src/images/twig_homepage.png deleted file mode 100644 index 439ac4b..0000000 Binary files a/src/images/twig_homepage.png and /dev/null differ diff --git a/src/images/whoops.png b/src/images/whoops.png deleted file mode 100644 index 1bf733a..0000000 Binary files a/src/images/whoops.png and /dev/null differ diff --git a/src/images/y-u-no-use-linux.jpg b/src/images/y-u-no-use-linux.jpg deleted file mode 100644 index 8dec0bb..0000000 Binary files a/src/images/y-u-no-use-linux.jpg and /dev/null differ diff --git a/src/isima/end.md b/src/isima/end.md deleted file mode 100644 index 89f9bc9..0000000 --- a/src/isima/end.md +++ /dev/null @@ -1,5 +0,0 @@ -# Next Week:
[Web Security 101](http://edu.williamdurand.fr/web-security-101-slides/) - ---- - -# The End. diff --git a/src/iut/07_dependency_management_example.md b/src/iut/07_dependency_management_example.md deleted file mode 100644 index 523ef3a..0000000 --- a/src/iut/07_dependency_management_example.md +++ /dev/null @@ -1,100 +0,0 @@ -# Example - -Instead of writing a console application by hand, let's use an existing library: -the **Symfony2 Console** component: - - !json - { - "require": { - "symfony/console": "~2.4" - } - } - -The structure of your application should look like: - - !text - console-app - ├── app - │   └── console - ├── composer.json - ├── src - ├── tests - └── vendor - ---- - -# The Symfony2 Console Component - -The easiest way to write **strong console applications**: - -* Create a set of commands; -* Add them to a console application. - -Your _Commands_ should extend the `Command` class: - - !php - class GreetCommand extends Command - { - protected function configure() - { - // configure the name, arguments, options, etc. - } - - protected function execute(InputInterface $in, OutputInterface $out) { - // do greet - } - } - ---- - -# A Basic Command (1/2) - -### Configuration - - !php - $this - ->setName('demo:greet') - ->addArgument( - 'name', - InputArgument::OPTIONAL, - 'Who do you want to greet?' - ); - - -### Execution - - !php - if (null === $name = $input->getArgument('name')) { - $name = 'World'; - } - - $output->writeln('Hello, ' . $name); - ---- - -# A Basic Command (2/2) - -### The Console Application - - !php - #!/usr/bin/env php - # app/console - add(new GreetCommand()); - $application->run(); - -### Usage - - !bash - $ app/console demo:greet - Hello, World - $ app/console demo:greet William - Hello, William - -> Read more: -[http://symfony.com/doc/current/components/console/](http://symfony.com/doc/current/components/console/index.html). diff --git a/src/iut/12_writing_better_code.md b/src/iut/12_writing_better_code.md deleted file mode 100644 index 0c5eb0c..0000000 --- a/src/iut/12_writing_better_code.md +++ /dev/null @@ -1,373 +0,0 @@ -# Writing Better Code - ---- - -# Agenda - -* Coding Standards -* Programming To The Interface -* Dependency Inversion Principle (DIP) -* Dependency Injection (DI) -* Inversion of Control (IoC) -* Dependency Injection Container (DIC) -* Component Driven Development -* From STUPID to SOLID code! -* Object Calisthenics - ---- - -# Coding Standards - - !php - bar = $bar; - $this->opts = $opts; - } - } - -> Read more about **coding standards** with -> [PSR-1](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md) -> and -> [PSR-2](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md). - ---- - -# PHP Coding Standards Fixer - -The PHP Coding Standards Fixer tool fixes most issues in your code when -you want to follow the PHP coding standards as defined in the PSR-1 and -PSR-2 documents. - -### Installation - - !bash - $ wget http://cs.sensiolabs.org/get/php-cs-fixer.phar - -### Usage - - !bash - $ php php-cs-fixer.phar fix /path/to/dir/or/file - -> More information at: -[http://cs.sensiolabs.org/](http://cs.sensiolabs.org/). - ---- - -# Programming To The Interface - -Reduces dependency on implementation specifics and makes code more reusable. - -The `BananaController` can use either **Twig** or the raw PHP implementation -as template engine thanks to the `TemplateEngine` interface: - - !php - interface TemplateEngine - { - /** - * @param string $template - * @param array $parameters - * - * @return string - */ - public function render($template, array $parameters = []); - } - -You should think about interfaces, not about internal implementation details. - ---- - -# Dependency Inversion Principle (DIP) - -The **D**ependency **I**nversion **P**rinciple has two parts: - -* High-level modules should not depend on low-level modules. Both should depend - on abstractions; -* Abstractions should not depend upon details. Details should depend upon - abstractions. - -DIP is about the level of the abstraction in the messages sent from your code to -the thing it is calling. - ---- - -# Dependency Injection (DI) - -**D**ependency **I**njection is about how one object acquires a dependency. - - !php - class Foo - { - private $bar; - - // **NOT** DI - public function __construct() - { - $this->bar = new Bar(); - } - } - -When a dependency is provided externally, then the system is using DI: - - !php - // DI! - public function __construct(Bar $bar) - { - $this->bar = $bar; - } - ---- - -# Dependency Injection (Anti) Pattern - -### ServiceLocator - -The basic idea behind a service locator is to have an object that knows how to -get hold of all of the services that an application might need. - - !php - class ServiceLocator - { - public static function getBar() - { - return new Bar(); - } - } - -

- - !php - class Foo - { - private $bar; - - public function __construct() - { - $this->bar = ServiceLocator::getBar(); - } - } - ---- - -# Dependency Injection Patterns - -### Constructor Injection - -All dependencies are **injected** using a **constructor**: - - !php - class Foo - { - private $bar; - - public function __construct(BarInterface $bar) - { - $this->bar = $bar; - } - } - ---- - -# Dependency Injection Patterns - -### Setter Injection - -Dependencies are **injected** through **setters**: - - !php - class Foo - { - private $bar; - - public function setBar(BarInterface $bar) - { - $this->bar = $bar; - } - } - ---- - -# Dependency Injection Patterns - -### Interface Injection - -An interface describes the **injection**: - - !php - interface BarAware - { - public function setBar(BarInterface $bar); - } - -It needs to be implemented by the class that wants to use a `BarInterface`: - - !php - class Foo implements BarAware - { - private $bar; - - public function setBar(BarInterface $bar) - { - $this->bar = $bar; - } - } - ---- - -# Inversion of Control (IoC) - -**I**nversion **o**f **C**ontrol is about who initiates the call. If your code -initiates a call, it is not IoC, if the container/system/library calls back -into code that you provided it, it is IoC. - -Hollywood Principle: _Don't call us, we'll call you_. - ---- - -# Dependency Injection Container (DIC) - -A framework or library for building graphs of objects by passing in (injecting) -each object's dependencies. Object lifetimes are handled by the container -instead of by the consuming object. - -Most of the time, you rely on configuration files to describe your **classes** -and their **dependencies**. A **class** in this context is also known as a -**service**. - -You ask this container to retrieve a service, and it is **lazy loaded** and -dynamically built: - - !php - // It's an instance of `TemplateEngine`, but you don't know - // anything about its internal implementation. - // Is it the raw PHP implementation or Twig? - $engine = $container->get('template_engine'); - ---- - -# PHP Implementations - -### Twittee - -[Twittee](https://github.com/fabpot/twittee) is the **smallest** Dependency Injection -Container written in PHP. -It fits in a _tweet_ (less than 140 characters): - - !php - class Container - { - protected $s = array(); - - function __set($k, $c) - { - $this->s[$k] = $c; - } - - function __get($k) - { - return $this->s[$k]($this); - } - } - ---- - -# PHP Implementations - -### Pimple - -[Pimple](https://github.com/fabpot/Pimple) is a small Dependency Injection Container -for PHP 5.3 that consists of just one file and one class. - -### The Symfony2 DependencyInjection Component - -The [DependencyInjection](https://github.com/symfony/DependencyInjection) component -allows you to standardize and centralize the way objects are constructed in your -application. - -> Read more: -> -> * [http://symfony.com/doc/current/book/service_container.html](http://symfony.com/doc/current/book/service_container.html); -> * [http://symfony.com/doc/current/components/dependency_injection/](http://symfony.com/doc/current/components/dependency_injection/). - ---- - -# Component Driven Development - -It’s all about **Separation of Concerns** (SoC). - -You design components with their own logic, each component does **one thing -well**, and **only one thing**. - -How to manage these components in your application? - -> Read more: [Component Driven Development: it's like -Lego!](http://williamdurand.fr/2012/02/01/component-driven-development-it-s-like-lego/) - ---- - -# From STUPID to SOLID code! (1/2) - -### STUPID - -* **S**ingleton -* **T**ight Coupling -* **U**ntestability -* **P**remature Optimization -* **I**ndescriptive Naming -* **D**uplication - -> Read more: -[http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#stupid-code-seriously](http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#stupid-code-seriously). - ---- - -# From STUPID to SOLID code! (2/2) - -### SOLID - -* **S**ingle Responsibility Principle -* **O**pen/Closed Principle -* **L**iskov Substitution Principle -* **I**nterface Segregation Principle -* **D**ependency Inversion Principle - -> Read more: -[http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#solid-to-the-rescue](http://williamdurand.fr/2013/07/30/from-stupid-to-solid-code/#solid-to-the-rescue). - ---- - -# Object Calisthenics - -**9 rules** invented by Jeff Bay in his book [The ThoughWorks -Anthology](http://pragprog.com/book/twa/thoughtworks-anthology): - -1. Only One Level Of Indentation Per Method -2. Don't Use The ELSE Keyword -3. Wrap All Primitives And Strings -4. First Class Collections -5. One Dot Per Line -6. Don't Abbreviate -7. Keep All Entities Small -8. No Classes With More Than Two Instance Variables -9. No Getters/Setters/Properties - -> Read more: -[http://williamdurand.fr/2013/06/03/object-calisthenics/](http://williamdurand.fr/2013/06/03/object-calisthenics/). - ---- - -

![](../images/11-steps.png) diff --git a/src/iut/13_testing.md b/src/iut/13_testing.md deleted file mode 100644 index d206d11..0000000 --- a/src/iut/13_testing.md +++ /dev/null @@ -1,276 +0,0 @@ -# Testing - ---- - -

-
- - -
-

- ---- - -# Agenda - -* Unit Testing -* Functional Testing -* Behavior Driven Development -* Testing Tweet Frameworks - ---- - -# Unit Testing - ---- - -# Unit Testing - -Unit testing is a Method by which individual units of source code are tested to -determine if they are fit for use. - -[PHPUnit](http://www.phpunit.de/manual/current/en/) is the de-facto standard for -unit testing in PHP projects. - -Install it using Composer: - - !javascript - { - "require-dev": { - "phpunit/phpunit": "~3.7" - } - } - -Or as a PHAR: - - !bash - $ wget http://pear.phpunit.de/get/phpunit.phar - $ chmod +x phpunit.phar - ---- - -# PHPUnit — The Rules - -The tests for a class `Class` go into a class `ClassTest`. - -`ClassTest` should inherit from `PHPUnit_Framework_TestCase`, but -a common practice is to create a `TestCase` class for a project, and to inherit -from it: - - !php - class TestCase extends PHPUnit_Framework_TestCase {} - -The tests are public methods that are named `test*`, but you can also use the -`@test` annotation: - - !php - class ClassTest extends TestCase - { - public function testFoo() - { - $object = new MyClass(); - $this->assertEquals('foo', $object->foo(), 'optional comment'); - } - } - ---- - -# PHPUnit — Assertions - -* `assertEquals()` -* `assertTrue()` -* `assertFalse()` -* etc. - -> See all assertion methods: -[http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html](http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions). - ---- - -# Running PHPUnit - -Running the test suite: - - !bash - $ phpunit - PHPUnit 3.7.0 by Sebastian Bergmann. - - . - - Time: 1 seconds, Memory: 5.25Mb - - OK (1 test, 1 assertion) - - -Getting the code coverage: - - !bash - $ phpunit --coverage-text - - -See also: - - !bash - $ phpunit --log-junit junit_output.xml - - $ phpunit --testdox - ---- - -# Test Double - -* **Dummy** objects are passed around but never actually used. Usually they are - just used to fill parameter lists; -* **Fake** objects actually have working implementations, but usually take some - shortcut which makes them not suitable for production (an in memory database - is a good example); -* **Stubs** provide canned answers to calls made during the test, usually not - responding at all to anything outside what's programmed in for the test. Stubs - may also record information about calls, such as an email gateway stub that - remembers the messages it 'sent', or maybe only how many messages it 'sent' - → **State verification**; -* **Mocks** are objects pre-programmed with expectations which form a - specification of the calls they are expected to receive → **Behavior - verification**. - ---- - -# Functional Testing - ---- - -# Functional Testing - -Also known as **acceptance testing**. - -Use tools to create automated tests that actually use your application rather -than only verifying that individual units of code behave correctly. - -* [Selenium](http://seleniumhq.org/) -* [Mink](http://mink.behat.org/) -* [CasperJS](http://casperjs.org/) - ---- - -# Behavior Driven Development - ---- - -# Behavior Driven Development - -## SpecBDD - -* You write specifications that describe how your actual code should - behave; -* Focused on technical behavior; -* [PHPSpec](http://www.phpspec.net/). - -## StoryBDD - -* You write human-readable stories that describe the behavior of your - application; -* Business oriented; -* [Behat](http://behat.org/). - ---- - -# Behat - -Install **Behat** via Composer: - - !javascript - { - "require-dev": { - "behat/behat": "2.4.*@stable" - }, - "config": { - "bin-dir": "bin/" - } - } - -The `bin/behat` command is now installed! - -Or as a PHAR: - - !bash - $ wget https://github.com/downloads/Behat/Behat/behat.phar - $ chmod +x behat.phar - ---- - -# Using Behat (1/2) - -Initialize your project: - - !bash - $ bin/behat --init - -Define a **Feature**: - - !gherkin - # features/your_first_feature.feature - Feature: Your first feature - In order to start using Behat - As a manager or developer - I need to try - -Define a **Scenario**: - - !gherkin - Scenario: Successfully describing scenario - Given there is something - When I do something - Then I should see something - ---- - -# Using Behat (2/2) - -Executing Behat: - - !bash - $ behat - -Writing your Step definitions: - - !php - /** - * @Given /^there is something$/ - */ - public function thereIsSomething() - { - throw new PendingException(); - } - -> **Must Read**: -[https://speakerdeck.com/everzet/behat-by-example](https://speakerdeck.com/everzet/behat-by-example). - -

- -> Read more about Behat: -[http://docs.behat.org/](http://docs.behat.org/). - ---- - -# Testing Tweet Frameworks - -### [Tweetest](http://adambrett.github.io/tweetest/) - - !php - function tweetest($c,$m) {$c=is_callable($c)?$c():$c;echo($c)?'.':"F[{$m}]";} - - tweetest($testValue !== 'bar', '$testValue should never equal bar'); - - -### [TestFrameworkInATweet.php](https://gist.github.com//mathiasverraes/9046427) - - !php - function it($m,$p){echo ($p?'✔︎':'✘')." It $m\n";if(!$p){$GLOBALS['f']=1;}} - function done(){if(@$GLOBALS['f'])die(1);} - - it("should sum two numbers", 1 + 1 == 2); - it("should display an X for a failing test", 1 + 1 == 3); - done(); diff --git a/src/iut/14_awesome_projects.md b/src/iut/14_awesome_projects.md deleted file mode 100644 index c8ce6e0..0000000 --- a/src/iut/14_awesome_projects.md +++ /dev/null @@ -1,279 +0,0 @@ -# Awesome Projects - ---- - -# Assert - -Assertions and guard methods for input validation (not filtering!) in -business-model, libraries and application low-level code. - - !php - use Assert\Assertion; - use Assert\AssertionFailedException; - - try { - \Assert\that($identifier)->notEmpty()->integer(); - Assertion::notEmpty($message, 'Message is not specified'); - } catch(AssertionFailedException $e) { - $e->getValue(); // the value that caused the failure - $e->getConstraints(); // the additional constraints of the assertion - } - -
- -

beberlei/assert

-
- ---- - -# Carbon - -A simple API extension for `DateTime`. - - !php - $tomorrow = Carbon::now()->addDay(); - $lastWeek = (new Carbon())->subWeek(); - $noonTodayLondonTime = Carbon::createFromTime(12, 0, 0, 'Europe/London'); - - if (Carbon::now()->isWeekend()) { - echo 'Party!'; - } - - Carbon::now()->subDays(5)->diffForHumans(); // 5 days ago - -Freezing time: - - !php - Carbon::setTestNow(Carbon::create(2001, 5, 21, 12)); - - echo Carbon::now(); // 2001-05-21 12:00:00 - // test, test, test! - - Carbon::setTestNow(); // clear the mock - echo Carbon::now(); // 2014-02-02 21:00:00 - -
- -

briannesbitt/Carbon

-
- ---- - -# Faker - -Fake data generator. - - !php - // use the factory to create a `Faker\Generator` instance - $faker = Faker\Factory::create(); - - // generate data by accessing properties - echo $faker->name; - // 'Lucy Cechtelar'; - echo $faker->address; - // "426 Jordy Lodge - // Cartwrightshire, SC 88120-6700" - echo $faker->text; - // Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam - // beatae sint laudantium consequatur. Magni occaecati itaque sint et sit - // tempore. Nesciunt amet quidem. Iusto deleniti cum autem ad quia aperiam. - echo $faker->email; - // 'tkshlerin@collins.com' - echo $faker->ipv4; - // '109.133.32.252' - echo $faker->creditCardNumber; - // '4485480221084675' - -
- -

fzaninotto/Faker

-
- ---- - -# Flysystem - -Filesystem abstraction layer. - - !php - use League\Flysystem as F - - // Adapters: Local, Amazon Web Services - S3, Rackspace Cloud Files, - // Dropbox, Ftp, Sftp, Zip, WebDAV - $filesystem = new F\Filesystem(new F\Adapter\Local(__DIR__.'/path/to/dir')); - - // Create a file - $filesystem->write('path/to/file.txt', 'contents'); - - // Update a file - $filesystem->update('file/to/update.ext', 'new contents'); - - // Write or update a file - $filesystem->put('filename.txt', 'contents'); - - // Delete a file - $filesyste->delete('delete/this/file.md'); - - // Check if a file exists - $exists = $filesystem->has('filename.txt'); - -
- -

thephpleague/flysystem

-
- ---- - -# Mockery - - - - !php - // The PHPUnit Way - $mock = $this->getMock('SomeClass'); - $mock->expects($this->once()) - ->method('getName') - ->will($this->returnValue('John Doe')); - - $mock2 = $this->getMock('AnotherClass'); - $mock2->expects($this->any()) - ->method('getNumber') - ->with(2) - ->will($this->returnValue(2)); - $mock2->expects($this->any()) - ->method('getNumber') - ->with(3) - ->will($this->returnValue(3)); - - // The Mockery Way - $mock = \Mockery::mock('SomeClass'); - $mock->shouldReceive('getName')->once()->andReturn('John Doe'); - - $mock2 = \Mockery::mock('AnotherClass'); - $mock2->shouldReceive('getNumber')->with(2)->andReturn(2); - $mock2->shouldReceive('getNumber')->with(3)->andReturn(3); - -
- -

padraic/mockery

-
- ---- - -# Option Type for PHP - - !php - use PhpOption\Option; - - class MyRepository - { - public function findSomeEntity($criteria) - { - return Option::fromValue($this->em->find(...)); - } - } - -### You always Require an Entity in Calling Code - - !php - // returns entity, or throws exception - $entity = $repository->findSomeEntity(...)->get(); - -### Fallback to Default Value If Not Available - - !php - $entity = $repository->findSomeEntity(...)->getOrElse(new Entity()); - -
- -

schmittjoh/phpoption

-
- ---- - -# PHP-Parser - -A PHP parser written in PHP, producing Abstract Syntax Trees (AST). - - !php -

- - !text - array( - 0: Stmt_Echo( - exprs: array( - 0: Scalar_String( - value: Hi - ) - 1: - Scalar_String( - value: - World - ) - ) - ) - ) - -
- -

nikic/PHP-Parser

-
- ---- - -#
![React](../images/react.png) - -Event-driven, non-blocking I/O with PHP: -[http://reactphp.org/](http://reactphp.org/). - -.fx: center - ---- - -# Swiftmailer - -Comprehensive mailing tools for PHP. - - !php - // Create the message - $message = Swift_Message::newInstance() - // Give the message a subject - ->setSubject('Your subject') - // Set the From address with an associative array - ->setFrom([ 'john@doe.com' => 'John Doe' ]) - // Set the To addresses with an associative array - ->setTo([ 'receiver@domain.org', 'other@domain.org' => 'A name' ]) - // Give it a body - ->setBody('Here is the message itself') - ; - - // Create the Transport - $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25); - - // Create the Mailer using your created Transport - $mailer = Swift_Mailer::newInstance($transport); - - // Send the message - $result = $mailer->send($message); - -
- -

swiftmailer/swiftmailer

-
- ---- - -# Whoops - -PHP errors for cool kids. - -![](../images/whoops.png) - -
- -

filp/whoops

-
diff --git a/src/iut/15_embracing_open_source.md b/src/iut/15_embracing_open_source.md deleted file mode 100644 index 2381553..0000000 --- a/src/iut/15_embracing_open_source.md +++ /dev/null @@ -1,31 +0,0 @@ -# Embracing Open Source - ---- - -# ![](../images/github.png) - -.fx: no-border - ---- - -# ![](../images/gifts.jpg) - ---- - -# ![](../images/travis-ci.png) - -.fx: no-border - ---- - -# Golden Rules - -* Read other people's code is the fastest way to learn; -* Think about what you have to do; -* Read the code, not the doc; -* Never trust the user; -* Think again; -* Simple is always better than complicated; -* Keep your code readable; -* Test your code, it eases refactoring; -* Keep [HTTP protocol](http://pretty-rfc.herokuapp.com/RFC2616) in mind. diff --git a/src/iut/16_end.md b/src/iut/16_end.md deleted file mode 100644 index 32fb278..0000000 --- a/src/iut/16_end.md +++ /dev/null @@ -1,5 +0,0 @@ -# The End. - ---- - -# Well... Maybe Not.
[PHP Extended](extended.html) diff --git a/src/me.md b/src/me.md deleted file mode 100644 index b6f7860..0000000 --- a/src/me.md +++ /dev/null @@ -1,36 +0,0 @@ -# Who Is Speaking? - ---- - -# William DURAND - -PhD / **CTO TailorDev** - -Graduated from IUT, ISIMA, Blaise Pascal University. Worked at: - -* [Michelin](http://www.michelin.fr/) (Clermont-Fd, France); -* [Nelmio](http://nelm.io) (Zürich, Switzerland); -* [e-TF1](http://www.tf1.fr/) (Paris, France); -* [Prizee.com](http://prizee.com) (Clermont-Fd, France). - -Open-Source evangelist: - -* Lead developer of [Geocoder](http://geocoder-php.org/), and [several other - projects](https://github.com/willdurand); -* (Inactive?) Contributor on Symfony. - -

- twitter.com/couac -  |  - github.com/willdurand -  |  - williamdurand.fr -

- ---- - -# [edu@drnd.me](mailto:edu@drnd.me) - ---- - -# ![](images/clermontech.png) diff --git a/themes/avalanche/avalanche/base.html b/themes/avalanche/avalanche/base.html index e9ffd81..bd7fe90 100644 --- a/themes/avalanche/avalanche/base.html +++ b/themes/avalanche/avalanche/base.html @@ -182,17 +182,9 @@

Help

{% endif %} {% endfor %} Fork me on GitHub