• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Tek-X: A Framework for People who Hate Frameworks - Lithium
 

Tek-X: A Framework for People who Hate Frameworks - Lithium

on

  • 6,322 views

All web application frameworks suck....

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.

Statistics

Views

Total Views
6,322
Views on SlideShare
5,962
Embed Views
360

Actions

Likes
15
Downloads
96
Comments
0

7 Embeds 360

http://francescoagati.wordpress.com 215
http://www.slideshare.net 138
https://francescoagati.wordpress.com 2
http://www.php-talks.com 2
http://facebook.slideshare.com 1
http://paper.li 1
http://linyo.ws 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • Introductions: team <br />
  • Handoff: Nate <br />
  • Intro / overview: Nate <br />
  • Handoff: Jo&#xEB;l <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Handoff: Nate <br /> - They&#x2019;re all about knowing when and how to apply them <br /> - Often, patterns derived from other languages make little to no sense in PHP <br />
  • <br />
  • <br />
  • Handoff: Jo&#xEB;l <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Handoff: Nate <br /> - Many GOF patterns were invented for Java to overcome deficiencies in the language itself <br /> - You don&#x2019;t need a dependency injection container in PHP, it&#x2019;s pointless <br /> - In languages with first-class functions (PHP as of 5.3), patterns like Command and Visitor are irrelevant <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • - Method calls and direction of invocation remain static <br /> - Each individual cross-cutting concern adds more boilerplate code <br />
  • - Method calls and direction of invocation remain static <br /> - Each individual cross-cutting concern adds more boilerplate code <br />
  • - Method calls and direction of invocation remain static <br /> - Each individual cross-cutting concern adds more boilerplate code <br />
  • - Method calls and direction of invocation remain static <br /> - Each individual cross-cutting concern adds more boilerplate code <br />
  • - Method calls and direction of invocation remain static <br /> - Each individual cross-cutting concern adds more boilerplate code <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • - Encapsulation notwithstanding, OO does not address the problem of state. <br />
  • - No way to know the value of _timeout. <br /> - Mutable state is the source of most software defects <br /> - Computers are good at dynamic process flows. Humans aren&#x2019;t. <br /> <br /> Handoff: Jo&#xEB;l, talking about mutable state <br />
  • - Easy != right. <br />
  • Handoff: Nate <br /> <br /> - But it&#x2019;s okay... because &#x201C;Design Patterns&#x201D; make everything okay <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • - Everyone has their favorites... <br />
  • Design patterns are not a golden hammer <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Handoff: Jo&#xEB;l <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Handoff: Nate <br /> - A referentially transparent function is one whose return value is only dependent on input parameters <br />
  • Handoff: Nate <br /> - A referentially transparent function is one whose return value is only dependent on input parameters <br />
  • Handoff: Nate <br /> - A referentially transparent function is one whose return value is only dependent on input parameters <br />
  • Handoff: Nate <br /> - A referentially transparent function is one whose return value is only dependent on input parameters <br />
  • Handoff: Nate <br /> - A referentially transparent function is one whose return value is only dependent on input parameters <br />
  • Handoff: Nate <br /> - A referentially transparent function is one whose return value is only dependent on input parameters <br />
  • - Learning other paradigms helps you in the same way as learning other languages. <br /> <br /> Handoff: Jo&#xEB;l <br /> - Rant about programmers vs. typists <br /> <br />
  • - This is different from assuming you won&#x2019;t make mistakes <br /> - We don&#x2019;t dumb down APIs or hide things. We just make it clear what you&#x2019;re doing. <br /> <br />
  • - This is different from assuming you won&#x2019;t make mistakes <br /> - We don&#x2019;t dumb down APIs or hide things. We just make it clear what you&#x2019;re doing. <br /> <br />
  • - This is different from assuming you won&#x2019;t make mistakes <br /> - We don&#x2019;t dumb down APIs or hide things. We just make it clear what you&#x2019;re doing. <br /> <br />
  • <br />
  • Handoff: Nate <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • - Supported by a majority of methods in the framework <br /> - *Always* the last parameter <br /> - Any settings contained only modify behavior for the duration of the call <br />
  • <br />
  • <br />
  • <br />
  • - Managing state is a very important idea which Lithium puts a lot of emphasis on. <br /> - Delineation between configuration state and request state <br />
  • - Managing state is a very important idea which Lithium puts a lot of emphasis on. <br /> - Delineation between configuration state and request state <br />
  • - Managing state is a very important idea which Lithium puts a lot of emphasis on. <br /> - Delineation between configuration state and request state <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • - This works because of the universal constructor <br />
  • - This works because of the universal constructor <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Handoff: Jo&#xEB;l <br />
  • <br />
  • <br />
  • <br />
  • - Use components from other libraries or frameworks, or hand-written classes <br /> - Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework <br />
  • - Use components from other libraries or frameworks, or hand-written classes <br /> - Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework <br />
  • - Use components from other libraries or frameworks, or hand-written classes <br /> - Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework <br />
  • - Use components from other libraries or frameworks, or hand-written classes <br /> - Filter system allows class interdependencies to be pushed to the surface, instead of buried within the framework <br />
  • <br />
  • - This is for when you have a frequently-used API call, or other request that you need to make extra fast <br /> - Bypasses the entire framework request cycle <br />
  • Handoff: Nate <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Point out parts that are `before` method execution, and `after` method execution. <br />
  • Point out parts that are `before` method execution, and `after` method execution. <br />
  • Point out parts that are `before` method execution, and `after` method execution. <br />
  • Point out parts that are `before` method execution, and `after` method execution. <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • - either pass it around or make it global <br />
  • - either pass it around or make it global <br />
  • - either pass it around or make it global <br />
  • - You can rip it out and things will still work <br /> - Relies on native language constructs, no micro-syntax <br />
  • - You can rip it out and things will still work <br /> - Relies on native language constructs, no micro-syntax <br />
  • Handoff: Jo&#xEB;l <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • <br />
  • Handoff: Nate <br /> - Explain about SQL operators being translated to Mongo commands <br /> - Helps transitioning from relational DBs to Mongo <br /> - Aids in developing cross-functional plugins <br />
  • Handoff: Jo&#xEB;l <br /> Everything works because of the magical Query API <br />
  • Handoff: Nate <br />
  • Allows you to do &#x201C;non-standard&#x201D; stuff <br />
  • Generates a SELECT statement with a subselect that finds all posts tagged foo, bar or baz <br />
  • <br />
  • - For all your &#x201C;enterprise&#x201D; needs, we have an officially-maintained plugin for fully integrating with Doctrine 2 <br /> - PDO abstraction layer with support for many RDBMS systems <br /> - Accessed through the same Model API as standard Lithium backends <br />
  • Handoff: Jo&#xEB;l <br />
  • <br />
  • <br />
  • <br />
  • <br />

Tek-X: A Framework for People who Hate Frameworks - Lithium Tek-X: A Framework for People who Hate Frameworks - Lithium Presentation Transcript

  • A Framework for People Who Hate Frameworks.
  • Find us on : http://joind.in/1592
  • A movement in 3 parts • Frameworks suck • Everything you know is wrong • Lithium tries to suck less
  • The first part.
  • Let’s get one thing out of the way:
  • Lithium sucks.
  • But that’s okay.
  • Because your framework sucks, too.
  • And yes, I mean:
  • And yes, I mean:
  • Why? (Besides the obvious attempt at being provocative)
  • Frameworks Suck
  • Frameworks Suck • Code you will never use.
  • Frameworks Suck • Code you will never use. • Complexity overhead.
  • Frameworks Suck • Code you will never use. • Complexity overhead. • You didn’t write it.
  • Also,
  • Also, Martin Fowler.
  • His name is the biggest, so it’s his fault.
  • His name is the biggest, so it’s his fault.
  • We’re not saying design patterns are bad. Quite the opposite.
  • Lithium implements many design patterns.
  • The Problem™
  • Some patterns only treat the symptoms, instead of the cause.
  • Some examples:
  • Object dependencies. “The principle of separating configuration from use.”
  • [CakePHP code] function initialize(&$controller, $settings = array()) { /* ...[snip]... */ $prefixes = Router::prefixes(); if (!empty($prefixes)) { foreach ($prefixes as $prefix) { /* ...[do something nasty to the global state]... */ } } if (Configure::read() > 0) { App::import('Debugger'); Debugger::checkSecurityKeys(); } }
  • Sucks. function initialize(&$controller, $settings = array()) { /* ...[snip]... */ $prefixes = Router::prefixes(); if (!empty($prefixes)) { foreach ($prefixes as $prefix) { /* ...[do something nasty to the global state]... */ } } if (Configure::read() > 0) { App::import('Debugger'); Debugger::checkSecurityKeys(); } }
  • Sucks. function initialize(&$controller, $settings = array()) { /* ...[snip]... */ $prefixes = Router::prefixes(); if (!empty($prefixes)) { foreach ($prefixes as $prefix) { /* ...[do something nasty to the global state]... */ } } if (Configure::read() > 0) { App::import('Debugger'); Debugger::checkSecurityKeys(); } } Sucks hard.
  • Configuration.
  • Everyone does it differently.
  • Sometimes in the same framework.
  • Sometimes in the same class.
  • function spam($emails) { $this->Email->replyTo = 'nigerian.prince@example.com'; $this->Email->from = 'Desmond Etete <nigerian.prince@example.com>'; $this->Email->template = 'mo_monies'; $this->Email->sendAs = 'annoying_html'; foreach ($emails as $email) { $this->Email->to = $email['address']; $this->Email->subject = "Good to you news I have {$email['name']}"; $this->Email->send(); } }
  • Sucks function spam($emails) { $this->Email->replyTo = 'nigerian.prince@example.com'; $this->Email->from = 'Desmond Etete <nigerian.prince@example.com>'; $this->Email->template = 'mo_monies'; $this->Email->sendAs = 'annoying_html'; foreach ($emails as $email) { $this->Email->to = $email['address']; $this->Email->subject = "Good to you news I have {$email['name']}"; $this->Email->send(); } }
  • Dependency injection.
  • A good idea!
  • ... or is it?
  • [variables changed to protect the innocent.]
  • [variables changed to protect the innocent.] [... not really. It’s Symfony.]
  • class User { function __construct($storage) { $this->storage = $storage; } } $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
  • Show that object who’s boss. class User { function __construct($storage) { $this->storage = $storage; } } $storage = new SessionStorage('SESSION_ID'); $user = new User($storage);
  • Of course, we want this to abstract this. Frameworks adore abstractions.
  • DI Container to the rescue!
  • class Container { static protected $shared = array(); // ... public function getMailer() { if (isset(self::$shared['mailer'])) { return self::$shared['mailer']; } $class = $this->parameters['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransport()); return self::$shared['mailer'] = $mailer; } }
  • But now you want an abstraction to manage the DI container. Duh.
  • So you create a “Service Container”.
  • class Container extends sfServiceContainer { static protected $shared = array(); protected function getMailTransportService() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => $this['mailer.username'], 'password' => $this['mailer.password'], 'ssl' => 'ssl', 'port' => 465, )); } }
  • Of course, we now want to abstract the crap out of the Service Container. Double duh.
  • So lets use a Builder to configure the Services.
  • Yeah! require_once '/PATH/TO/sfServiceContainerAutoloader.php'; sfServiceContainerAutoloader::register(); $sc = new sfServiceContainerBuilder(); $sc-> register('mail.transport', 'Zend_Mail_Transport_Smtp')-> addArgument('smtp.gmail.com')-> addArgument(array( 'auth' => 'login', 'username' => '%mailer.username%', 'password' => '%mailer.password%', 'ssl' => 'ssl', 'port' => 465, ))-> setShared(false) ; $sc-> register('mailer', '%mailer.class%')-> addMethodCall('setDefaultTransport', array(new sfServiceReference('mail.transport'))) ;
  • And for good measure, have our configurations for Service Containers in XML files.
  • So....
  • So.... We have Dependency Injection.
  • So.... We have Dependency Injection. Managed by a Service Container.
  • So.... We have Dependency Injection. Managed by a Service Container. Parametrized with XML data.
  • So.... We have Dependency Injection. Managed by a Service Container. Parametrized with XML data. And the whole thing configured by a Builder.
  • 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.
  • Anyone see what’s wrong here?
  • Many of these patterns were implemented in Java, to solve language problems that PHP just doesn’t have.
  • Try this some time...
  • The second part.
  • Everything you know is wrong.
  • The sun does not revolve around OOP Galileo facing the Roman Inquistion - Cristiano Banti (1857)
  • The sun does not revolve around OOP ...nevertheless, it moves. Galileo facing the Roman Inquistion - Cristiano Banti (1857)
  • Dependency injection.
  • Dependency Injection =
  • 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();
  • 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();
  • Dependency Injection
  • Dependency Injection • Fixes the problem of static dependencies
  • Dependency Injection • Fixes the problem of static dependencies • Ignores the problem of static relationships
  • Dependency Injection • Fixes the problem of static dependencies • Ignores the problem of static relationships • Same methods called on injected classes
  • 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
  • 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
  • class Service extends lithiumcoreObject { protected $_classes = array( 'request' => 'lithiumnethttpRequest', 'response' => 'lithiumnethttpResponse', 'socket' => 'lithiumnetsocketContext' ); protected function _init() { $class = Libraries::locate('socket.util', $this->_classes['socket']); $this->_connection = new $class($this->_config); // ... } /* ...[snip]... */ public function send($method, $path = null, $data = null, array $options = array()) { /* ...[snip]... */ $response = $this->_connection->send($request, array('classes' => $this->_classes)); // ... } }
  • class Service extends lithiumcoreObject { protected $_classes = array( 'request' => 'lithiumnethttpRequest', 'response' => 'lithiumnethttpResponse', 'socket' => 'lithiumnetsocketContext' ); protected function _init() { $class = Libraries::locate('socket.util', $this->_classes['socket']); $this->_connection = new $class($this->_config); // ... } /* ...[snip]... */ public function send($method, $path = null, $data = null, array $options = array()) { /* ...[snip]... */ $response = $this->_connection->send($request, array('classes' => $this->_classes)); // ... } }
  • Coupling should be in proportion to domain relevance.
  • The problem of state.
  • If...
  • If... Configure::write('debug', 0);
  • If... Configure::write('debug', 0); is evil,
  • If... Configure::write('debug', 0); is evil, $this->debug = 0;
  • If... Configure::write('debug', 0); is evil, $this->debug = 0; is the
  • of evil.
  • class Service { protected $_timeout = 30; public function send($method, $data = null, array $options = array()) { // WTF does this do? $this->_prepare(); $response = $this->_connection->send($request, array( 'timeout' => $this->_timeout )); // ... } }
  • OO doesn’t make you think (about state).
  • Design patterns.
  • ActiveRecord Data Access Object Unit of Work Dependency Injection Registry Front Controller MVC Value Object Data Mapper Service Layer
  • L E F A
  • Design patterns
  • Design patterns • Each pattern is only useful in a limited context
  • 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
  • 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
  • Tools do not mean... ...you can build a house.
  • The third part.
  • Lithium tries to suck less.
  • Un-broken solutions.
  • Aspect-Oriented Design
  • Aspect-Oriented Design • Separation of concerns
  • Aspect-Oriented Design • Separation of concerns • Domain classes should not know or care about cross- cutting concerns
  • Aspect-Oriented Design • Separation of concerns • Domain classes should not know or care about cross- cutting concerns • Examples:
  • Aspect-Oriented Design • Separation of concerns • Domain classes should not know or care about cross- cutting concerns • Examples: • Caching
  • Aspect-Oriented Design • Separation of concerns • Domain classes should not know or care about cross- cutting concerns • Examples: • Caching • Logging
  • Aspect-Oriented Design • Separation of concerns • Domain classes should not know or care about cross- cutting concerns • Examples: • Caching • Logging • Access Control, etc.
  • Functional Programming
  • Functional Programming • Only possible when functions are first-class citizens
  • Functional Programming • Only possible when functions are first-class citizens • Referential transparency
  • Functional Programming • Only possible when functions are first-class citizens • Referential transparency • Function purity
  • Referential transparency is not...
  • Referential transparency is not... $this date() $_*
  • Referential transparency is not... $this date() $_*
  • These Are Not Design Patterns.
  • Less Suck
  • Less Suck • Draws on years of experience building web frameworks
  • Less Suck • Draws on years of experience building web frameworks • PHP 5.3+ only
  • Less Suck • Draws on years of experience building web frameworks • PHP 5.3+ only • Doesn’t assume you’re stupid
  • Ways we suck less:
  • Consistency.
  • <?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() { // ... } } ?>
  • 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())
  • <?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() { // ... } } ?>
  • <?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 // ... } } ?>
  • <?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)); ?>
  • <?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() { // ... } } ?>
  • <?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() { // ... } } ?>
  • <?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() { // ... } } ?>
  • <?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() { // ... } } ?>
  • <?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()); } } } ?> ?>
  • <?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()); } } } ?> ?>
  • <?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()); } } } ?> ?>
  • $options = array()
  • Keeps parameter lists short & Makes class APIs more extensible
  • $config = array()
  • Same idea. But...! Modifies class / object state.
  • Adaptable
  • Adaptable Auth Cache Catalog Connections Logger Session
  • use lithiumsecurityAuth; Auth::config(array( 'customer' => array( 'adapter' => 'Form', 'model' => 'Customer', 'fields' => array('email', 'password') ) ));
  • use lithiumstorageCache; Cache::config(array( 'local' => array('adapter' => 'Apc'), 'distributed' => array( 'adapter' => 'Memcached', 'servers' => array('127.0.0.1', 11211), ), 'default' => array('adapter' => 'File') ));
  • use lithiumdataConnections; Connections::config(array( 'old' => array( 'type' => 'database', 'adapter' => 'MySql', 'user' => 'bobby_tables', 'password' => '******', 'database' => 'my_app' ), 'new' => array( 'type' => 'MongoDb', 'database' => 'my_app' ) ));
  • use lithiumstorageSession; Session::config(array( 'cookie' => array( 'adapter' => 'Cookie', 'expire' => '+2 days' ), 'default' => array('adapter' => 'Php') ));
  • Also fun:
  • use lithiumstorageSession; Session::config(array( 'default' => array( 'adapter' => 'MyCustomAdapter', 'expires' => '+2 days', 'custom' => 'Whatevah!' ) ));
  • use lithiumstorageSession; Session::config(array( 'default' => array( 'adapter' => 'MyCustomAdapter', 'expires' => '+2 days', 'custom' => 'Whatevah!' ) ));
  • use lithiumstorageSession; Session::config(array( 'default' => array( 'adapter' => 'MyCustomAdapter', 'expires' => '+2 days', 'custom' => 'Whatevah!' ) )); public function __construct(array $config = array())
  • Multiple environments?
  • use lithiumstorageCache; Cache::config(array( 'default' => array( 'development' => array( 'adapter' => 'Apc' ), 'production' => array( 'adapter' => 'Memcached', 'servers' => array('127.0.0.1', 11211) ) ) ));
  • use lithiumstorageCache; Cache::config(array( 'default' => array( 'development' => array( 'adapter' => 'Apc' ), 'production' => array( 'adapter' => 'Memcached', 'servers' => array('127.0.0.1', 11211) ) ) ));
  • Works identically for all adapters.
  • If you remember nothing else about configuration state...
  • Immutability. Set it and forget it.
  • Performance.
  • Zoom?
  • Zoom? • Performance vs. speed of development is a series of trade-offs
  • 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
  • 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
  • Zoom!
  • Zoom! • Choice is good
  • Zoom! • Choice is good • Use native extensions (PECL) whenever possible.
  • Zoom! • Choice is good • Use native extensions (PECL) whenever possible. • Don’t like a class? Change it. At runtime.
  • 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.
  • Example
  • use appmodelsPost; use lithiumactionResponse; use lithiumnethttpRouter; Router::connect('/frequent_api_call.json', array(), function($request) { return new Response(array( 'type' => 'application/json', 'body' => Post::recent()->to('json') )); });
  • Flexibility.
  • Lithium is the most flexible framework. (yeah, we said it)
  • Most class dependencies are dynamic.
  • class Service extends lithiumcoreObject { protected $_classes = array( 'request' => 'lithiumnethttpRequest', 'response' => 'lithiumnethttpResponse', 'socket' => 'lithiumnetsocketContext' ); } $service = new Service(array('classes' => array( 'socket' => 'mycustomSocket' )));
  • The Filter System: Aspect-Oriented Design for PHP.
  • Example: Caching & Logging
  • Example: Caching & Logging Post::find()
  • Example: Caching & Logging Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • Example: Caching & Logging Caching Logging Post::find()
  • use lithiumanalysisLogger; Post::applyFilter('find', function($self, $params, $chain) { // Generate the log message $conditions = $params['options']['conditions']; $message = 'Post query with constraint ' . var_export($conditions, true); Logger::write('info', $message); return $chain->next($self, $params, $chain); });
  • use lithiumanalysisLogger; Post::applyFilter('find', function($self, $params, $chain) { // Generate the log message $conditions = $params['options']['conditions']; $message = 'Post query with constraint ' . var_export($conditions, true); Logger::write('info', $message); return $chain->next($self, $params, $chain); });
  • use lithiumanalysisLogger; Post::applyFilter('find', function($self, $params, $chain) { // Generate the log message $conditions = $params['options']['conditions']; $message = 'Post query with constraint ' . var_export($conditions, true); Logger::write('info', $message); return $chain->next($self, $params, $chain); });
  • use lithiumanalysisLogger; Post::applyFilter('find', function($self, $params, $chain) { // Generate the log message $conditions = $params['options']['conditions']; $message = 'Post query with constraint ' . var_export($conditions, true); Logger::write('info', $message); return $chain->next($self, $params, $chain); });
  • use lithiumanalysisLogger; Post::applyFilter('find', function($self, $params, $chain) { // Generate the log message $conditions = $params['options']['conditions']; $message = 'Post query with constraint ' . var_export($conditions, true); Logger::write('info', $message); return $chain->next($self, $params, $chain); });
  • use lithiumanalysisLogger; Post Logger Post::applyFilter('find', function($self, $params, $chain) { // Generate the log message $conditions = $params['options']['conditions']; $message = 'Post query with constraint ' . var_export($conditions, true); Logger::write('info', $message); return $chain->next($self, $params, $chain); });
  • What about Observer?
  • What about Observer? • Dependent on a centralized publish/ subscribe system
  • What about Observer? • Dependent on a centralized publish/ subscribe system • Extra layer of abstraction
  • What about Observer? • Dependent on a centralized publish/ subscribe system • Extra layer of abstraction • Fewer possibilities
  • What about Observer?
  • What about Observer? • Filters are self-contained and attach directly to objects
  • What about Observer? • Filters are self-contained and attach directly to objects • Direct and intuitive
  • Features: Everything is an adapter. (well, almost)
  • Databases • 1st-class support for document-oriented databases • MongoDB & CouchDB: production ready • Relational databases in beta • Cassandra in the works, too
  • <?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>
  • This works on...
  • This works on... • MongoDB
  • This works on... • MongoDB • CouchDB
  • This works on... • MongoDB • CouchDB • MySQL
  • This works on... • MongoDB • CouchDB • MySQL • SQLite (almost)
  • MongoDB + CouchDB: $post = Post::create(array( 'title' => 'Ein bier, bitte', 'body' => 'Was ist los mit dir?', 'tags' => array('PHP', 'Bier'), 'author' => array('name' => 'Nate') )); $post->save();
  • MongoDB: $posts = Post::all(array('conditions' => array( 'tags' => array('PHP', 'Bier'), 'author.name' => 'Nate' ))); // Translates to... db.posts.find({ tags : { $in : ['PHP', 'Bier'] }, 'author.name' : 'Nate' })
  • Databases • Adapter based, plugin aware • Will ship with MySQL, SQLite • SQL Server support via plugin • Query API
  • 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
  • The Query API $ages = User::all(array( 'group' => 'age', 'reduce' => 'function(obj, prev) { prev.count++; }', 'initial' => array('count' => 0) ));
  • The Query API $query = new Query(array( 'type' => 'read', 'model' => 'appmodelsPost', 'fields' => array('Post.title', 'Post.body'), 'conditions' => array('Post.id' => new Query(array( 'type' => 'read', 'fields' => array('post_id'), 'model' => 'appmodelsTagging', 'conditions' => array('Tag.name' => array('foo', 'bar', 'baz')), ))) ));
  • 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
  • Btw, li3_doctrine
  • Plays nice with others • Easily load & use libraries from other frameworks: • Zend Framework, Solar, Symfony, PEAR, etc. • PSR-0 Class-loading standard
  • /* add the trunk */ Libraries::add("Zend", array( "prefix" => "Zend_", "includePath" => true, "bootstrap" => "Loader/Autoloader.php", "loader" => array("Zend_Loader_Autoloader", "autoload"), "transform" => function($class) { return str_replace("_", "/", $class) . ".php"; } )); /* add the incubator */ Libraries::add("Zend_Incubator", array( "prefix" => "Zend_", "includePath" => '/htdocs/libraries/Zend/incubator/library', "transform" => function($class) { return str_replace("_", "/", $class) . ".php"; } ));
  • 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'); } }
  • This has been a presentation by: Nate Abele (@nateabele) Joël Perras (@jperras) Sucks. But check it out anyway.