Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
-
Upload
nate-abele -
Category
Technology
-
view
1.793 -
download
2
description
Transcript of Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
A Framework for People Who Hate Frameworks.
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.
Why?
Frameworks Suck
•Code you will never use.
•Complexity overhead.
•You didn’t write it.
Martin Fowler.Also,
His name is the biggest, so it’s his
fault.
We’re not saying design patterns are bad.
Quite the opposite.
Lithium implements many well known 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.”
Configuration.
Everyone does it differently.
Sometimes in the same framework.
Sometimes in the same class.
Dependency injection.
A great idea!
... but you need to understand what you’re doing.
Some patterns were implemented in Java, to
solve language problems that PHP just doesn’t
have.
“Design Patterns In Dynamic Languages”
Peter Norvig
http://norvig.com/design-patterns/
Try this some time...
The second part.
Everythingyou knowis wrong.
The sun does notrevolve around OOP
Galileo facing the Roman Inquistion- Cristiano Banti (1857)
...nevertheless,it moves.
Dependency injection.
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
Dependency Injection
•Various attempts at making DI work better:
•DI Container
•Using dynamic nature of PHP to your advantage.
Coupling should be in
proportion to domain
relevance.
The problem of
state.
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
MVC
Value Object
Data Access Object
Front Controller
Dependency InjectionRegistry
Data Mapper Service Layer
Unit of Work
FALE
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• Separation of concerns
• Domain classes should not know or care about cross-cutting concerns
• Examples:
• Caching
• Logging
• Access Control, etc.
Functional Programming
•Only possible when functions are first-class citizens
•Referential transparency
•Functional purity
Referential transparency is not...
$this date()
$_*
Referential transparency is not...
$this date()
$_*
These Are NotDesign Patterns.
•Draws on years of experience building web frameworks
•PHP 5.3+ only
•Doesn’t assume you’re stupid
Less Suck
Ways we try to suck less:
Consistency.
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
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())
public function __construct(array $config = array())
public function __construct(array $config = array())
public function __construct(array $config = array())
public function __construct(array $config = array())
public function __construct(array $config = array())
public function __construct(array $config = array())
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 application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
2
<?php
class Foo extends \lithium\core\Object {
protected function _init() { $or = $some->highOverHead($operation); $or()->otherwise(HARD_TO_TEST)->code(); }}
?>
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
2
<?php
class Foo extends \lithium\core\Object {
protected function _init() { $or = $some->highOverHead($operation); $or()->otherwise(HARD_TO_TEST)->code(); }}
?>
$foo = new Foo(array('init' => false));
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
3new application\bar\Foo();// loads app/bar/Foo.php
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
4
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
5
protected function _init() { $cache = $this->_classes['cache']; $cache::write(__CLASS__, $this->_someGeneratedValue()); }}
?>
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
5
protected function _init() { $cache = $this->_classes['cache']; $cache::write(__CLASS__, $this->_someGeneratedValue()); }}
?>
$foo = new Foo(array('classes' => array( 'cache' => 'application\extensions\Cache')));
<?php
namespace application\bar;
use \lithium\util\String;use \lithium\util\Collection;
class Foo extends \lithium\core\Object {
protected $_classes = array( 'cache' => '\lithium\storage\Cache', 'logger' => '\lithium\analysis\Logger' );
public function __construct(array $config = array()) { // ... }
protected function _init() { // ... }}
?>
protected function _init() { $cache = $this->_classes['cache']; $cache::write(__CLASS__, $this->_someGeneratedValue()); }}
?>
$options = array()
Keeps parameter lists short
Makes class APIs more extensible
&
$config = array()
Same idea.Modifies class / object state.
But...!
Adaptable
Auth
Cache
Catalog
Connections
Logger
Session
use lithium\security\Auth;
Auth::config(array( 'customer' => array( 'adapter' => 'Form', 'model' => 'Customer', 'fields' => array('email', 'password') )));
use lithium\storage\Cache;
Cache::config(array( 'local' => array('adapter' => 'Apc'), 'distributed' => array( 'adapter' => 'Memcached', 'servers' => array('127.0.0.1', 11211), ), 'default' => array('adapter' => 'File')));
use lithium\data\Connections;
Connections::config(array( 'old' => array( 'type' => 'database', 'adapter' => 'MySql', 'user' => 'bobby_tables', 'password' => '******', 'database' => 'my_app' ), 'new' => array( 'type' => 'MongoDb', 'database' => 'my_app' )));
use lithium\storage\Session;
Session::config(array( 'cookie' => array( 'adapter' => 'Cookie', 'expire' => '+2 days' ), 'default' => array('adapter' => 'Php')));
Also fun:
use lithium\storage\Session;
Session::config(array( 'default' => array( 'adapter' => 'MyCustomAdapter', 'expires' => '+2 days', 'custom' => 'Whatever!' )));
use lithium\storage\Session;
Session::config(array( 'default' => array( 'adapter' => 'MyCustomAdapter', 'expires' => '+2 days', 'custom' => 'Whatever!' )));
public function __construct(array $config = array())
Multiple environments?
use lithium\storage\Cache;
Cache::config(array( 'default' => array( 'development' => array( 'adapter' => 'Apc' ), 'production' => array( 'adapter' => 'Memcached', 'servers' => array('127.0.0.1', 11211) ) )));
use lithium\storage\Cache;
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?
•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!
•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 with XHProf and XDebug cachegrinds.
Example: Resource Routing
use app\models\Post;use lithium\action\Response;use lithium\net\http\Router;
Router::connect('/frequent_api_call.json', array(), function($request) { return new Response(array( 'type' => 'application/json', 'body' => Post::recent()->to('json') ));});
PlatformRackspace Cloud
• 256MB ram• Ubuntu Karmic• Apache 2.2.12• PHP 5.3.1• Xcache• Siege 2.68
2009-11-26
Flexibility.
Lithium is a highly flexible framework.
Most class dependencies are dynamic.(Our implementation of dependency injection)
class Service extends \lithium\core\Object {
protected $_classes = array( 'request' => 'lithium\net\http\Request', 'response' => 'lithium\net\http\Response', 'socket' => 'lithium\net\socket\Context' );}
$service = new Service(array('classes' => array( 'socket' => 'my\custom\Socket')));
The Filter System:
Aspect-Oriented Design
for PHP.
Caching
Logging
Example: Caching & Logging
Post::find()
Caching
Logging
Post::find()
Example: Caching & Logging
use lithium\analysis\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);});
use lithium\analysis\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);});
Post Logger
What about Observer?
•Dependent on a centralized publish/subscribe system
•Extra layer of abstraction
•Fewer possibilities
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/Redis/Riak in the works, too
<?php
$post = Post::create(array( 'title' => 'ビール2本', 'body' => 'クレジットカードは使えますか?'));$post->save();
$post = Post::find($id);
?>
<h2><?=$post->title; ?></h2><p><?=$post->body; ?></p>
This works on...
This works on...
•MongoDB
•CouchDB
•MySQL
•SQLite
MongoDB + CouchDB:
$post = Post::create(array( 'title' => 'ビール2本', 'body' => 'クレジットカードは使えますか?', 'tags' => array('PHP', 'Japan'), 'author' => array('name' => 'Nate')));
$post->save();
MongoDB:
$posts = Post::all(array('conditions' => array( 'tags' => array('PHP', 'Japan'), 'author.name' => 'Nate')));
// Translates to...db.posts.find({ tags : { $in : ['PHP', 'Japan'] }, 'author.name' : 'Nate'})
Databases
•Adapter based, plugin aware
•Will ship with MySQL, SQLite & PostgreSQL
•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' => 'app\models\Post', 'fields' => array('Post.title', 'Post.body'), 'conditions' => array('Post.id' => new Query(array( 'type' => 'read', 'fields' => array('post_id'), 'model' => 'app\models\Tagging', '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 app\controllers;
use \Zend_Mail_Storage_Pop3;
class EmailController extends \lithium\action\Controller {
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)
http://lithify.me Sucks. But check it out anyway.