All web application frameworks suck.
Some are too complex for the task at hand, and others don’t offer enough flexibility when your application steps outside of the confines of the ubiquitous blog tutorial. As stated by the venerable Sean Coates: “the #1 reason to avoid frameworks: you’ll spend all your time working around edge cases.”
Lithium, a new PHP 5.3+ rapid application development framework started by several CakePHP core alumnus tired of the status quo, is designed to help you get the job done, and get out of your way. Built from the ground-up to cater to people who hate frameworks, it attempts to reduce edge cases, and expose an intelligent public interface that sucks less.
We’ll take a jolly jaunt through the internals of Lithium and examine how we’re leveraging closures, late static binding and anonymous functions made available in PHP 5.3 to write a framework that Sucks Less, including our one-of-a-kind aspect-oriented inspired filter architecture, adapter-based architecture, and first-class support for non-relational datastores such as MongoDB and CouchDB.
40. class User {
function __construct($storage) {
$this->storage = $storage;
}
}
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
41. Show that object who’s boss.
class User {
function __construct($storage) {
$this->storage = $storage;
}
}
$storage = new SessionStorage('SESSION_ID');
$user = new User($storage);
42. Of course, we want this to abstract this.
Frameworks adore abstractions.
57. So....
We have Dependency Injection.
Managed by a Service Container.
Parametrized with XML data.
And the whole thing configured by a Builder.
58. So....
We have Dependency Injection.
Managed by a Service Container.
Parametrized with XML data.
And the whole thing configured by a Builder.
...to fix one problem.
69. class User {
public function create() {
$logger = new Logger();
$logger->write('Creating a new user...');
$this->_doSomeInitialization();
$this->_databaseConnection->doATransaction($this)->create();
$logger->write('Finished creating user');
}
}
$user = new User();
$user->create();
70. class User {
public function create() {
$logger = new Logger();
$logger->write('Creating a new user...');
$this->_doSomeInitialization();
$this->_databaseConnection->doATransaction($this)->create();
$logger->write('Finished creating user');
}
}
$user = new User();
$user->create();
74. Dependency Injection
• Fixes the problem of static dependencies
• Ignores the problem of static relationships
• Same methods called on injected classes
75. Dependency Injection
• Fixes the problem of static dependencies
• Ignores the problem of static relationships
• Same methods called on injected classes
• No way to introduce new relationships
76. Dependency Injection
• Fixes the problem of static dependencies
• Ignores the problem of static relationships
• Same methods called on injected classes
• No way to introduce new relationships
• Higher overhead, more boilerplate code
98. Design patterns
• Each pattern is only useful in a limited
context
• Layering many design patterns on top of
each other often indicates poor design
choices
99. Design patterns
• Each pattern is only useful in a limited
context
• Layering many design patterns on top of
each other often indicates poor design
choices
• Mis-application arises from trying to run
before you can walk
109. Aspect-Oriented Design
• Separation of concerns
• Domain classes should not know or care about cross-
cutting concerns
• Examples:
• Caching
110. Aspect-Oriented Design
• Separation of concerns
• Domain classes should not know or care about cross-
cutting concerns
• Examples:
• Caching
• Logging
111. Aspect-Oriented Design
• Separation of concerns
• Domain classes should not know or care about cross-
cutting concerns
• Examples:
• Caching
• Logging
• Access Control, etc.
126. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ...
}
}
?>
127. public function __construct(array $config = array())
<?php
public function __construct(array $config = array())
namespace applicationbar;
public function __construct(array $config = array())
use public function __construct(array $config = array())
lithiumutilString;
use lithiumutilCollection;
public function __construct(array $config = array())
class Foo extends lithiumcoreObject {
public function __construct(array $config = array())
protected $_classes = array(
public function'lithiumstorageCache', = array())
'cache' => __construct(array $config
'logger' => 'lithiumanalysisLogger'
public function __construct(array $config = array())
);
public function __construct(array $config = array()) {
// ...
}
public function __construct(array $config = array())
protected function _init() {
public function __construct(array $config = array())
// ...
}
public function __construct(array $config = array())
}
public function __construct(array $config = array())
?>
public function __construct(array $config = array())
128. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ...
}
}
?>
129. <?php
<?php
namespace applicationbar;
class Foo extends lithiumcoreObject
use lithiumutilString; {
use lithiumutilCollection;
protected function _init() {
class Foo extends lithiumcoreObject {
$or = $some->highOverHead($operation);
$or()->otherwise(HARD_TO_TEST)->code();
protected $_classes = array(
'cache' => 'lithiumstorageCache',
}
'logger' => 'lithiumanalysisLogger'
}
);
?>
public function __construct(array $config = array()) {
// ...
}
protected function _init() { 2
// ...
}
}
?>
130. <?php
<?php
namespace applicationbar;
class Foo extends lithiumcoreObject
use lithiumutilString; {
use lithiumutilCollection;
protected function _init() {
class Foo extends lithiumcoreObject {
$or = $some->highOverHead($operation);
$or()->otherwise(HARD_TO_TEST)->code();
protected $_classes = array(
'cache' => 'lithiumstorageCache',
}
'logger' => 'lithiumanalysisLogger'
}
);
?>
public function __construct(array $config = array()) {
// ...
}
protected function _init() { 2
// ...
}
}
$foo = new Foo(array('init' => false));
?>
131. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ...
}
}
?>
132. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ...
}
}
?>
133. <?php
namespace applicationbar;
3
use lithiumutilString;
use lithiumutilCollection;
new applicationbarFoo();
// loads app/bar/Foo.php
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ...
}
}
?>
134. <?php
namespace applicationbar;
4
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ...
}
}
?>
135. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
5
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ... = $this->_classes['cache'];
$cache
} $cache::write(__CLASS__, $this->_someGeneratedValue());
} }
}
?>
?>
136. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
5
);
$foo = new Foo(array('classes' => array(
public function __construct(array $config = array()) {
'cache' => 'applicationextensionsCache'
// ...
)));
}
protected function _init() {
// ... = $this->_classes['cache'];
$cache
} $cache::write(__CLASS__, $this->_someGeneratedValue());
} }
}
?>
?>
137. <?php
namespace applicationbar;
use lithiumutilString;
use lithiumutilCollection;
class Foo extends lithiumcoreObject {
protected $_classes = array(
'cache' => 'lithiumstorageCache',
'logger' => 'lithiumanalysisLogger'
);
public function __construct(array $config = array()) {
// ...
}
protected function _init() {
// ... = $this->_classes['cache'];
$cache
} $cache::write(__CLASS__, $this->_someGeneratedValue());
} }
}
?>
?>
164. Zoom?
• Performance vs. speed of development is a
series of trade-offs
• Large-scale apps don’t use stock framework
infrastructure, and that’s a good thing
165. Zoom?
• Performance vs. speed of development is a
series of trade-offs
• Large-scale apps don’t use stock framework
infrastructure, and that’s a good thing
• A generalized framework will never be as
fast as hand-tuned code
168. Zoom!
• Choice is good
• Use native extensions (PECL) whenever
possible.
169. Zoom!
• Choice is good
• Use native extensions (PECL) whenever
possible.
• Don’t like a class? Change it. At runtime.
170. Zoom!
• Choice is good
• Use native extensions (PECL) whenever
possible.
• Don’t like a class? Change it. At runtime.
• Profiled at every step of the way.
204. Databases
• 1st-class support for document-oriented
databases
• MongoDB & CouchDB: production ready
• Relational databases in beta
• Cassandra in the works, too
205. <?php
$post = Post::create(array(
'title' => 'Ein bier, bitte',
'body' => 'Was ist los mit dir?'
));
$post->save();
$post = Post::find($id);
?>
<h2><?=$post->title; ?></h2>
<p><?=$post->body; ?></p>
213. Databases
• Adapter based, plugin aware
• Will ship with MySQL, SQLite
• SQL Server support via plugin
• Query API
214. The Query API
• Flexible data container
• Allows each backend data store to only worry
about features it implements
• Keeps model API separate from backend data
sources
217. The Query API
• Run simple queries via the Model API
• Build your own complex queries with the
Query API
• Create your own adapter, or drop in a
custom query optimizer
219. Plays nice with others
• Easily load & use libraries from other
frameworks:
• Zend Framework, Solar, Symfony, PEAR,
etc.
• PSR-0 Class-loading standard
221. namespace appcontrollers;
use Zend_Mail_Storage_Pop3;
class EmailController extends lithiumactionController {
public function index() {
$mail = new Zend_Mail_Storage_Pop3(array(
'host' => 'localhost', 'user' => 'test', 'password' => 'test'
));
return compact('mail');
}
}
222.
223. This has been a presentation by:
Nate Abele (@nateabele)
Joël Perras (@jperras)
Sucks. But check it out anyway.
Notas del editor
Introductions: team
Handoff: Nate
Intro / overview: Nate
Handoff: Jo&#xEB;l
Handoff: Nate
- They&#x2019;re all about knowing when and how to apply them
- Often, patterns derived from other languages make little to no sense in PHP
Handoff: Jo&#xEB;l
Handoff: Nate
- Many GOF patterns were invented for Java to overcome deficiencies in the language itself
- You don&#x2019;t need a dependency injection container in PHP, it&#x2019;s pointless
- In languages with first-class functions (PHP as of 5.3), patterns like Command and Visitor are irrelevant
- Method calls and direction of invocation remain static
- Each individual cross-cutting concern adds more boilerplate code
- Method calls and direction of invocation remain static
- Each individual cross-cutting concern adds more boilerplate code
- Method calls and direction of invocation remain static
- Each individual cross-cutting concern adds more boilerplate code
- Method calls and direction of invocation remain static
- Each individual cross-cutting concern adds more boilerplate code
- Method calls and direction of invocation remain static
- Each individual cross-cutting concern adds more boilerplate code
- Encapsulation notwithstanding, OO does not address the problem of state.
- No way to know the value of _timeout.
- Mutable state is the source of most software defects
- Computers are good at dynamic process flows. Humans aren&#x2019;t.
Handoff: Jo&#xEB;l, talking about mutable state
- Easy != right.
Handoff: Nate
- But it&#x2019;s okay... because &#x201C;Design Patterns&#x201D; make everything okay
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
- Everyone has their favorites...
Design patterns are not a golden hammer
Handoff: Jo&#xEB;l
Handoff: Nate
- A referentially transparent function is one whose return value is only dependent on input parameters
Handoff: Nate
- A referentially transparent function is one whose return value is only dependent on input parameters
Handoff: Nate
- A referentially transparent function is one whose return value is only dependent on input parameters
Handoff: Nate
- A referentially transparent function is one whose return value is only dependent on input parameters
Handoff: Nate
- A referentially transparent function is one whose return value is only dependent on input parameters
Handoff: Nate
- A referentially transparent function is one whose return value is only dependent on input parameters
- Learning other paradigms helps you in the same way as learning other languages.
Handoff: Jo&#xEB;l
- Rant about programmers vs. typists
- This is different from assuming you won&#x2019;t make mistakes
- We don&#x2019;t dumb down APIs or hide things. We just make it clear what you&#x2019;re doing.
- This is different from assuming you won&#x2019;t make mistakes
- We don&#x2019;t dumb down APIs or hide things. We just make it clear what you&#x2019;re doing.
- This is different from assuming you won&#x2019;t make mistakes
- We don&#x2019;t dumb down APIs or hide things. We just make it clear what you&#x2019;re doing.
Handoff: Nate
- Supported by a majority of methods in the framework
- *Always* the last parameter
- Any settings contained only modify behavior for the duration of the call
- Managing state is a very important idea which Lithium puts a lot of emphasis on.
- Delineation between configuration state and request state
- Managing state is a very important idea which Lithium puts a lot of emphasis on.
- Delineation between configuration state and request state
- Managing state is a very important idea which Lithium puts a lot of emphasis on.
- Delineation between configuration state and request state
- This works because of the universal constructor
- This works because of the universal constructor
Handoff: Jo&#xEB;l
- Use components from other libraries or frameworks, or hand-written classes
- Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework
- Use components from other libraries or frameworks, or hand-written classes
- Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework
- Use components from other libraries or frameworks, or hand-written classes
- Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework
- Use components from other libraries or frameworks, or hand-written classes
- Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework
- This is for when you have a frequently-used API call, or other request that you need to make extra fast
- Bypasses the entire framework request cycle
Handoff: Nate
Point out parts that are `before` method execution, and `after` method execution.
Point out parts that are `before` method execution, and `after` method execution.
Point out parts that are `before` method execution, and `after` method execution.
Point out parts that are `before` method execution, and `after` method execution.
- either pass it around or make it global
- either pass it around or make it global
- either pass it around or make it global
- You can rip it out and things will still work
- Relies on native language constructs, no micro-syntax
- You can rip it out and things will still work
- Relies on native language constructs, no micro-syntax
Handoff: Jo&#xEB;l
Handoff: Nate
- Explain about SQL operators being translated to Mongo commands
- Helps transitioning from relational DBs to Mongo
- Aids in developing cross-functional plugins
Handoff: Jo&#xEB;l
Everything works because of the magical Query API
Handoff: Nate
Allows you to do &#x201C;non-standard&#x201D; stuff
Generates a SELECT statement with a subselect that finds all posts tagged foo, bar or baz
- For all your &#x201C;enterprise&#x201D; needs, we have an officially-maintained plugin for fully integrating with Doctrine 2
- PDO abstraction layer with support for many RDBMS systems
- Accessed through the same Model API as standard Lithium backends