SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 14 day free trial to unlock unlimited reading.
In this presentation, we walk take a flat PHP4-style application and gently migrate it into our own "framework", that uses components from Symfony2, Lithium, Zend Framework and a library called Pimple. By the end, you'll see how any ugly application can take advantage of the many wonderful tools available to PHP developers.
In this presentation, we walk take a flat PHP4-style application and gently migrate it into our own "framework", that uses components from Symfony2, Lithium, Zend Framework and a library called Pimple. By the end, you'll see how any ugly application can take advantage of the many wonderful tools available to PHP developers.
1.
A PHP Christmas Miracle
A story of deception, wisdom, and finding our
common interface
Ryan Weaver
@weaverryan
Saturday, December 3, 11
2.
Who is this dude?
• Co-author of the Symfony2 Docs
• Core Symfony2 contributor
• Co-owner of KnpLabs US
• Fiancee of the much more
talented @leannapelham
http://www.knplabs.com/en
http://www.github.com/weaverryan
@weaverryan
Saturday, December 3, 11
3.
Act 1:
A History of modern PHP
@weaverryan
Saturday, December 3, 11
12.
Lack of sharing means
duplicated efforts
Saturday, December 3, 11
13.
But, there is some hope...
Saturday, December 3, 11
14.
PSR-0
• The PHP community came together, sang
Kumbaya and wrote up some class-naming
standards
• PSR-0 isn’t a library, it’s just an agreement to
name your classes in one of two ways
@weaverryan
Saturday, December 3, 11
15.
PSR-0 with namespaces
Namespace your classes and have the namespaces
follow the directory structure
class: SymfonyComponentHttpFoundationRequest
path: vendor/src/Symfony/Component/HttpFoundation/Request.php
Saturday, December 3, 11
16.
PSR-0 with underscores
Use underscores in your classes and follow the
directory structure
class: Twig_Extension_Core
path: vendor/twig/lib/Twig/Extension/Core.php
Saturday, December 3, 11
17.
But what does this mean?
• An "autoloader" is a tool you can use so that
you don't have to worry about “including”
classes before you use them
• Use anyone’s autoloader
• We're all still duplicating each other's work,
but at least everyone’s autoloader does the
same thing
@weaverryan
Saturday, December 3, 11
18.
What else can we agree on?
Saturday, December 3, 11
20.
But we’ll see how all the
libraries can still work
together
Saturday, December 3, 11
21.
And we can always hope for
a Christmas miracle
Saturday, December 3, 11
22.
Act 2:
Starting with a Horrible App
@weaverryan
Saturday, December 3, 11
23.
How many of you use a
framework on a regular
basis?
Saturday, December 3, 11
24.
How many of you have been
told that it's a bad idea to not
use a framework?
Saturday, December 3, 11
25.
Was I the one who told you
that?
Saturday, December 3, 11
26.
Today we’re going to break
the rules...
Saturday, December 3, 11
27.
... build a framework from
“scratch” ...
Saturday, December 3, 11
28.
... and see why that’s no
longer necessarily a bad
thing.
Saturday, December 3, 11
29.
Today’s 2 goals:
• Refactor a crappy flat PHP application into a
framework that makes sense
• Use as many libraries from as many quarreling
PHP tribes as possible
‣ Symfony
‣ Zend Framework
‣ Lithium
‣ ... only lack of time prevents more...
@weaverryan
Saturday, December 3, 11
30.
Our starting point
• Following along with the code of our app at:
http://bit.ly/php-xmas
• Our app is a single file that fuels two pages
Saturday, December 3, 11
32.
• Shucks, we even have a database connection
Saturday, December 3, 11
33.
Open our Database connection
// index.php
try {
$dbPath = __DIR__.'/data/database.sqlite';
$dbh = new PDO('sqlite:'.$dbPath);
} catch(PDOException $e) {
die('Panic! '.$e->getMessage());
}
Saturday, December 3, 11
34.
Try to get a clean URI
// index.php
$uri = $_SERVER['REQUEST_URI'];
if ($pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
Saturday, December 3, 11
35.
Render the homepage
// index.php
if ($uri == '/' || $uri == '') {
echo '<h1>Welcome to PHP Santa</h1>';
echo '<a href="/letters">Readletters</a>';
if (isset($_GET['name'])) {
echo sprintf(
'<p>Oh, and hello %s!</p>',
$_GET['name']
);
}
}
Saturday, December 3, 11
36.
Print out some letters
// index.php
if ($uri == '/letters') {
$sql = 'SELECT * FROM php_santa_letters';
echo '<h1>Read the letters to PHP Santa</h1>';
echo '<ul>';
foreach ($dbh->query($sql) as $row) {
echo sprintf(
'<li>%s - dated %s</li>',
$row['content'],
$row['received_at']
);
}
echo '</ul>';
}
Saturday, December 3, 11
38.
Great, let’s clean this mess up
Saturday, December 3, 11
39.
Act 3:
Symfony's HTTP Foundation
@weaverryan
Saturday, December 3, 11
40.
Problems
• Our code for trying to get a clean URL is a bit
archaic and probably error prone
• We're echoing content from our controllers,
maybe we can evolve
@weaverryan
Saturday, December 3, 11
41.
Solution
• Symfony’s HttpFoundation Component
• Gives us (among other things) a solid Request
and Response class
@weaverryan
Saturday, December 3, 11
43.
Current Status
@weaverryan
Saturday, December 3, 11
44.
Autoloading
• No matter what framework or libraries you use,
you’ll need an autoloader
• We’ll use Symfony’s “ClassLoader”
• Each PSR-0 autoloader is very similar
@weaverryan
Saturday, December 3, 11
45.
Create a bootstrap file
<?php
// bootstrap.php
require __DIR__.'/vendors/Symfony/Component/
ClassLoader/UniversalClassLoader.php';
use SymfonyComponentClassLoaderUniversalClassLoader;
// setup the autoloader
$loader = new UniversalClassLoader();
$loader->registerNamespace(
'Symfony', __DIR__.'/vendors'
);
$loader->register();
Saturday, December 3, 11
46.
... and include it
<?php
// index.php
require 'bootstrap.php';
// ...
Saturday, December 3, 11
47.
So how does this help?
Saturday, December 3, 11
48.
$uri = $_SERVER['REQUEST_URI'];
if ($pos = strpos($uri, '?')) {
$uri = substr($uri, 0, $pos);
}
... becomes ...
use SymfonyComponentHttpFoundationRequest;
$request = Request::createFromGlobals();
// the clean URI - a lot of logic behind it!!!
$uri = $request->getPathInfo();
Saturday, December 3, 11
49.
if (isset($_GET['name'])) {
echo sprintf(
'<p>Oh, and hello %s!</p>',
$_GET['name']
);
}
... becomes ...
if ($name = $request->query->get('name')) {
echo sprintf(
'<p>Oh, and hello %s!</p>',
$name
);
}
Saturday, December 3, 11
50.
The “Request” object
• Normalizes server variables across systems
• Shortcut methods to common things like
getClientIp(), getHost(), getContent(), etc
• Nice object-oriented interface
@weaverryan
Saturday, December 3, 11
51.
The “Response” object
• We also have a Response object
• Instead of echoing out content, we populate
this fluid object
@weaverryan
Saturday, December 3, 11
52.
header("HTTP/1.1 404 Not Found");
echo '<h1>404 Page not Found</h1>';
echo '<p>This is most certainly *not* an xmas
miracle</p>';
... becomes ...
$content = '<h1>404 Page not Found</h1>';
$content .= '<p>This is most certainly *not*
an xmas miracle</p>';
$response = new Response($content);
$response->setStatusCode(404);
$response->send();
Saturday, December 3, 11
53.
Act 4:
Routing
@weaverryan
Saturday, December 3, 11
54.
Problems
• Our app is a giant gross “if” statement
if ($uri == '/' || $uri == '') {
// ...
} elseif ($uri == '/letters') {
// ...
} else {
// ...
}
• Grabbing a piece from the URL like
/blog/my-blog-post will take some work
@weaverryan
Saturday, December 3, 11
55.
Solution
• Lithium’s Routing library
• Routing matches URIs (e.g. /foo) and returns
information we attached to that URI pattern
• All the nasty regex matching is out-of-sight
@weaverryan
Saturday, December 3, 11
56.
3 Steps to Bringing in an
external tool
Saturday, December 3, 11
57.
#1 Download the library
git submodule add git://github.com/UnionOfRAD/
lithium.git vendors/lithium
Saturday, December 3, 11
58.
#2 Configure the autoloader
// bootstrap.php
// ...
$loader = new UniversalClassLoader();
$loader->registerNamespace('Symfony', __DIR__.'/vendors');
$loader->registerNamespace('lithium', __DIR__.'/vendors');
$loader->register();
Saturday, December 3, 11
59.
#3 Celebrate!
use lithiumnethttpRouter;
$router = new Router();
// ...
Saturday, December 3, 11
60.
Current Status
@weaverryan
Saturday, December 3, 11
61.
So how do we use the router?
Saturday, December 3, 11
62.
Full disclosure: “use”
statements I’m hiding from the
next page
use SymfonyComponentHttpFoundationRequest;
use lithiumnethttpRouter;
use lithiumactionRequest as Li3Request;
Saturday, December 3, 11
63.
a) Map URI to “controller”
$request = Request::createFromGlobals();
$li3Request = new Li3Request();
// get the URL from Symfony's request, give it to lithium
$li3Request->url = $request->getPathInfo();
// create a router, build the routes, and then execute it
$router = new Router();
$router->connect('/letters', array('controller' => 'letters'));
$router->connect('/', array('controller' => 'homepage'));
$router->parse($li3Request);
if (isset($li3Request->params['controller'])) {
$controller = $li3Request->params['controller'];
} else {
$controller = 'error404';
}
Saturday, December 3, 11
64.
b) Execute the controller*
// execute the controller, send the request, get the response
$response = call_user_func_array($controller, array($request));
if (!$response instanceof Response) {
throw new Exception(sprintf(
'WTF! Your controller "%s" didn't return a response!!',
$controller
));
}
$response->send();
* each controller is a flat function
Saturday, December 3, 11
65.
The Controllers
function homepage(Request $request) {
$content = '<h1>Welcome to PHP Santa</h1>';
$content .= '<a href="/letters">Read the letters</a>';
if ($name = $request->query->get('name')) {
$content .= sprintf(
'<p>Oh, and hello %s!</p>',
$name
);
}
return new Response($content);
}
Saturday, December 3, 11
66.
The Controllers
function letters(Request $request)
{
global $dbh; $kitten--
$sql = 'SELECT * FROM php_santa_letters';
$content = '<h1>Read the letters to PHP Santa</h1>';
$content .= '<ul>';
foreach ($dbh->query($sql) as $row) {
$content .= sprintf(
'<li>%s - dated %s</li>',
$row['content'],
$row['received_at']
);
}
$content .= '</ul>';
return new Response($content);
}
Saturday, December 3, 11
67.
The Controllers
function error404(Request $request)
{
$content = '<h1>404 Page not Found</h1>';
$content .= 'This is most certainly *not* an xmas miracle';
$response = new Response($content);
$response->setStatusCode(404);
return $response;
}
Saturday, December 3, 11
68.
The Big Picture
1. Request cleans the URI
2. Router matches the URI to a route, returns a
“controller” string
3. We execute the controller function
4. The controller creates a Response object
5. We send the Response headers and content
@weaverryan
Saturday, December 3, 11
69.
Your 20 line framework
Saturday, December 3, 11
70.
Act 5:
Pimple!
@weaverryan
Saturday, December 3, 11
71.
Problems
• We’ve got lots of random, disorganized
objects floating around
• And we can’t easily access them from within
our controllers
function letters(Request $request)
{
global $dbh;
// ....
}
@weaverryan
Saturday, December 3, 11
72.
Solution
• Pimple! - a Dependency Injection Container
• Dependency Injection Container:
the scariest word we could think of to
describe an array of objects on steroids
@weaverryan
Saturday, December 3, 11
73.
Remember: 3 Steps to
bringing in an external tool
Saturday, December 3, 11
74.
#1 Download the library
git submodule add git://github.com/fabpot/
Pimple.git vendors/Pimple
Saturday, December 3, 11
75.
#2 Configure the autoloader
// bootstrap.php
// ...
require __DIR__.'/vendors/Pimple/lib/Pimple.php';
actually, it’s only one file - so just require it!
Saturday, December 3, 11
76.
#3 Celebrate!
$c = new Pimple();
Saturday, December 3, 11
77.
Pimple Creates Objects
• Use Pimple to create and store your objects in
a central place
• If you have the Pimple container object, then
you have access to every other object in your
application
@weaverryan
Saturday, December 3, 11
78.
Centralize the db connection
$c = new Pimple();
$c['connection'] = $c->share(function() {
$dsn = 'sqlite:'.__DIR__.'/data/database.sqlite';
return new PDO($dsn);
});
Saturday, December 3, 11
79.
Centralize the db connection
$c1 = $c['connection'];
$c2 = $c['connection'];
// they are the same - only one object is created!
$c1 === $c2
Saturday, December 3, 11
80.
Centralize the db connection
$c1 = $c['connection'];
$c2 = $c['connection'];
// they are the same - only one object is created!
$c1 === $c2
Saturday, December 3, 11
81.
Access to what we need
• So far, we’re using a “global” keyword to
access our database connection
• But if we pass around our Pimple container,
we always have access to anything we need -
including the database connection
@weaverryan
Saturday, December 3, 11
82.
Pass the container to the
controller
$c = new Pimple();
// ...
$response = call_user_func_array(
$controller,
array($request, $c)
);
Saturday, December 3, 11
83.
function letters(Request $request, Pimple $c)
{
$dbh = $c['connection']; $kitten++
$sql = 'SELECT * FROM php_santa_letters';
$content = '<h1>Read the letters to PHP Santa</h1>';
$content .= '<ul>';
foreach ($dbh->query($sql) as $row) {
// ...
}
// ...
}
Saturday, December 3, 11
84.
What else?
How about configuration?
Saturday, December 3, 11
85.
$c = new Pimple();
// configuration
$c['connection_string'] = 'sqlite:'.__DIR__
.'/data/database.sqlite';
$c['connection'] = $c->share(function(Pimple $c) {
return new PDO($c['connection_string']);
});
Saturday, December 3, 11
86.
Further?
What about dependencies?
Saturday, December 3, 11
88.
With everything in the
container, our “framework”
just got skinny
Saturday, December 3, 11
89.
$c = new Pimple();
// create objects in Pimple
// execute our routing, merge attributes to request
$result = $c['router']->parse($c['li3_request']);
$c['request']->attributes
->add($c['li3_request']->params);
// get controller and execute!
$controller = $c['request']->attributes
->get('controller', 'error404');
$response = call_user_func_array(
$controller,
array($c['request'], $c)
);
$response->send();
Saturday, December 3, 11
90.
Logging with ZF2
@weaverryan
Saturday, December 3, 11
91.
Problems
• I don’t have enough frameworks in my
framework
• Oh yeah, and we need logging...
@weaverryan
Saturday, December 3, 11
92.
Solution
• Zend Framework2
• ZF2 has a ton of components, including a
logger
@weaverryan
Saturday, December 3, 11
93.
3 Steps to bringing in an
external tool
Saturday, December 3, 11
94.
#1 Download the library
git submodule add git://github.com/
zendframework/zf2.git vendors/zf2
Saturday, December 3, 11
95.
#2 Configure the autoloader
// bootstrap.php
// ...
$loader = new UniversalClassLoader();
$loader->registerNamespace('Symfony', __DIR__.'/vendors');
$loader->registerNamespace('lithium', __DIR__.'/vendors');
$loader->registerNamespace(
'Zend',
__DIR__.'/vendors/zf2/library'
);
$loader->register();
Saturday, December 3, 11
96.
#3 Celebrate!
use ZendLogLogger;
use ZendLogWriterStream;
$logger = Logger($pimple['logger_writer']);
Yes we did just bring in a 100k+ lines of
code for a simple logger :)
Saturday, December 3, 11
97.
Current Status
@weaverryan
Saturday, December 3, 11
98.
Create the Logger in our
Fancy Container
use ZendLogLogger;
use ZendLogWriterStream;
$c['log_path'] = __DIR__.'/data/web.log';
$c['logger_writer'] = $c->share(function($pimple) {
return new Stream($pimple['log_path']);
});
$c['logger'] = $c->share(function($pimple) {
return new Logger($pimple['logger_writer']);
});
Saturday, December 3, 11
99.
And use it anywhere
function error404(Request $request, Pimple $c)
{
$c['logger']->log(
'Crap, 404 for '.$request->getPathInfo(),
Logger::ERR
);
$content = '<h1>404 Page not Found</h1>';
// ...
}
Saturday, December 3, 11
100.
Getting kinda easy, right?
Saturday, December 3, 11
101.
What other libraries can you
think to integrate?
Saturday, December 3, 11
102.
Getting Organized
@weaverryan
Saturday, December 3, 11
103.
Problems
• Our application has 4 major parts:
1) autoloading setup
2) Creation of container
3) Definition of routes
4) Definition of controllers
5) The code that executes everything
• For our business, only #3 and #4 are important
• ... but it’s all jammed together
@weaverryan
Saturday, December 3, 11
104.
Solution
• Some definitions
“Application” - the code that makes you money
“Framework” - under-the-hood code that
impresses your geek friends
• To be productive, let’s “hide” the framework
@weaverryan
Saturday, December 3, 11
105.
Starting point
• Our app basically has 2 files
‣ bootstrap.php: holds autoloading
‣ index.php: holds
- container setup
- definition of routes
- definition of controllers
- the code that executes it all
@weaverryan
Saturday, December 3, 11
106.
Ending point
‣ bootstrap.php: holds
- autoloading
- container setup
- the code that executes it all
(as a function called _run_application())
‣ controllers.php: holds controllers
‣ routes.php: holds routes
‣ index.php: pulls it all together
@weaverryan
Saturday, December 3, 11
107.
Nothing to see here...
<?php
// index.php
$c = require 'bootstrap.php';
require 'routing.php';
require 'controllers.php';
$response = _run_application($c);
$response->send();
Saturday, December 3, 11
109.
Routes have a home
// routing.php
$c['router']->connect(
'/letters',
array('controller' => 'letters')
);
$c['router']->connect(
'/{:name}',
array(
'controller' => 'homepage',
'name' => null
)
);
Saturday, December 3, 11
110.
Controllers have a home
// controllers.php
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
function homepage(Request $request) {
// ...
}
function letters(Request $request, $c)
{
// ...
}
function error404(Request $request)
{
// ...
}
Saturday, December 3, 11
111.
To make $$$, work in
routes.php and controllers.php
Saturday, December 3, 11
112.
Final Thoughts
@weaverryan
Saturday, December 3, 11
113.
It doesn’t matter if you use a
framework...
Saturday, December 3, 11
114.
... inherited a legacy spaghetti
system...
Saturday, December 3, 11
115.
... or practice “not-invented-
here” development
Saturday, December 3, 11
116.
...an innumerable number of
tools are available...
Saturday, December 3, 11
117.
... so you can stop writing
your framework ...
Saturday, December 3, 11
118.
... and start writing your
application
Saturday, December 3, 11
119.
What’s available? Search GitHub
Saturday, December 3, 11
120.
Thanks!
Questions?
Ryan Weaver
@weaverryan
Saturday, December 3, 11
121.
References
• http://bit.ly/php-xmas
• http://github.com/symfony
• https://github.com/UnionOfRAD/lithium
• https://github.com/zendframework/zf2
• http://pimple.sensiolabs.org/
And if we had more time...
• http://twig.sensiolabs.org/
• https://github.com/knplabs/KnpMenu
• https://github.com/Seldaek/monolog
• https://github.com/avalanche123/Imagine
@weaverryan
Saturday, December 3, 11