building lithium apps
DESCRIPTION
Building Lithium Apps (Like a Boss) was a workshop presented on the structure and philosophy of the Lithium framework and its applications, and how best to take advantage of them.TRANSCRIPT
BUILDING LITHIUMAPPS LIKE A BOSSPace University, NYC · 10-19-2010
ARCHITECTURE
?
architecture |ˈärkiˌtek ch ər|
noun! the complex or carefully designed structure of! something
noun! Orderly arrangement of parts; structure
noun! the conceptual structure and logical organization of a computer! or computer-based system
“ARCHITECTURE” IN PHP
“PROCEDURAL”
“OBJECT-ORIENTED”
Procedural Object-Oriented
Procedural Object-Oriented
PARADIGMS
Event-Driven
Aspect-Oriented
Declarative
Procedural
Functional
Object-Oriented
PARADIGMS
Event-Driven
Aspect-Oriented
Declarative
Procedural
Functional
Object-Oriented
PARADIGMS
Event-Driven
Aspect-Oriented
Declarative
Procedural
Functional
Object-Oriented
PARADIGMS
Event-Driven
Aspect-Oriented
Declarative
Procedural
Functional
Object-Oriented
BOOTSTRAP / DISPATCH
index.php
/posts
?url=/posts
index.php
/posts
?url=/posts
config/bootstrap.php
config/bootstrap/libraries.php
config/bootstrap/cache.php
config/bootstrap/connections.php
config/bootstrap/action.php
...
index.php
/posts
?url=/posts
config/bootstrap.php
config/bootstrap/libraries.php
config/bootstrap/cache.php
config/bootstrap/connections.php
config/bootstrap/action.php
...
lithium\action\Dispatcher::run(new lithium\action\Request()
)
config/bootstrap/cache.php
Cache::config(array( 'local' => array( 'adapter' => 'Apc' ), 'distributed' => array( 'adapter' => 'Memcached', 'servers' => array( array('127.0.0.1', 11211, 100) array('127.0.0.2', 11211, 100) ) )));
config/bootstrap/connections.php
Connections::add('default', array( 'development' => array( 'type' => 'MongoDb', 'host' => 'localhost', 'database' => 'foo' ), 'production' => array( 'type' => 'MongoDb', 'host' => 'db.host', 'database' => 'foo.prod' )));
Connections::add('cassandra', array( 'type' => 'Cassandra', 'keyspace' => 'foo'));
STATICS & STATE
new lithium\action\Request()
$_GET $_POST $_SERVER
CONFIGURATION
REQUEST
FILTERS
A.K.A. ASPECT-ORIENTED PROGRAMMING
lithium\action\Dispatcher { //...
public static function run($request, ...) { // Pass the request to the Router $params = $classes['router']::process($request);
// Do some checking...
// Get a controller... return static::call($controller); }}
There are no routes here yet
Dispatcher::applyFilter('run', function($self, $params, $chain) {
foreach (Libraries::get() as $name => $config) { // Some sanity checking goes here... include "{$config['path']}/config/routes.php"; } return $chain->next($self, $params, $chain);});
config/bootstrap/action.php
lithium\action\Dispatcher { //...
public static function run($request, ...) { // Pass the request to the Router $params = $classes['router']::process($request);
// Do some checking...
// Get a controller... // $controller = ... return static::call($controller); }}
Dispatcher::applyFilter('run', function($self, $params, $chain) {
foreach (Libraries::get() as $name => $config) { // Some sanity checking goes here... include "{$config['path']}/config/routes.php"; } return $chain->next($self, $params, $chain);});
CONTROLLERS
use app\models\Posts;
app\controllers\PostsController {
public function index() { $posts = Posts::all(); return compact('posts'); }}
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
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
lithium\net\http\Media {
$formats = array( 'html' => array(...), 'json' => array(...), 'xml' => array(...), '...' );}
array('posts' => ...
)
new lithium\action\Response()
new lithium\action\Response()
Controller
View
Dispatcher index.php
REQUEST / RESPONSE
Router::connect('/photos/{:id:[0-9a-f]{24}}.jpg', array(), function($request) { return new Response(array( 'type' => 'jpg', 'body' => Photo::first($request->id)->file->getBytes() ));});
/** * This handles both the home page and the archives pages. */Router::connect('/{:page}', array('page' => 1), array( 'pattern' => '@^/?(?P<page>\d+)$|^\/?$@', 'keys' => array('page' => true), 'handler' => function($request) use ($render) { $page = intval($request->page); $posts = Posts::recent(compact('page'));
return new Response(array( 'body' => $render('index', compact('posts'), compact('request')) )); }));
/** * Handles adding new posts. */Router::connect('/add', array(), function($request) use ($render) { $post = Posts::create();
if (($request->data) && $post->save($request->data)) { return new Response(array('location' => '/')); } return new Response(array( 'body' => $render('edit', compact('post'), compact('request')) ));});
/** * Edits existing pages. */Router::connect('/{:slug:[a-z0-9\-]+}/edit', array('edit' => true), function($request) use ($render) { $conditions = array('slug' => $request->slug); $post = Posts::first(compact('conditions'));
if (($request->data) && $post->save($request->data)) { return new Response(compact('request') + array('location' => array('slug' => $post->slug))); } return new Response(array( 'body' => $render('edit', compact('post'), compact('request')) ));});
/** * Handles single page views. */Router::connect('/{:slug:[a-z0-9\-]+}', array(), function($request) use ($render) { $conditions = array('slug' => $request->slug, 'published' => true); $post = Posts::first(compact('conditions'));
return new Response(array( 'status' => $post ? 200 : 404, 'body' => $render($post ? 'view' : 'error', compact('post'), compact('request')) ));});
WAX ON,
WAX OFF
DEPENDENCIES
class User {
public function somethingUseful() { Logger::info("Something useful is happening"); // ... }}
class User {
public function somethingUseful() { Logger::info("Something useful is happening"); // ... }}
FALE
Super dumb!!
COUPLING RELEVANCE
Post::applyFilter('save', function($self, $params, $chain) { $entity =& $params['entity'];
if (!$entity->exists()) { $entity->created = time(); } return $chain->next($self, $params, $chain);});
?
Post::applyFilter('save', function($self, $params, $chain) { $entity =& $params['entity'];
if (!$entity->exists()) { $entity->created = time(); } return $chain->next($self, $params, $chain);});
?FALE
class WebService {
protected $_classes = array( 'socket' => 'lithium\net\http\socket\Context', 'request' => 'lithium\net\http\Request', 'response' => 'lithium\net\http\Response' );}
use app\models\Post;use li3_elasticsearch\extensions\data\behavior\Searchable;
Post::applyFilter('save', function($self, $params, $chain) { $entity =& $params['entity']; $id = $entity->guid; Searchable::add('public_posts', 'stream', $id, $entity->to('array')); return $chain->next($self, $params, $chain);});
REUSABILITY
require LITHIUM_LIBRARY_PATH . '/lithium/core/Object.php';require LITHIUM_LIBRARY_PATH . '/lithium/core/StaticObject.php';require LITHIUM_LIBRARY_PATH . '/lithium/util/Collection.php';require LITHIUM_LIBRARY_PATH . '/lithium/util/collection/Filters.php';require LITHIUM_LIBRARY_PATH . '/lithium/util/Inflector.php';require LITHIUM_LIBRARY_PATH . '/lithium/util/String.php';require LITHIUM_LIBRARY_PATH . '/lithium/core/Adaptable.php';require LITHIUM_LIBRARY_PATH . '/lithium/core/Environment.php';require LITHIUM_LIBRARY_PATH . '/lithium/net/Message.php';require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Message.php';require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Media.php';require LITHIUM_LIBRARY_PATH . '/lithium/net/http/Request.php';require ....
define('LITHIUM_APP_PATH', dirname(dirname(__DIR__)));define('LITHIUM_LIBRARY_PATH', LITHIUM_APP_PATH . '/../libraries');
Remove whenplugin-izing
config/bootstrap/libraries.php
Make conditional
Libraries::add('my_application', array('default' => true
));
my_awesome_plugin/ config/ bootstrap.php routes.php anything_in_an_app/
Required
Optional
Unless youdisable it
Libraries::add('my_awesome_plugin', array( 'bootstrap' => false));
Happens in the filterwe saw in action.php
DEMO...