THE STATE OF LITHIUM
li3_nyc::init() // 2012-02-07
SPONSORS
AGENDA
• State   of the framework

• New     features

• Upcoming     features

• Community         plugins

• Library   management & The Lab

• Tips   & tricks

• Q&A     / Demos!
STATE OF THE FRAMEWORK
PROJECT & COMMUNITY STATS

• ~130   plugin repositories on GitHub

• 350+   commits from 40+ contributors since 0.10

• 60+   commits to the manual since 0.10

• Closed   250+ issues since moving to GitHub

• 175   pull requests submitted since moving to GitHub
ROADMAP PROGRESS

• Cookie   signing / encryption

• CSRF    protection

• Error   Handling

• Manual   / automatic SQL result mapping

• Relationship   support
NEW FEATURES
ENCRYPT & SIGN COOKIES

Session::config(array('default' => array(
       'adapter' => 'Cookie',
       'strategies' => array(
           'Hmac' => array('secret' => '$f00bar$'),
           'Encrypt' => array('secret' => '$f00bar$')
       )
)));
NEST ROUTES
Router::connect("/admin/{:args}", array('admin' => true), array(
    'continue' => true
));

Router::connect("/{:locale:en|de|jp}/{:args}", array(), array(
    'continue' => true
));

Router::connect("/{:args}.{:type}", array(), array(
    'continue' => true
));
HANDLE ERRORS
$method = array(Connections::get('default'), 'read');

ErrorHandler::apply($method, array(), function($error, $params) {
    $queryParams = json_encode($params['query']);
    $msg = "Query error: {$error['message']}";

      Logger::warning("{$msg}, data: {$queryParams}");
      return new DocumentSet();
});
PROTECT FORMS
<?=$this->form->create(); ?>
    <?=$this->security->requestToken(); ?>
    <?=$this->form->field('title'); ?>
    <?=$this->form->submit('Submit'); ?>
<?=$this->form->end(); ?>


public function add() {
    if (
         $this->request->data &&
         !RequestToken::check($this->request)
    ) {
         // Badness!!
    }
}
UPCOMING FEATURES
HTTP SERVICE CLASSES
$http = new Http(array(
    ...
    'methods' => array(
        'do' => array('method' => 'post', 'path' => '/do')
    )
));
                                                POST /do HTTP/1.1
$response = $http->do(new Query(array(
                                                …
     'data' => array('title' => 'sup')
)));                                            title=sup
// var_dump($response->data());
FILTER / SORT COLLECTIONS

$users->find(array("type" => "author"))->sort("name");



$users->first(array("name" => "Nate"));
MULTIBYTE CLASS


• Supports   mbstring, intl, iconv & (womp, womp) plain PHP

• Only   one method right now: strlen()

• More   would be good… open a pull request
SCHEMA CLASS


• Arbitrary   data types

• Arbitrary   handlers

• Example: hash   modified passwords before writing

• Other   example: JSON en/decode arbitrary data for MySQL
PLUGINS
LI3_DOCTRINE2
                                Connections::add('default', array(
                                    'type' => 'Doctrine',
• Works   with Lithium stuff:       'driver' => 'pdo_mysql',
                                    'host' => 'localhost',
                                    'user' => 'root',
 • Connections                      'password' => 'password',
                                    'dbname' => 'my_db'
                                ));
 • Authentication               /**
                                 * @Entity
                                 * @Table(name="users")

 • Forms                         */
                                class User extends li3_doctrine2modelsBaseEntity {
                                    /**
                                     * @Id

 • Sessions                          * @GeneratedValue
                                     * @Column(type="integer")
                                     */
                                    private $id;

 • Validation                       /**
                                     * @Column(type="string",unique=true)
                                     */
                                    private $email;
                                    ...
                                }
LI3_FILESYSTEM
use li3_filesystemstorageFileSystem;

FileSystem::config(array(
    'default' => array(
        'adapter' => 'File'
    ),
    'ftp' => array(
        'adapter' => 'Stream',
        'wrapper' => 'ftp',
        'path' => 'user:password@example.com/pub/'
    }
));

FileSystem::write('default', '/path/to/file', $data);
LI3_ACCESS
Access::config(array(
    'asset' => array(
        'adapter' => 'Rules',
        'allowAny' => true,
        'default' => array('isPublic', 'isOwner', 'isParticipant'),
        'user'     => function() { return Accounts::current(); },
        'rules'    => array(
            'isPublic' => function($user, $asset, $options) {
                    ...
               },
               'isOwner' => function($user, $asset, $options) {
                    ...
               },
               'isParticipant' => function($user, $asset, $options) {
                    ...
               }
           )
     )
     ...
);
LI3_ACCESS
Access::config(array(
    ...
    'action' => array(
        'adapter' => 'Rules',
        'default' => array('isPublic', 'isAuthenticated'),
        'allowAny' => true,
        'user'      => function() { return Accounts::current(); },
        'rules'     => array(
            'isPublic' => function($user, $request, array $options) {
                ...
            },
            'isAuthenticated' => function($user, $request, array $options) {
                ...
            },
            'isAdmin' => function($user, $asset, $options) {
                ...
            }
        )
    )
);
LI3_ACCESS
Dispatcher::applyFilter('_call', function($self, $params, $chain) {
    $opts    = $params['params'];
    $request = $params['request'];
    $ctrl    = $params['callable'];

      if ($access = Access::check('action', null, $ctrl, $opts)) {
          // Reject
      }
      if ($access = Access::check('action', null, $ctrl, array('rules' => 'isAdmin') + $opts)) {
          // Reject
      }
      return $chain->next($self, $params, $chain);
});




class PostsController extends Base {

      public $publicActions = array('index', 'view');
}
http://bit.ly/li3plugins
THE LAB
TIPS & TRICKS
RETURN ON REDIRECT

class PostsController extends Base {

    public function view($post) {
        if (!$post) {
            $this->redirect("Posts::index");
        }
        // Herp derp
    }
}
FAT FILTERS == BAD
Dispatcher::applyFilter('run', function($self, $params, $chain) {
    // Herp
    // Derp
    // Herp
    // Derp
    // Herp
    // Derp
    // Herp
    // Derp
    // Herp
    // Derp
    // Herp
    (repeat x100)
});
DEFINE YOUR SCHEMA!

{
      count: 5,
      ...
}
...
{
      count: "5",
      ...
}
DON’T FEAR THE SUBCLASS
class Base extends lithiumdataModel {

    public function save($entity, $data = null, array $options = array()) {
        if ($data) {
            $entity->set($data);
        }
        if (!$entity->exists()) {
            $entity->created = new MongoDate();
        }
        $entity->updated = new MongoDate();
        return parent::save($entity, null, $options);
    }
}

class Posts extends Base {
    ...
}
QUICK & DIRTY ADMIN
Dispatcher::config(array('rules' => array(
     'admin' => array(
         'action' => 'admin_{:action}'
    )
)));
                                   class PostsController extends Base {
Router::connect(
                                      public function admin_index() {
    '/admin/{:args}',
                                          ...
    array('admin' => true),
                                      }
    array('continue' => true)
);
                                      public function index() {
                                          ...
                                      }
                                  }
Q&A / DEMOS
THANKS!

The State of Lithium

  • 1.
    THE STATE OFLITHIUM li3_nyc::init() // 2012-02-07
  • 2.
  • 3.
    AGENDA • State of the framework • New features • Upcoming features • Community plugins • Library management & The Lab • Tips & tricks • Q&A / Demos!
  • 4.
    STATE OF THEFRAMEWORK
  • 5.
    PROJECT & COMMUNITYSTATS • ~130 plugin repositories on GitHub • 350+ commits from 40+ contributors since 0.10 • 60+ commits to the manual since 0.10 • Closed 250+ issues since moving to GitHub • 175 pull requests submitted since moving to GitHub
  • 6.
    ROADMAP PROGRESS • Cookie signing / encryption • CSRF protection • Error Handling • Manual / automatic SQL result mapping • Relationship support
  • 7.
  • 8.
    ENCRYPT & SIGNCOOKIES Session::config(array('default' => array( 'adapter' => 'Cookie', 'strategies' => array( 'Hmac' => array('secret' => '$f00bar$'), 'Encrypt' => array('secret' => '$f00bar$') ) )));
  • 9.
    NEST ROUTES Router::connect("/admin/{:args}", array('admin'=> true), array( 'continue' => true )); Router::connect("/{:locale:en|de|jp}/{:args}", array(), array( 'continue' => true )); Router::connect("/{:args}.{:type}", array(), array( 'continue' => true ));
  • 10.
    HANDLE ERRORS $method =array(Connections::get('default'), 'read'); ErrorHandler::apply($method, array(), function($error, $params) { $queryParams = json_encode($params['query']); $msg = "Query error: {$error['message']}"; Logger::warning("{$msg}, data: {$queryParams}"); return new DocumentSet(); });
  • 11.
    PROTECT FORMS <?=$this->form->create(); ?> <?=$this->security->requestToken(); ?> <?=$this->form->field('title'); ?> <?=$this->form->submit('Submit'); ?> <?=$this->form->end(); ?> public function add() { if ( $this->request->data && !RequestToken::check($this->request) ) { // Badness!! } }
  • 12.
  • 13.
    HTTP SERVICE CLASSES $http= new Http(array( ... 'methods' => array( 'do' => array('method' => 'post', 'path' => '/do') ) )); POST /do HTTP/1.1 $response = $http->do(new Query(array( … 'data' => array('title' => 'sup') ))); title=sup // var_dump($response->data());
  • 14.
    FILTER / SORTCOLLECTIONS $users->find(array("type" => "author"))->sort("name"); $users->first(array("name" => "Nate"));
  • 15.
    MULTIBYTE CLASS • Supports mbstring, intl, iconv & (womp, womp) plain PHP • Only one method right now: strlen() • More would be good… open a pull request
  • 16.
    SCHEMA CLASS • Arbitrary data types • Arbitrary handlers • Example: hash modified passwords before writing • Other example: JSON en/decode arbitrary data for MySQL
  • 17.
  • 18.
    LI3_DOCTRINE2 Connections::add('default', array( 'type' => 'Doctrine', • Works with Lithium stuff: 'driver' => 'pdo_mysql', 'host' => 'localhost', 'user' => 'root', • Connections 'password' => 'password', 'dbname' => 'my_db' )); • Authentication /** * @Entity * @Table(name="users") • Forms */ class User extends li3_doctrine2modelsBaseEntity { /** * @Id • Sessions * @GeneratedValue * @Column(type="integer") */ private $id; • Validation /** * @Column(type="string",unique=true) */ private $email; ... }
  • 19.
    LI3_FILESYSTEM use li3_filesystemstorageFileSystem; FileSystem::config(array( 'default' => array( 'adapter' => 'File' ), 'ftp' => array( 'adapter' => 'Stream', 'wrapper' => 'ftp', 'path' => 'user:[email protected]/pub/' } )); FileSystem::write('default', '/path/to/file', $data);
  • 20.
    LI3_ACCESS Access::config(array( 'asset' => array( 'adapter' => 'Rules', 'allowAny' => true, 'default' => array('isPublic', 'isOwner', 'isParticipant'), 'user' => function() { return Accounts::current(); }, 'rules' => array( 'isPublic' => function($user, $asset, $options) { ... }, 'isOwner' => function($user, $asset, $options) { ... }, 'isParticipant' => function($user, $asset, $options) { ... } ) ) ... );
  • 21.
    LI3_ACCESS Access::config(array( ... 'action' => array( 'adapter' => 'Rules', 'default' => array('isPublic', 'isAuthenticated'), 'allowAny' => true, 'user' => function() { return Accounts::current(); }, 'rules' => array( 'isPublic' => function($user, $request, array $options) { ... }, 'isAuthenticated' => function($user, $request, array $options) { ... }, 'isAdmin' => function($user, $asset, $options) { ... } ) ) );
  • 22.
    LI3_ACCESS Dispatcher::applyFilter('_call', function($self, $params,$chain) { $opts = $params['params']; $request = $params['request']; $ctrl = $params['callable']; if ($access = Access::check('action', null, $ctrl, $opts)) { // Reject } if ($access = Access::check('action', null, $ctrl, array('rules' => 'isAdmin') + $opts)) { // Reject } return $chain->next($self, $params, $chain); }); class PostsController extends Base { public $publicActions = array('index', 'view'); }
  • 23.
  • 24.
  • 25.
  • 26.
    RETURN ON REDIRECT classPostsController extends Base { public function view($post) { if (!$post) { $this->redirect("Posts::index"); } // Herp derp } }
  • 27.
    FAT FILTERS ==BAD Dispatcher::applyFilter('run', function($self, $params, $chain) { // Herp // Derp // Herp // Derp // Herp // Derp // Herp // Derp // Herp // Derp // Herp (repeat x100) });
  • 28.
    DEFINE YOUR SCHEMA! { count: 5, ... } ... { count: "5", ... }
  • 29.
    DON’T FEAR THESUBCLASS class Base extends lithiumdataModel { public function save($entity, $data = null, array $options = array()) { if ($data) { $entity->set($data); } if (!$entity->exists()) { $entity->created = new MongoDate(); } $entity->updated = new MongoDate(); return parent::save($entity, null, $options); } } class Posts extends Base { ... }
  • 30.
    QUICK & DIRTYADMIN Dispatcher::config(array('rules' => array( 'admin' => array( 'action' => 'admin_{:action}' ) ))); class PostsController extends Base { Router::connect( public function admin_index() { '/admin/{:args}', ... array('admin' => true), } array('continue' => true) ); public function index() { ... } }
  • 31.
  • 32.