The Zen of Lithium

4,372 views

Published on

"The Zen of Lithium" provides an overview of some of the philosophies behind the Lithium framework

Published in: Technology
2 Comments
12 Likes
Statistics
Notes
No Downloads
Views
Total views
4,372
On SlideShare
0
From Embeds
0
Number of Embeds
81
Actions
Shares
0
Downloads
84
Comments
2
Likes
12
Embeds 0
No embeds

No notes for slide

The Zen of Lithium

  1. 1. THEOF
  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-OrientedEvent-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 byanother level of indirection. Except for theproblem 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 OThe 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.phprequire dirname(__DIR__) . /config/bootstrap.php;echo lithiumactionDispatcher::run( new lithiumactionRequest());
  31. 31. config/bootstrap.phprequire __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.phpuse lithiumcoreLibraries;Libraries::add(lithium);Libraries::add(app, array(default => true));Libraries::add(li3_docs);
  33. 33. config/bootstrap/cache.phpuse 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.phpuse 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.phpuse 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 dont 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 dont 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 CLASSclass 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 endend
  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 endend
  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 CLASSMedia::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 CLASSMedia::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 CLASSMedia::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 CLASSMedia::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. ROUTINGRouter::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 HANDLERSRouter::connect(/{:user}/{:controller}/{:action});
  68. 68. ROUTE HANDLERSRouter::connect(/{:user}/{:controller}/{:action}, array(), function($request) { if (!Users::count(array(conditions => array(user => $request->user)))) { return false; } return $request;});
  69. 69. ROUTE HANDLERSRouter::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 HANDLERSRouter::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-APPSRouter::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. MODELSnamespace appmodels;class Posts extends lithiumdataModel {}
  76. 76. MODELSnamespace appmodels;class Posts extends lithiumdataModel { protected $_meta = array( key => custom_id, source => custom_posts_table connection => legacy_mysql_db );}
  77. 77. MODELSnamespace appmodels;class Posts extends lithiumdataModel { protected $_meta = array( key => array( custom_id, other_custom_id ) );}
  78. 78. MODELSPosts::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. MODELSnamespace appmodels;class Posts extends lithiumdataModel { public function tags($entity) { return explode(,, $entity->tags); }}foreach ($post->tags() as $tag) { // ...}
  81. 81. MODELSnamespace appmodels;class Posts extends lithiumdataModel { public function tags($entity) { return explode(,, $entity->tags); }}foreach ($post->tags() as $tag) { // ...}
  82. 82. MODELSnamespace 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. RELATIONSHIPSnamespace appmodels;class Posts extends lithiumdataModel { public $belongsTo = array(Users);}
  86. 86. RELATIONSHIPSnamespace appmodels;class Posts extends lithiumdataModel { public $belongsTo = array(Author => array( class => Users ));}
  87. 87. RELATIONSHIPSnamespace appmodels;class Posts extends lithiumdataModel { public $belongsTo = array(Author => array( class => Users, conditions => array(active => true), key => author_id ));}
  88. 88. RELATIONSHIPSnamespace 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. loggingcachingfind()
  98. 98. loggingcachingfind()
  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.COMSean Coates
  103. 103. MAPALONG.COM Chris ShiflettAndrei Zmievski
  104. 104. TOTSY.COMMitch 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 technologists 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. RESOURCESGetting connected Learning AOPlithify.me bit.ly/aop-designgithub.com/UnionOfRAD bit.ly/aop-gwoo#li3 on irc.freenode.net bit.ly/aop-li3@UnionOfRAD bit.ly/aop-oop Talks bit.ly/mwop-aopslideshare.net/nateabele
  115. 115. PHOTO CREDITShttp://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.htmlhttp://www.flickr.com/photos/cadsonline/4321530819/http://www.flickr.com/photos/maiptitfleur/4942829255/

×