PhD / CTO TailorDev
Graduated from IUT, ISIMA, Blaise Pascal University. Worked at:
Open-Source evangelist:
twitter.com/couac | github.com/willdurand | williamdurand.fr
$ sudo apt-get install php5-common libapache2-mod-php5 php5-cli
$ 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
HipHop Virtual Machine for PHP, created by Facebook.
HHVM uses a just-in-time compilation approach to achieve superior performance.
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.
// 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.
The hash_equals()
function has been added in PHP 5.6 to compare two strings in constant time.
$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
??
// 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';
<=>
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
class Foo
{
}
Important: No class-level visibility in PHP.
abstract class AbstractFoo
{
abstract public function doSomething();
}
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
}
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');
public
protected
private
Attribute visibility MUST be set.
Method visibility SHOULD be set.
Methods without any explicit visibility keyword are defined as public
.
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;
}
class Foo
{
public function doSomething()
{
}
}
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);
PHP 7 \o/
Works with int
, float
, string
, and bool
:
function sumOfInts(int ...$ints) {
return array_sum($ints);
}
function sumOfInts(int ...$ints) : int {
return array_sum($ints);
}
The ->
operator is used to call methods on objects.
$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.
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;
}
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!
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
$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.
New operator ...
as of PHP 5.6:
function sum(...$numbers)
{
return array_sum($numbers);
}
echo sum(1, 2);
// 3
$numbers = [ 2, 3 ];
echo sum(1, ...$numbers);
// 6
interface Fooable
{
const VALUE = 123;
// it's always public anyway
public function doSomething();
}
// Interface may extend several other interfaces.
// This is not possible with class though!
interface MyTraversable extends Traversable, Countable
{
}
// a class may implement several interfaces, but may extend only one class!
class Foo implements Fooable, MyTraversable {}
Namespaces prevent naming collisions with identifiers such as function, class, and interface names:
namespace Vendor\Model;
// ...
Or:
namespace MyNamespace {
// ...
}
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
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) {
// ...
}
}
class
KeywordSince 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.
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.
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.
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.
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.
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:
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.
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.
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.
#!/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!
A typical client/server request follows this pattern:
Server: Here is the resource at URI:
Content
For HTTP, a typical client is a web browser, and a server is a web server.
/bananas/joe
: URI for banana "Joe"/bananas/henry
: URI for banana "Henry"/bananas
: collection of all available bananasRequest is made of:
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
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.
Response is made of:
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>
1xx
Informational2xx
Successful200
OK201
Created204
No Content3xx
Redirections301
Moved Permanently302
Found304
Not Modified4xx
Client Error400
Bad Request401
Unauthorized403
Forbidden404
Not Found405
Method Not Allowed406
Not Acceptable409
Conflict415
Unsupported Media Type451
Unavailable For Legal Reasons5xx
Server Error500
Internal Server ErrorThere 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:
$_GET
;$_POST
;$_REQUEST
global variable.You can always use the following, but you need to parse them by yourself:
$_SERVER['QUERY_STRING']
;$HTTP_RAW_POST_DATA
(deprecated,
do not use).Note: Don't use $_REQUEST
, as there is a collision risk!
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!
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.
application/vnd.[XYZ]
;Accept
/ Content-Type
headers.Header | Description |
---|---|
`Content-Type` | HTTP message format |
`Accept` | HTTP response format preference |
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 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
Priority | Mime 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 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&limit=10" />
<link rel="first" href="/api/users?page=1&limit=10" />
<link rel="last" href="/api/users?page=1&limit=10" />
</collection>
Must read: Haters gonna HATEOAS.
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
require()
WayInclude 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
require_once()
WayThe 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!
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
{
/* ... */
}
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.
__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');
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
\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.
Like PSR-0, but better:
Access properties as an array:
$tom = new MyObject();
$tom['name'] = 'Tom';
Allow the use of serialize()
and unserialize()
.
Objects implementing JsonSerializable
can customize their JSON representation
when encoded with json_encode()
.
Allow the use of foreach
.
Read more: http://php.net/manual/en/reserved.interfaces.php.
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);
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.
Provides a collection of classes and interfaces:
SplStack
, SplQueue
,
SplObjectStorage
, etc.
LogicException
, InvalidArgumentException
, OutOfRangeException
,
RuntimeException
, etc.
class_parents()
, spl_autoload_register()
, spl_autoload_unregister()
, etc.
Read more about the SPL: http://php.net/manual/en/book.spl.php.
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.
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/.
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!
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
}
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.
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:
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).
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 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.
Typical client request process in MVC architecture:
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:
More on this next week!
More on this in a few minutes!
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();
}
}
<!-- my_template.html -->
<p>Hello, <?php echo $name; ?>!</p>
Even better with PHP 5.4+:
<p>Hello, <?= $name ?>!</p>
$engine = new PhpTemplateEngine('/path/to/templates');
echo $engine->render('my_template.html', [
'name' => 'World',
]);
=> <p>Hello, World!</p>
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/.
{# my_template.html #}
<p>Hello, {{ name }}!</p>
$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>
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,
]);
}
}
Routing is the process of binding URI
s to controllers.
The simplest kind of routing, but also the hardest one to maintain:
web/
├ trees/
│ └ pineapple.php
â”” tree.php
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.
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.
In our context, a database is seen as a server hosting:
records
;tables
or collections
;databases
.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!
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;
}
}
$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);
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();
}
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);
A DAO implements the well-known Create Read Update Delete methods.
Read should not be a single method: Finders to the rescue!
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);
}
/**
* @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();
}
/**
* @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();
}
/**
* @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();
}
// 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.
An object that wraps a row in a database table, encapsulates the database access, and adds domain logic on that data.
$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);
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;
}
}
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
}
}
$banana = new Banana();
$banana->setName('Fantastic Banana');
$con = new Connection('...');
$mapper = new BananaMapper($con);
$mapper->persist($banana);
$mapper->remove($banana);
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];
}
}
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.
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.$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
{
}
$con = new Connection($dsn, $user, $password);
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();
}
}
/**
* @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,
]);
}
Introduces the notion of relations between objects:
An ORM is often considered as a tool that implements some design patterns seen above, and that eases relationships between objects.
$profile = $banana->getProfile();
$bananas = $bananaTree->getBananas();
$roles = [];
foreach ($banana->getBananaRoles() as $bananaRole) {
$roles[] = $bananaRole->getRole();
}
// Or, better:
$roles = $banana->getRoles();
An ORM that implements the Table Data Gateway and Row Data Gateway patterns, often seen as an Active Record approach.
Documentation: www.propelorm.org.
An ORM that implements the Data Mapper pattern.
Documentation: www.doctrine-project.org.
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;
}
}
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;
}
}
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);
}
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 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.
}
}
A findSatisfying()
method can be added to the CustomerRepository
:
interface CustomerRepository
{
...
/**
* @return Customer[]
*/
public function findSatisfying(CustomerSpecification $specification);
}
$specification = new CustomerIsPremium();
$customers = $repository->findSatisfying($specification);
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);
}
}
class NotSpecification implements CustomerSpecification
{
public function __construct(CustomerSpecification $s)
{
$this->s = $s;
}
public function isSatisfiedBy(Customer $c)
{
return !$this->s->isSatisfiedBy($c);
}
}
// 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);
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
}
}
}
Sessions are a way to preserve certain data across subsequent accesses.
// 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.
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
No Authentication/Security Layer, anyone can access everything.
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.
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.
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);
}
}
}
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
});
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 ],
];
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.
$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');
});
$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('/');
});
Useful for API authentication.
http://pretty-rfc.herokuapp.com/RFC2617
Table of contents | t |
---|---|
Exposé | ESC |
Autoscale | e |
Full screen slides | f |
Presenter view | p |
Edit the current slide | s |
Slide numbers | n |
Blank screen | b |
Notes | 2 |
Help | h |