Successfully reported this slideshow.

The Zen of Lithium

12

Share

Loading in …3
×
1 of 118
1 of 118

More Related Content

Related Books

Free with a 14 day trial from Scribd

See all

The Zen of Lithium

  1. 1. THE OF
  2. 2. • Former lead developer, CakePHP • Co-founder & lead developer of Lithium for ~2 years • Original BostonPHP framework bake-off champ! • Twitter: @nateabele
  3. 3. • Started as a series of test scripts on early dev builds of PHP 5.3 • Released as “Cake3” in July ‘09 • Spun off as Lithium in October ’09 • Based on 5 years’ experience developing a high-adoption web framework
  4. 4. ARCHITECTURE
  5. 5. Procedural Object-Oriented
  6. 6. Procedural Object-Oriented
  7. 7. Aspect-Oriented Event-Driven Procedural PARADIGMS Declarative Functional Object-Oriented
  8. 8. The Fall of Rome PARADIGM HUBRIS
  9. 9. + $
  10. 10. Z END F RAMEWORK 1.5
  11. 11. $transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => 'foo', 'password' => 'bar', 'ssl' => 'ssl', 'port' => 465, )); $mailer = new Zend_Mail(); $mailer->setDefaultTransport($transport);
  12. 12. class Container { public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => 'root', 'password' => 'sekr1t', 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } }
  13. 13. class Container { protected $parameters = array(); public function __construct(array $parameters = array()) { $this->parameters = $parameters; } public function getMailTransport() { return new Zend_Mail_Transport_Smtp('smtp.gmail.com', array( 'auth' => 'login', 'username' => $this->parameters['mailer.username'], 'password' => $this->parameters['mailer.password'], 'ssl' => 'ssl', 'port' => 465, )); } public function getMailer() { $mailer = new Zend_Mail(); $mailer->setDefaultTransport($this->getMailTransport()); return $mailer; } }
  14. 14. $container = new Container(array( 'mailer.username' => 'root', 'mailer.password' => 'sekr1t', 'mailer.class' => 'Zend_Mail', )); $mailer = $container->getMailer();
  15. 15. 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, )); } protected function getMailerService() { if (isset(self::$shared['mailer'])) { return self::$shared['mailer']; } $class = $this['mailer.class']; $mailer = new $class(); $mailer->setDefaultTransport($this->getMailTransportService()); return self::$shared['mailer'] = $mailer; } }
  16. 16. 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') ));
  17. 17. <?xml version="1.0" ?> <container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="mailer.username">root</parameter> <parameter key="mailer.password">sekr1t</parameter> <parameter key="mailer.class">Zend_Mail</parameter> </parameters> <services> <service id="mail.transport" class="Zend_Mail_Transport_Smtp" shared="false"> <argument>smtp.gmail.com</argument> <argument type="collection"> <argument key="auth">login</argument> <argument key="username">%mailer.username%</argument> <argument key="password">%mailer.password%</argument> <argument key="ssl">ssl</argument> <argument key="port">465</argument> </argument> </service> <service id="mailer" class="%mailer.class%"> <call method="setDefaultTransport"> <argument type="service" id="mail.transport" /> </call> </service> </services> </container>
  18. 18. Dependency injection container + Service container + Service container builder + XML
  19. 19. ==
  20. 20. mail()
  21. 21. THE MORAL “ All problems in computer science can be solved by another level of indirection. Except for the problem of too many layers of indirection. ” — Butler Lampson / David Wheeler
  22. 22. The Guggenheim Fallingwater GREAT ARCHITECTURE
  23. 23. WHO WON?
  24. 24. Jet Li’s Fearless JET LI AS HOU YUANJIA
  25. 25. BRUCE LEE
  26. 26. J EET K UNE D O The Way of the Intercepting Fist
  27. 27. GOALS • Understand a variety of paradigms & their strengths • Respect context when choosing paradigms / techniques • Be simple as possible (but no simpler)
  28. 28. PROBLEMS • Managing configuration • Staying flexible • Extending internals • Easy things: easy; hard things: possible
  29. 29. CONFIGURATION
  30. 30. webroot/index.php require dirname(__DIR__) . '/config/bootstrap.php'; echo lithiumactionDispatcher::run( new lithiumactionRequest() );
  31. 31. config/bootstrap.php require __DIR__ . '/bootstrap/libraries.php'; require __DIR__ . '/bootstrap/errors.php'; require __DIR__ . '/bootstrap/cache.php'; require __DIR__ . '/bootstrap/connections.php'; require __DIR__ . '/bootstrap/action.php'; require __DIR__ . '/bootstrap/session.php'; require __DIR__ . '/bootstrap/g11n.php'; require __DIR__ . '/bootstrap/media.php'; require __DIR__ . '/bootstrap/console.php';
  32. 32. config/bootstrap/libraries.php use lithiumcoreLibraries; Libraries::add('lithium'); Libraries::add('app', array('default' => true)); Libraries::add('li3_docs');
  33. 33. config/bootstrap/cache.php use lithiumstorageCache; Cache::config(array( 'local' => array('adapter' => 'Apc'), 'distributed' => array( 'adapter' => 'Memcache', 'host' => '127.0.0.1:11211' ), 'default' => array('adapter' => 'File') ));
  34. 34. config/bootstrap/connections.php use lithiumdataConnections; Connections::config(array( 'default' => array( 'type' => 'MongoDb', 'database' => 'my_mongo_db' ), 'legacy' => array( 'type' => 'database', 'adapter' => 'MySql', 'login' => 'bobbytables', 'password' => 's3kr1t', 'database' => 'my_mysql_db' ) ));
  35. 35. config/bootstrap/session.php use lithiumsecurityAuth; Auth::config(array( 'customer' => array( 'adapter' => 'Form', 'model' => 'Customers', 'fields' => array('email', 'password') ), 'administrator' => array( 'adapter' => 'Http', 'method' => 'digest', 'users' => array('nate' => 'li3') ) ));
  36. 36. MULTIPLE ENVIRONMENTS? use lithiumstorageCache; Cache::config(array( 'default' => array( 'development' => array('adapter' => 'Apc'), 'production' => array( 'adapter' => 'Memcache', 'host' => '127.0.0.1:1121' ) ) ));
  37. 37. MULTIPLE ENVIRONMENTS? use lithiumstorageCache; Cache::config(array( 'default' => array( 'development' => array('adapter' => 'Apc'), 'production' => array( 'adapter' => 'Memcache', 'host' => '127.0.0.1:1121' ) ) ));
  38. 38. namespace lithiumnethttp; use lithiumcoreLibraries; class Service extends lithiumcoreObject { protected $_classes = array( 'media' => 'lithiumnethttpMedia', 'request' => 'lithiumnethttpRequest', 'response' => 'lithiumnethttpResponse', ); public function __construct(array $config = array()) { $defaults = array( 'scheme' => 'http', 'host' => 'localhost', // ... ); parent::__construct($config + $defaults); } protected function _init() { // ... } }
  39. 39. $service = new Service(array( 'scheme' => 'https', 'host' => 'web.service.com', 'username' => 'user', 'password' => 's3kr1t' ));
  40. 40. $service = new Service(array( 'scheme' => 'https', 'host' => 'web.service.com', 'username' => 'user', 'password' => 's3kr1t' )); { “lithiumnethttpService”: { “scheme”: “https”, “host”: “web.service.com”, “username”: “user”, “password”: “s3kr1t” } }
  41. 41. $service = new Service(array( 'scheme' => 'https', 'host' => 'web.service.com', 'username' => 'user', 'password' => 's3kr1t', 'classes' => array( 'request' => 'mycustomRequest' ) ));
  42. 42. FLEXIBILITY
  43. 43. HELPERS <?=$this->form->text('email'); ?>
  44. 44. HELPERS <?=$this->form->text('email'); ?> <input type="text" name="email" id="MemberEmail" value="nate.abele@gmail.com" />
  45. 45. HELPERS <?=$this->form->field('name', array( 'wrap' => array('class' => 'wrapper') )); ?>
  46. 46. HELPERS <?=$this->form->field('name', array( 'wrap' => array('class' => 'wrapper') )); ?> <div class="wrapper"> <label for="MemberName">Name</label> <input type="text" name="name" id="MemberName" /> <div class="error">You don't have a name?</div> </div>
  47. 47. HELPERS <?=$this->form->field('name', array( 'wrap' => array('class' => 'item'), 'template' => '<li{:wrap}>{:error}{:label}{:input}</li>' )); ?>
  48. 48. HELPERS <?=$this->form->field('name', array( 'wrap' => array('class' => 'item'), 'template' => '<li{:wrap}>{:error}{:label}{:input}</li>' )); ?> <li class="item"> <div class="error">You don't have a name?</div> <label for="MemberName">Name</label> <input type="text" name="name" id="MemberName" /> </div>
  49. 49. HELPERS $this->form->config(array('templates' => array( 'field' => "<li{:wrap}>{:error}{:label}{:input}</li>" )));
  50. 50. HELPERS <input type="text" name="email" id="MemberEmail" value="nate.abele@gmail.com" />
  51. 51. HELPERS $form = $this->form; $this->form->config(array('attributes' => array( 'id' => function($method, $name, $options) use (&$form) { if ($method != 'text' && $method != 'select') { return; } $model = null; if ($binding = $form->binding()) { $model = basename(str_replace('', '/', $binding->model())) . '_'; } return Inflector::underscore($model . $name); } )));
  52. 52. HELPERS $form = $this->form; $this->form->config(array('attributes' => array( 'id' => function($method, $name, $options) use (&$form) { if ($method != 'text' && $method != 'select') { return; } $model = null; if ($binding = $form->binding()) { $model = basename(str_replace('', '/', $binding->model())) . '_'; } return Inflector::underscore($model . $name); } )));
  53. 53. HELPERS <input type="text" name="email" id="member_email" value="nate.abele@gmail.com" />
  54. 54. THE MEDIA CLASS class WeblogController < ActionController::Base def index @posts = Post.find :all respond_to do |format| format.html format.xml { render :xml => @posts.to_xml } format.rss { render :action => "feed.rxml" } end end end
  55. 55. THE MEDIA CLASS ! class WeblogController < ActionController::Base def index @posts = Post.find :all respond_to do |format| format.html format.xml { render :xml => @posts.to_xml } format.rss { render :action => "feed.rxml" } end end end
  56. 56. THE MEDIA CLASS <?php echo $javascript->object($data); ?>
  57. 57. THE MEDIA CLASS ! <?php echo $javascript->object($data); ?>
  58. 58. THE MEDIA CLASS lithiumnethttpMedia { $formats = array( array( 'html' => array(...), 'posts' => ... 'json' => array(...), ) 'xml' => array(...), '...' ); }
  59. 59. THE MEDIA CLASS lithiumnethttpMedia { $formats = array( array( 'html' => array(...), 'posts' => ... 'json' => array(...), ) 'xml' => array(...), '...' ); }
  60. 60. THE MEDIA CLASS Media::type('mobile', array('text/html'), array( 'view' => 'lithiumtemplateView', 'paths' => array( 'template' => array( '{:library}/views/{:controller}/{:template}.mobile.php', '{:library}/views/{:controller}/{:template}.html.php', ), 'layout' => array( '{:library}/views/layouts/{:layout}.mobile.php', '{:library}/views/layouts/{:layout}.html.php', ), 'element' => array( '{:library}/views/elements/{:template}.mobile.php' '{:library}/views/elements/{:template}.html.php' ) ), 'conditions' => array('mobile' => true) ));
  61. 61. THE MEDIA CLASS Media::type('mobile', array('text/html'), array( 'view' => 'lithiumtemplateView', 'paths' => array( 'template' => array( '{:library}/views/{:controller}/{:template}.mobile.php', '{:library}/views/{:controller}/{:template}.html.php', ), 'layout' => array( '{:library}/views/layouts/{:layout}.mobile.php', '{:library}/views/layouts/{:layout}.html.php', ), 'element' => array( '{:library}/views/elements/{:template}.mobile.php' '{:library}/views/elements/{:template}.html.php' ) ), 'conditions' => array('mobile' => true) ));
  62. 62. THE MEDIA CLASS Media::type('ajax', array('text/html'), array( 'view' => 'lithiumtemplateView', 'paths' => array( 'template' => array( '{:library}/views/{:controller}/{:template}.ajax.php', '{:library}/views/{:controller}/{:template}.html.php', ), 'layout' => false, 'element' => array( '{:library}/views/elements/{:template}.ajax.php' '{:library}/views/elements/{:template}.html.php' ) ), 'conditions' => array('ajax' => true) ));
  63. 63. THE MEDIA CLASS Media::type('ajax', array('text/html'), array( 'view' => 'lithiumtemplateView', 'paths' => array( 'template' => array( '{:library}/views/{:controller}/{:template}.ajax.php', '{:library}/views/{:controller}/{:template}.html.php', ), 'layout' => false, 'element' => array( '{:library}/views/elements/{:template}.ajax.php' '{:library}/views/elements/{:template}.html.php' ) ), 'conditions' => array('ajax' => true) ));
  64. 64. CONDITIONS? 'conditions' => array('ajax' => true) == $request->is('ajax') == $_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
  65. 65. CONDITIONS? $request->detect('iPhone', array('HTTP_USER_AGENT', '/iPhone/')); $isiPhone = $request->is('iPhone'); $request->detect('custom', function($request) { if ($value = $request->env("HTTP_WHATEVER")) { // Do something with $value } return false; });
  66. 66. ROUTING Router::connect('/{:controller}/{:action}/{:id:[0-9]+}', array( 'id' => null )); new Route(array( 'template' => '/{:controller}/{:action}/{:id:[0-9]+}', 'pattern' => '@^(?:/(?P[^/]+))(?:/(?P[^/]+)?)?(?:/(?P[0-9]+)?)?$@', 'params' => array('id' => null, 'action' => 'index'), // ... 'subPatterns' => array('id' => '[0-9]+'), 'persist' => array('controller') )); Router::connect(new CustomRoute($params));
  67. 67. ROUTE HANDLERS Router::connect('/{:user}/{:controller}/{:action}');
  68. 68. ROUTE HANDLERS Router::connect('/{:user}/{:controller}/{:action}', array(), function($request) { if (!Users::count(array('conditions' => array('user' => $request->user)))) { return false; } return $request; });
  69. 69. ROUTE HANDLERS Router::connect('/{:user}/{:controller}/{:action}', array(), function($request) { if (!Users::count(array('conditions' => array('user' => $request->user)))) { return false; } return $request; }); Router::connect('/', array(), function($request) { if (Session::read('user')) { $location = 'Accounts::index'; } else { $location = 'Users::add'; } return new Response(array('status' => 302, 'location' => $location)); });
  70. 70. ROUTE HANDLERS Router::connect( '/photos/view/{:id:[0-9a-f]{24}}.jpg', array(), function($request) { return new Response(array( 'headers' => array('Content-type' => 'image/jpeg'), 'body' => Photos::first($request->id)->file->getBytes() )); } );
  71. 71. MICRO-APPS Router::connect('/posts.json', array(), function($request) { return new Response(array( 'headers' => array('Content-type' => 'application/json'), 'body' => Posts::all()->to('json') )); }); Router::connect('/posts/{:id}.json', array(), function($request) { return new Response(array( 'headers' => array('Content-type' => 'application/json'), 'body' => Posts::first($request->id)->to('json') )); });
  72. 72. EXTENSIBILITY
  73. 73. HELPERS <?=$this->html->*() ?> lithiumtemplatehelperHtml
  74. 74. HELPERS <?=$this->html->*() ?> lithiumtemplatehelperHtml appextensionshelperHtml
  75. 75. MODELS namespace appmodels; class Posts extends lithiumdataModel { }
  76. 76. MODELS namespace appmodels; class Posts extends lithiumdataModel { protected $_meta = array( 'key' => 'custom_id', 'source' => 'custom_posts_table' 'connection' => 'legacy_mysql_db' ); }
  77. 77. MODELS namespace appmodels; class Posts extends lithiumdataModel { protected $_meta = array( 'key' => array( 'custom_id', 'other_custom_id' ) ); }
  78. 78. MODELS Posts::create(array( 'title' => 'My first post ever', 'body' => 'Wherein I extoll the virtues of Lithium' )); // ... $post->save();
  79. 79. MODELS $post->tags = 'technology,PHP,news'; $post->save(); // ... foreach ($post->tags as $tag) { #FALE }
  80. 80. MODELS namespace appmodels; class Posts extends lithiumdataModel { public function tags($entity) { return explode(',', $entity->tags); } } foreach ($post->tags() as $tag) { // ... }
  81. 81. MODELS namespace appmodels; class Posts extends lithiumdataModel { public function tags($entity) { return explode(',', $entity->tags); } } foreach ($post->tags() as $tag) { // ... }
  82. 82. MODELS namespace appmodels; class Posts extends lithiumdataModel { public static function expire() { return static::update( array('expired' => true), array('updated' => array( '<=' => strtotime('3 months ago') )) ); } } $didItWork = Posts::expire();
  83. 83. ENTITIES & COLLECTIONS $posts = Posts::findAllBySomeCondition();
  84. 84. ENTITIES & COLLECTIONS $posts = Posts::findAllBySomeCondition(); $posts->first(function($post) { return $post->published == true; }); $posts->each(function($post) { return $post->counter++; }); $ids = $posts->map(function($post) { return $post->id; });
  85. 85. RELATIONSHIPS namespace appmodels; class Posts extends lithiumdataModel { public $belongsTo = array('Users'); }
  86. 86. RELATIONSHIPS namespace appmodels; class Posts extends lithiumdataModel { public $belongsTo = array('Author' => array( 'class' => 'Users' )); }
  87. 87. RELATIONSHIPS namespace appmodels; class Posts extends lithiumdataModel { public $belongsTo = array('Author' => array( 'class' => 'Users', 'conditions' => array('active' => true), 'key' => 'author_id' )); }
  88. 88. RELATIONSHIPS namespace appmodels; class Posts extends lithiumdataModel { public $belongsTo = array('Author' => array( 'class' => 'Users', 'conditions' => array('active' => true), 'key' => array( 'author_id' => 'id', 'other_key' => 'other_id' ) )); }
  89. 89. NO H AS A ND B ELONGS T O M ANY !!
  90. 90. DOCUMENT DATABASES $post = Posts::create(array( 'title' => "New post", 'body' => "Something worthwhile to read", 'tags' => array('PHP', 'tech'), 'author' => array('name' => 'Nate') ));
  91. 91. DOCUMENT DATABASES $posts = Posts::all(array('conditions' => array( 'tags' => array('PHP', 'tech'), 'author.name' => 'Nate' )));
  92. 92. DOCUMENT DATABASES $ages = Users::all(array( 'group' => 'age', 'reduce' => 'function(obj, prev) { prev.count++; }', 'initial' => array('count' => 0) ));
  93. 93. 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')), ))) ));
  94. 94. FILTERS
  95. 95. $post = Posts::first($id);
  96. 96. Posts::applyFilter('find', function($self, $params, $chain) { $key = // Make a cache key from $params['options'] if ($result = Cache::read('default', $key)) { return $result; } $result = $chain->next($self, $params, $chain); Cache::write('default', $key, $result); return $result; });
  97. 97. logging caching find()
  98. 98. logging caching find()
  99. 99. Posts::applyFilter('find', function($self, $params, $chain) { $key = // Make a cache key from $params['options'] if ($result = Cache::read('default', $key)) { return $result; } $result = $chain->next($self, $params, $chain); Cache::write('default', $key, $result); return $result; });
  100. 100. THE TALK OF THE TOWN
  101. 101. CAN I USE IT IN PRODUCTION?
  102. 102. GIMMEBAR.COM Sean Coates
  103. 103. MAPALONG.COM Chris Shiflett Andrei Zmievski
  104. 104. TOTSY.COM Mitch Pirtle
  105. 105. ...AND MANY OTHERS
  106. 106. David Coallier • President, PEAR Group • CTO, Echolibre / Orchestra.io “ After looking at Lithium I’ve come to realize how far ahead it is compared to other frameworks from a technologist's point of view. ”
  107. 107. Helgi Þormar Þorbjörnsson • Developer, PEAR Installer • PEAR Core Dev, 8 years “ It’s the f*****g epiphany of modern! ”
  108. 108. Fahad Ibnay Heylaal • Creator, Croogo CMS “ I believe the future is in Lithium. give it time to grow, and the developers behind it are awesome. ”
  109. 109. 1.0?
  110. 110. SO CLOSE!!
  111. 111. TOMORROW...
  112. 112. 0.10
  113. 113. THANKS!! Find me later : @nateabele nate.abele@gmail.com http://nateabele.com/
  114. 114. RESOURCES Getting connected Learning AOP lithify.me bit.ly/aop-design github.com/UnionOfRAD bit.ly/aop-gwoo #li3 on irc.freenode.net bit.ly/aop-li3 @UnionOfRAD bit.ly/aop-oop Talks bit.ly/mwop-aop slideshare.net/nateabele
  115. 115. PHOTO CREDITS http://www.flickr.com/photos/mkebbe/28298461/ http://www.flickr.com/photos/josefeliciano/3849557951/ http://www.flickr.com/photos/cku/1386908692/ http://www.flickr.com/photos/macten/4611148426/ http://www.rustybrick.com/prototype-js-vs-jquery-comparison.html http://www.flickr.com/photos/cadsonline/4321530819/ http://www.flickr.com/photos/maiptitfleur/4942829255/

×