Top Banner
THE OF
118

The Zen of Lithium

Jan 15, 2015

Download

Technology

Nate Abele

"The Zen of Lithium" provides an overview of some of the philosophies behind the Lithium framework
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: The Zen of Lithium

THE

OF

Page 2: The Zen of Lithium

• Former lead developer, CakePHP

• Co-founder & lead developer of Lithium for ~2 years

• Original BostonPHP framework bake-off champ!

• Twitter: @nateabele

Page 3: The Zen of Lithium

• 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

Page 4: The Zen of Lithium

ARCHITECTURE

Page 5: The Zen of Lithium

Procedural Object-Oriented

Page 6: The Zen of Lithium

Procedural Object-Oriented

Page 7: The Zen of Lithium

PARADIGMS

Event-Driven

Aspect-Oriented

Declarative

Procedural

Functional

Object-Oriented

Page 8: The Zen of Lithium

PARADIGM HUBRIS

The Fall of Rome

Page 9: The Zen of Lithium
Page 10: The Zen of Lithium

+ $

Page 11: The Zen of Lithium

ZEND FRAMEWORK 1.5

Page 12: The Zen of Lithium

$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);

Page 13: The Zen of Lithium

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; }}

Page 14: The Zen of Lithium

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; }}

Page 15: The Zen of Lithium

$container = new Container(array( 'mailer.username' => 'root', 'mailer.password' => 'sekr1t', 'mailer.class' => 'Zend_Mail',));

$mailer = $container->getMailer();

Page 16: The Zen of Lithium

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; }}

Page 17: The Zen of Lithium

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') ));

Page 18: The Zen of Lithium

<?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>

Page 19: The Zen of Lithium

Dependency injection container

+ Service container

+ Service container builder

+ XML

Page 20: The Zen of Lithium

==

Page 21: The Zen of Lithium

mail()

Page 22: The Zen of Lithium
Page 23: The Zen of Lithium

All problems in computer science can be solved by another level of indirection. Except for the problem of too many layers of indirection.

THE MORAL

“”

— Butler Lampson / David Wheeler

Page 24: The Zen of Lithium

GREAT ARCHITECTURE

The Guggenheim Fallingwater

Page 25: The Zen of Lithium
Page 26: The Zen of Lithium

WHO WON?

Page 27: The Zen of Lithium

JET LI AS HOU YUANJIA

Jet Li’s Fearless

Page 28: The Zen of Lithium

BRUCE LEE

Page 29: The Zen of Lithium

JEET KUNE DOThe Way of the Intercepting Fist

Page 30: The Zen of Lithium

GOALS

• Understand a variety of paradigms & their strengths

• Respect context when choosing paradigms / techniques

• Be simple as possible (but no simpler)

Page 31: The Zen of Lithium

PROBLEMS

• Managing configuration

• Staying flexible

• Extending internals

• Easy things: easy; hard things: possible

Page 32: The Zen of Lithium

CONFIGURATION

Page 33: The Zen of Lithium

require dirname(__DIR__) . '/config/bootstrap.php';

echo lithium\action\Dispatcher::run( new lithium\action\Request());

webroot/index.php

Page 34: The Zen of Lithium

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';

config/bootstrap.php

Page 35: The Zen of Lithium

config/bootstrap/libraries.php

use lithium\core\Libraries;

Libraries::add('lithium');

Libraries::add('app', array('default' => true));

Libraries::add('li3_docs');

Page 36: The Zen of Lithium

config/bootstrap/cache.php

use lithium\storage\Cache;

Cache::config(array( 'local' => array('adapter' => 'Apc'), 'distributed' => array( 'adapter' => 'Memcache', 'host' => '127.0.0.1:11211' ), 'default' => array('adapter' => 'File')));

Page 37: The Zen of Lithium

config/bootstrap/connections.php

use lithium\data\Connections;

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' )));

Page 38: The Zen of Lithium

config/bootstrap/session.php

use lithium\security\Auth;

Auth::config(array( 'customer' => array( 'adapter' => 'Form', 'model' => 'Customers', 'fields' => array('email', 'password') ), 'administrator' => array( 'adapter' => 'Http', 'method' => 'digest', 'users' => array('nate' => 'li3') )));

Page 39: The Zen of Lithium

use lithium\storage\Cache;

Cache::config(array( 'default' => array( 'development' => array('adapter' => 'Apc'), 'production' => array( 'adapter' => 'Memcache', 'host' => '127.0.0.1:1121' ) )));

MULTIPLE ENVIRONMENTS?

Page 40: The Zen of Lithium

use lithium\storage\Cache;

Cache::config(array( 'default' => array( 'development' => array('adapter' => 'Apc'), 'production' => array( 'adapter' => 'Memcache', 'host' => '127.0.0.1:1121' ) )));

MULTIPLE ENVIRONMENTS?

Page 41: The Zen of Lithium

namespace lithium\net\http;

use lithium\core\Libraries;

class Service extends \lithium\core\Object {

protected $_classes = array( 'media' => 'lithium\net\http\Media', 'request' => 'lithium\net\http\Request', 'response' => 'lithium\net\http\Response', );

public function __construct(array $config = array()) { $defaults = array( 'scheme' => 'http', 'host' => 'localhost', // ... ); parent::__construct($config + $defaults); }

protected function _init() { // ... }}

Page 42: The Zen of Lithium

$service = new Service(array( 'scheme' => 'https', 'host' => 'web.service.com', 'username' => 'user', 'password' => 's3kr1t'));

Page 43: The Zen of Lithium

$service = new Service(array( 'scheme' => 'https', 'host' => 'web.service.com', 'username' => 'user', 'password' => 's3kr1t'));{ “lithium\\net\\http\\Service”: { “scheme”: “https”, “host”: “web.service.com”, “username”: “user”, “password”: “s3kr1t” }}

Page 44: The Zen of Lithium

$service = new Service(array( 'scheme' => 'https', 'host' => 'web.service.com', 'username' => 'user', 'password' => 's3kr1t', 'classes' => array( 'request' => 'my\custom\Request' )));

Page 45: The Zen of Lithium

FLEXIBILITY

Page 46: The Zen of Lithium

HELPERS

<?=$this->form->text('email'); ?>

Page 47: The Zen of Lithium

HELPERS

<?=$this->form->text('email'); ?>

<input type="text" name="email" id="MemberEmail" value="[email protected]" />

Page 48: The Zen of Lithium

HELPERS

<?=$this->form->field('name', array( 'wrap' => array('class' => 'wrapper'))); ?>

Page 49: The Zen of Lithium

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>

Page 50: The Zen of Lithium

HELPERS

<?=$this->form->field('name', array( 'wrap' => array('class' => 'item'), 'template' => '<li{:wrap}>{:error}{:label}{:input}</li>')); ?>

Page 51: The Zen of Lithium

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>

Page 52: The Zen of Lithium

HELPERS

$this->form->config(array('templates' => array( 'field' => "<li{:wrap}>{:error}{:label}{:input}</li>")));

Page 53: The Zen of Lithium

HELPERS

<input type="text" name="email" id="MemberEmail" value="[email protected]" />

Page 54: The Zen of Lithium

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); })));

Page 55: The Zen of Lithium

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); })));

Page 56: The Zen of Lithium

HELPERS

<input type="text" name="email" id="member_email" value="[email protected]" />

Page 57: The Zen of Lithium

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

Page 58: The Zen of Lithium

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 !

Page 59: The Zen of Lithium

THE MEDIA CLASS

<?php echo $javascript->object($data); ?>

Page 60: The Zen of Lithium

THE MEDIA CLASS

<?php echo $javascript->object($data); ?>!

Page 61: The Zen of Lithium

THE MEDIA CLASS

lithium\net\http\Media {

$formats = array( 'html' => array(...), 'json' => array(...), 'xml' => array(...), '...' );}

array( 'posts' => ...)

Page 62: The Zen of Lithium

THE MEDIA CLASS

lithium\net\http\Media {

$formats = array( 'html' => array(...), 'json' => array(...), 'xml' => array(...), '...' );}

array( 'posts' => ...)

Page 63: The Zen of Lithium

THE MEDIA CLASSMedia::type('mobile', array('text/html'), array( 'view' => 'lithium\template\View', '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)));

Page 64: The Zen of Lithium

THE MEDIA CLASSMedia::type('mobile', array('text/html'), array( 'view' => 'lithium\template\View', '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)));

Page 65: The Zen of Lithium

THE MEDIA CLASS

Media::type('ajax', array('text/html'), array( 'view' => 'lithium\template\View', '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)));

Page 66: The Zen of Lithium

THE MEDIA CLASS

Media::type('ajax', array('text/html'), array( 'view' => 'lithium\template\View', '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)));

Page 67: The Zen of Lithium

CONDITIONS?

'conditions' => array('ajax' => true)

==

$request->is('ajax')

==

$_SERVER['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'

Page 68: The Zen of Lithium

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;});

Page 69: The Zen of Lithium

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));

Page 70: The Zen of Lithium

ROUTE HANDLERS

Router::connect('/{:user}/{:controller}/{:action}');

Page 71: The Zen of Lithium

ROUTE HANDLERS

Router::connect('/{:user}/{:controller}/{:action}', array(), function($request) { if (!Users::count(array('conditions' => array('user' => $request->user)))) { return false; } return $request;});

Page 72: The Zen of Lithium

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));});

Page 73: The Zen of Lithium

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() )); });

Page 74: The Zen of Lithium

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') ));});

Page 75: The Zen of Lithium

EXTENSIBILITY

Page 76: The Zen of Lithium

HELPERS

<?=$this->html->*() ?>

lithium\template\helper\Html

Page 77: The Zen of Lithium

HELPERS

<?=$this->html->*() ?>

lithium\template\helper\Html

app\extensions\helper\Html

Page 78: The Zen of Lithium

MODELS

namespace app\models;

class Posts extends \lithium\data\Model {

}

Page 79: The Zen of Lithium

MODELS

namespace app\models;

class Posts extends \lithium\data\Model {

protected $_meta = array( 'key' => 'custom_id', 'source' => 'custom_posts_table' 'connection' => 'legacy_mysql_db' );}

Page 80: The Zen of Lithium

MODELS

namespace app\models;

class Posts extends \lithium\data\Model {

protected $_meta = array( 'key' => array( 'custom_id', 'other_custom_id' ) );}

Page 81: The Zen of Lithium

MODELS

Posts::create(array( 'title' => 'My first post ever', 'body' => 'Wherein I extoll the virtues of Lithium'));

// ...

$post->save();

Page 82: The Zen of Lithium

MODELS

$post->tags = 'technology,PHP,news';$post->save();

// ...

foreach ($post->tags as $tag) { #FALE}

Page 83: The Zen of Lithium

MODELS

foreach ($post->tags() as $tag) { // ...}

namespace app\models;

class Posts extends \lithium\data\Model {

public function tags($entity) { return explode(',', $entity->tags); }}

Page 84: The Zen of Lithium

MODELS

foreach ($post->tags() as $tag) { // ...}

namespace app\models;

class Posts extends \lithium\data\Model {

public function tags($entity) { return explode(',', $entity->tags); }}

Page 85: The Zen of Lithium

MODELS

namespace app\models;

class Posts extends \lithium\data\Model {

public static function expire() { return static::update( array('expired' => true), array('updated' => array( '<=' => strtotime('3 months ago') )) ); }}

$didItWork = Posts::expire();

Page 86: The Zen of Lithium

ENTITIES & COLLECTIONS

$posts = Posts::findAllBySomeCondition();

Page 87: The Zen of Lithium

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;});

Page 88: The Zen of Lithium

RELATIONSHIPS

namespace app\models;

class Posts extends \lithium\data\Model {

public $belongsTo = array('Users');}

Page 89: The Zen of Lithium

RELATIONSHIPS

namespace app\models;

class Posts extends \lithium\data\Model {

public $belongsTo = array('Author' => array( 'class' => 'Users' ));}

Page 90: The Zen of Lithium

RELATIONSHIPS

namespace app\models;

class Posts extends \lithium\data\Model {

public $belongsTo = array('Author' => array( 'class' => 'Users', 'conditions' => array('active' => true), 'key' => 'author_id' ));}

Page 91: The Zen of Lithium

RELATIONSHIPS

namespace app\models;

class Posts extends \lithium\data\Model {

public $belongsTo = array('Author' => array( 'class' => 'Users', 'conditions' => array('active' => true), 'key' => array( 'author_id' => 'id', 'other_key' => 'other_id' ) ));}

Page 92: The Zen of Lithium

NO HASANDBELONGSTOMANY!!

Page 93: The Zen of Lithium

DOCUMENT DATABASES

$post = Posts::create(array( 'title' => "New post", 'body' => "Something worthwhile to read", 'tags' => array('PHP', 'tech'), 'author' => array('name' => 'Nate')));

Page 94: The Zen of Lithium

DOCUMENT DATABASES

$posts = Posts::all(array('conditions' => array( 'tags' => array('PHP', 'tech'), 'author.name' => 'Nate')));

Page 95: The Zen of Lithium

DOCUMENT DATABASES

$ages = Users::all(array( 'group' => 'age', 'reduce' => 'function(obj, prev) { prev.count++; }', 'initial' => array('count' => 0)));

Page 96: The Zen of Lithium

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')), )))));

Page 97: The Zen of Lithium

FILTERS

Page 98: The Zen of Lithium

$post = Posts::first($id);

Page 99: The Zen of Lithium

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;});

Page 100: The Zen of Lithium

find()

caching

logging

Page 101: The Zen of Lithium

find()

caching

logging

Page 102: The Zen of Lithium

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;});

Page 103: The Zen of Lithium

THE TALK OF THE TOWN

Page 104: The Zen of Lithium

CAN I USE IT IN PRODUCTION?

Page 105: The Zen of Lithium

GIMMEBAR.COM

Sean Coates

Page 106: The Zen of Lithium

MAPALONG.COM

Chris Shiflett

Andrei Zmievski

Page 107: The Zen of Lithium

TOTSY.COM

Mitch Pirtle

Page 108: The Zen of Lithium

. . .AND MANY OTHERS

Page 109: The Zen of Lithium

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.

“”

Page 110: The Zen of Lithium

Helgi Þormar Þorbjörnsson

• Developer, PEAR Installer

• PEAR Core Dev, 8 years

It’s the f*****g epiphany of modern!“ ”

Page 111: The Zen of Lithium

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.

“”

Page 112: The Zen of Lithium

1 .0?

Page 113: The Zen of Lithium

SO CLOSE!!

Page 114: The Zen of Lithium

TOMORROW...

Page 115: The Zen of Lithium

0.10

Page 116: The Zen of Lithium

THANKS!!

@nateabele

[email protected]

http://nateabele.com/

Find me later :

Page 118: The Zen of Lithium

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/