Upgrade to Pro — share decks privately, control downloads, hide ads and more …

LazyRecord: The Fast PHP ORM

LazyRecord: The Fast PHP ORM

Yo-An Lin

April 22, 2012
Tweet

More Decks by Yo-An Lin

Other Decks in Programming

Transcript

  1. LazyRecord
    The Fast PHP ORM
    林佑安
    Yo-An Lin (c9s)
    12年4月22⽇日星期⽇日

    View Slide

  2. .metadata
    • 林佑安 (c9s)
    • 190 repo/projects on GitHub
    • Perl programming since 2008
    • PHP programming since last year
    • c, c++, javascript, obj-c, ruby, python,
    haskell, java, c#, VB .NET ...
    12年4月22⽇日星期⽇日

    View Slide

  3. Why another PHP
    ORM ?
    12年4月22⽇日星期⽇日

    View Slide

  4. PHP ORMs
    • Doctrine
    12年4月22⽇日星期⽇日

    View Slide

  5. PHP ORMs
    • Doctrine
    • Propel
    12年4月22⽇日星期⽇日

    View Slide

  6. PHP ORMs
    • Doctrine
    • Propel
    • Idiorm / Paris
    12年4月22⽇日星期⽇日

    View Slide

  7. Propel / Doctrine
    • Propel uses XML Schema file.
    12年4月22⽇日星期⽇日

    View Slide

  8. Propel / Doctrine
    • Propel uses XML Schema file.
    • Doctrine uses XML/YAML/Annotations.
    12年4月22⽇日星期⽇日

    View Slide

  9. Propel / Doctrine
    • Propel uses XML Schema file.
    • Doctrine uses XML/YAML/Annotations.
    • Slow & Fat.
    12年4月22⽇日星期⽇日

    View Slide

  10. Propel / Doctrine
    • Propel uses XML Schema file.
    • Doctrine uses XML/YAML/Annotations.
    • Slow & Fat.
    • Doctrine is too complicated.
    12年4月22⽇日星期⽇日

    View Slide

  11. Common characteristic
    12年4月22⽇日星期⽇日

    View Slide

  12. • XML for configuration file.
    • XML for schema file.
    • XML for everything.
    • Concepts are from Java, too complicated.
    12年4月22⽇日星期⽇日

    View Slide

  13. Propel XML
    runtime.conf
    12年4月22⽇日星期⽇日

    View Slide




  14. propel-bookstore
    console
    7




    sqlite

    DebugPDO
    mysql:host=localhost;dbname=bookstore
    testuser
    password

    false


    true


    utf8

    set search_path myschema, public
    INSERT INTO BAR ('hey', 'there')





    mysql:host=slave-server1; dbname=bookstore


    mysql:host=slave-server2; dbname=bookstore








    true


    true
    3


    true
    1

    12年4月22⽇日星期⽇日

    View Slide


  15. sqlite

    DebugPDO
    mysql:host=localhost;dbname=bookstore
    testuser
    password

    false


    true


    utf8

    set search_path myschema, public
    INSERT INTO BAR ('hey', 'there')





    mysql:host=slave-server1; dbname=bookstore


    mysql:host=slave-server2; dbname=bookstore








    true


    true
    3


    true
    1






    12年4月22⽇日星期⽇日

    View Slide

  16. 12年4月22⽇日星期⽇日

    View Slide

  17. It should be simpler
    12年4月22⽇日星期⽇日

    View Slide

  18. Inspirations
    • JiftyDBI / Perl
    • KiokuDB / Perl
    • ActiveRecord / Ruby
    • Propel / PHP
    12年4月22⽇日星期⽇日

    View Slide

  19. ActiveRecord Pattern
    12年4月22⽇日星期⽇日

    View Slide

  20. client = Client.find(10)
    client = Client.first
    Client.where("orders_count = ?", params[:orders])
    Client.where("created_at >= :start_date AND created_at <= :end_date",
    {:start_date => params[:start_date], :end_date => params[:end_date]})
    Client.order("created_at DESC")
    Client.limit(5).offset(30)
    12年4月22⽇日星期⽇日

    View Slide

  21. Object::Declare
    Audrey Tang
    唐鳳
    use Object::Declare ['MyApp::Column', 'MyApp::Param'];
    my %objects = declare {
    param foo =>
    !is global,
    is immutable,
    valid_values are qw( more values );
    column bar =>
    field1 is 'value',
    field2 is 'some_other_value',
    sub_params are param( is happy ), param ( is sad );
    };
    print $objects{foo}; # a MyApp::Param object
    print $objects{bar}; # a MyApp::Column object
    # Assuming that MyApp::Column::new simply blesses into a hash...
    print $objects{bar}{sub_params}[0]; # a MyApp::Param object
    print $objects{bar}{sub_params}[1]; # a MyApp::Param object
    2006
    12年4月22⽇日星期⽇日

    View Slide

  22. Jifty::DBI
    package Simple;
    use Jifty::DBI::Schema;
    use Jifty::DBI::Record schema {
    column foo => type is 'text';
    column bar => type is 'text';
    };
    12年4月22⽇日星期⽇日

    View Slide

  23. Jifty::DBI
    package TestApp::Model::Phone;
    use Jifty::DBI::Schema;
    use Jifty::DBI::Record schema {
    column user => references TestApp::Model::User by 'id',
    is mandatory;
    column type => ...;
    column value =>
    validator is sub { ... },
    default is sub { }
    ;
    };
    12年4月22⽇日星期⽇日

    View Slide

  24. Jifty
    Model Schema
    12年4月22⽇日星期⽇日

    View Slide

  25. Jifty
    Model Schema ⇛ Action
    12年4月22⽇日星期⽇日

    View Slide

  26. Jifty
    Model Schema ⇛ Action ⇛
    CRUD
    12年4月22⽇日星期⽇日

    View Slide

  27. Jifty
    App::Model::Phone ☚ write once
    12年4月22⽇日星期⽇日

    View Slide

  28. Jifty
    App::Model::Phone
    App::Model::PhoneCollection
    12年4月22⽇日星期⽇日

    View Slide

  29. Jifty
    App::Model::Phone
    App::Model::PhoneCollection
    App::Action::CreatePhone
    12年4月22⽇日星期⽇日

    View Slide

  30. Jifty
    App::Model::Phone
    App::Model::PhoneCollection
    App::Action::CreatePhone
    App::Action::UpdatePhone
    12年4月22⽇日星期⽇日

    View Slide

  31. Jifty
    App::Model::Phone
    App::Model::PhoneCollection
    App::Action::CreatePhone
    App::Action::UpdatePhone
    App::Action::DeletePhone
    12年4月22⽇日星期⽇日

    View Slide

  32. Jifty
    App::Model::Phone
    App::Model::PhoneCollection
    App::Action::CreatePhone
    App::Action::UpdatePhone
    App::Action::DeletePhone
    $phone->as_create_action()->render();
    12年4月22⽇日星期⽇日

    View Slide

  33. 12年4月22⽇日星期⽇日

    View Slide

  34. What we need
    • Can use PHP closures for validation, default value,
    completion ..etc.
    • Everything should be lazy.
    • Simple API
    • No overdesign.
    • Mixin schema
    • CRUD generation.
    • Front-end CRUD integration.
    12年4月22⽇日星期⽇日

    View Slide

  35. PHP 5.3 Characteristic
    • APC is fast.
    • require / include is slow.
    • json_encode / json_decode are slower than
    require.
    • method is slower than properties.
    • magic method is slower than normal method.
    • array is always faster than object.
    12年4月22⽇日星期⽇日

    View Slide

  36. $array[] vs array_push
    https://github.com/c9s/SimpleBench
    12年4月22⽇日星期⽇日

    View Slide

  37. Function calls
    https://github.com/c9s/SimpleBench
    12年4月22⽇日星期⽇日

    View Slide

  38. PHP ORM v1
    12年4月22⽇日星期⽇日

    View Slide

  39. EteDB
    • Initialize model schema in runtime.
    • Schema is defined in Model (in
    __constructor).
    • MySQL only.
    • dynamic class generator (using eval)
    • too slow.
    12年4月22⽇日星期⽇日

    View Slide

  40. PHP ORM v2
    12年4月22⽇日星期⽇日

    View Slide

  41. LazyRecord
    • Lazy schema
    loader
    • Lazy attribute
    • Lazy class loader
    • Lazy connection
    • Static class
    generator
    • SQL Generator
    for MySQL,
    PgSQL, SQLite
    • SplClassLoader
    • ... etc
    12年4月22⽇日星期⽇日

    View Slide

  42. Based on SQLBuilder
    12年4月22⽇日星期⽇日

    View Slide

  43. SQLBuilder
    • A Simple SQL Generator.
    • Prevent Injection.
    • Migration generator. (index, alter table...etc)
    • Support SQLite, Pgsql, Mysql syntax.
    • Pure SQL or with named-parameters.
    12年4月22⽇日星期⽇日

    View Slide

  44. $sqlbuilder = new SQLBuilder\QueryBuilder( $driver );
    $sql = $sqlbuilder->table('authors')->insert([
    'name' => 'Mary',
    'address' => 'Paris',
    ])->build();
    12年4月22⽇日星期⽇日

    View Slide

  45. -- General syntax
    INSERT INTO authors ( name , address ) VALUES ( 'Name' , 'Address' );
    -- PgSQL
    INSERT INTO "Authors" ( "Name" , "Address" ) VALUES ( 'Name' , 'Address' );
    -- MySQL
    INSERT INTO `Authors` ( `Name` , `Address` ) VALUES ( 'Name' , 'Address' );
    -- PDO
    INSERT INTO authors ( name , address ) VALUES ( ? , ? );
    INSERT INTO authors ( name , address ) VALUES ( :name , :address );
    12年4月22⽇日星期⽇日

    View Slide

  46. $sql = $builder->table('Member')->select('*')
    ->where()
    ->equal( 'a' , 'bar' ) // a = 'bar'
    ->notEqual( 'a' , 'bar' ) // a != 'bar'
    ->is( 'a' , 'null' ) // a is null
    ->isNot( 'a' , 'null' ) // a is not equal
    ->greater( 'a' , '2011-01-01' );
    ->greater( 'a' , ['date(2011-01-01)'] ); // do not escape
    ->or()->less( 'a' , 123 )
    ->and()->like( 'content' , '%content%' );
    ->group() // and ( a = 123 or b != 123 )
    ->is( 'a' , 123 )
    ->isNot( 'b', 123 )
    ->ungroup()
    ->build();
    12年4月22⽇日星期⽇日

    View Slide

  47. Overview
    12年4月22⽇日星期⽇日

    View Slide

  48. Model Overview
    12年4月22⽇日星期⽇日

    View Slide

  49. $author = new Author;
    $ret = $author->create([
    'name' => "Deflator Test $i",
    'country' => 'Tokyo',
    'confirmed' => true,
    'date' => new DateTime('2011-01-01 00:00:00'),
    ]);
    if( $ret->success ) {
    echo "Created!";
    }
    12年4月22⽇日星期⽇日

    View Slide

  50. $ret = $author->update(array( 'name' => 'Bar' ));
    if( $ret->success ) {
    echo "Updated!";
    }
    else {
    echo $ret; // __toString support
    }
    12年4月22⽇日星期⽇日

    View Slide

  51. $record = Author::load(array( 'name' => 'Foo' ));
    // To find a record with primary key:
    $record = Author::load( 1 );
    // To update a record (static):
    $ret = Author::update( array(
    'name' => 'Author'
    ))->where()
    ->equal('id',3)
    ->execute();
    12年4月22⽇日星期⽇日

    View Slide

  52. $author->toJson();
    $author->toArray();
    $author->toXml();
    $author->toYaml();
    12年4月22⽇日星期⽇日

    View Slide

  53. Collection Overview
    12年4月22⽇日星期⽇日

    View Slide

  54. $authors = new AuthorCollection;
    foreach( $authors as $author ) {
    echo $author->name , "\n"
    }
    Iterator
    12年4月22⽇日星期⽇日

    View Slide

  55. $names = new NameCollection;
    $names->where()
    ->equal('name','Foo')
    ->groupBy('name','address');
    ?>
    SQLBuilder Mix-In
    12年4月22⽇日星期⽇日

    View Slide

  56. $newCollection = $names->filter(function($item) {
    // do something else
    })->filter(function($item) {
    return $item->confirmed;
    });
    ?>
    Filter
    12年4月22⽇日星期⽇日

    View Slide

  57. $names->each(function($item) {
    $item->update([ .... ]);
    });
    ?>
    Each
    12年4月22⽇日星期⽇日

    View Slide

  58. /* page 1, 10 per page */
    $authors = new AuthorCollection;
    $pager = $authors->pager(1,10);
    $pager = $authors->pager();
    $items = $pager->items();
    $pager->next(); // next page
    ?>
    Integrate with OFFSET & LIMIT
    Collection Pager
    12年4月22⽇日星期⽇日

    View Slide

  59. Relationship
    // has many
    $address = $author->addresses->create([
    'address' => 'farfaraway'
    ]);
    // create related address
    $author->addresses[] = [
    'address' => 'Harvard'
    ];
    $addresses = $author->addresses->items();
    foreach( $author->addresses as $address ) {
    echo $address->address , "\n";
    }
    12年4月22⽇日星期⽇日

    View Slide

  60. Schema
    12年4月22⽇日星期⽇日

    View Slide

  61. Powered by
    CascadingAttribute.php
    12年4月22⽇日星期⽇日

    View Slide

  62. use LazyRecord\Schema\SchemaDeclare;
    class AddressSchema extends SchemaDeclare
    {
    function schema()
    {
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  63. use LazyRecord\Schema\SchemaDeclare;
    class AddressSchema extends SchemaDeclare
    {
    function schema()
    {
    $this->column('address')
    ->varchar(128);
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  64. use LazyRecord\Schema\SchemaDeclare;
    class AddressSchema extends SchemaDeclare
    {
    function schema()
    {
    $this->column('address')
    ->integer();
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  65. use LazyRecord\Schema\SchemaDeclare;
    class AddressSchema extends SchemaDeclare
    {
    function schema()
    {
    $this->column('address')
    ->timestamp();
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  66. Default value & builder
    12年4月22⽇日星期⽇日

    View Slide

  67. $this->column('name')
    ->varchar(30)
    ->default('Default');
    12年4月22⽇日星期⽇日

    View Slide

  68. $this->column('name')
    ->varchar(30)
    ->default( array('current_timestamp') );
    12年4月22⽇日星期⽇日

    View Slide

  69. $this->column('name')
    ->varchar(30)
    ->defaultBuilder(function() {
    return date('c');
    })
    12年4月22⽇日星期⽇日

    View Slide

  70. $this->column('name')
    ->varchar(30)
    ->default('Default')
    ->default( array('current_timestamp') )
    ->defaultBuilder(function() {
    return date('c');
    })
    12年4月22⽇日星期⽇日

    View Slide

  71. Validator
    12年4月22⽇日星期⽇日

    View Slide

  72. $this->column('name')
    ->varchar(30)
    ->validator('ValidatorClass')
    12年4月22⽇日星期⽇日

    View Slide

  73. $this->column('name')
    ->varchar(30)
    ->validator( array('ValidatorClass','method') )
    12年4月22⽇日星期⽇日

    View Slide

  74. $this->column('name')
    ->varchar(30)
    ->validator('function_name')
    12年4月22⽇日星期⽇日

    View Slide

  75. $this->column('name')
    ->varchar(30)
    ->validator(function($val) { .... })
    12年4月22⽇日星期⽇日

    View Slide

  76. Filter
    12年4月22⽇日星期⽇日

    View Slide

  77. $this->column('name')
    ->varchar(30)
    ->filter( function($val) {
    return preg_replace('#word#','zz',$val);
    });
    12年4月22⽇日星期⽇日

    View Slide

  78. Deflator / Inflator
    12年4月22⽇日星期⽇日

    View Slide

  79. use LazyRecord\Schema\SchemaDeclare;
    class NameSchema extends SchemaDeclare
    {
    function schema()
    {
    $this->column('created_on')
    ->date()
    ->isa('DateTime')
    ->deflator( function($val) {
    if( is_a( $val, 'DateTime' ) )
    return $val->format('Y-m-d');
    elseif( is_integer($val) ) {
    return strftime( '%Y-%m-%d' , $val );
    }
    return $val;
    })
    ->inflator( function($val) {
    return new \DateTime( $val );
    });
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  80. $name->created_on; // DateTime object
    $name->created_on->format('Y-m-d');
    $name->create([
    'created_on' => new DateTime;
    ]);
    12年4月22⽇日星期⽇日

    View Slide

  81. Mixin schema
    12年4月22⽇日星期⽇日

    View Slide

  82. $this->mixin('MetadataMixinSchema');
    $this->mixin('I18nMixinSchema');
    $this->mixin('CommentMinxSchema');
    12年4月22⽇日星期⽇日

    View Slide

  83. Multiple data source
    12年4月22⽇日星期⽇日

    View Slide

  84. data_sources:
    master:
    dsn: 'mysql:host=localhost;dbname=lazy_test'
    user: root
    pass: 123123
    database.yml
    12年4月22⽇日星期⽇日

    View Slide

  85. data_sources:
    master:
    dsn: 'mysql:host=localhost;dbname=lazy_test'
    user: root
    pass: 123123
    slave:
    dsn: 'mysql:dbname=lazy_test'
    query_options: { quote_column: true, quote_table: true }
    database.yml
    12年4月22⽇日星期⽇日

    View Slide

  86. // data source for writing
    $this->writeTo('master');
    // data source for reading
    $this->readFrom('slave');
    data_sources:
    master:
    dsn: 'mysql:host=localhost;dbname=lazy_test'
    user: root
    pass: 123123
    slave:
    dsn: 'mysql:dbname=lazy_test'
    query_options: { quote_column: true, quote_table: true }
    database.yml
    schema
    12年4月22⽇日星期⽇日

    View Slide

  87. 拼裝時刻
    12年4月22⽇日星期⽇日

    View Slide

  88. LazyBone
    http://github.com/c9s/LazyBone.git
    12年4月22⽇日星期⽇日

    View Slide

  89. LazyBone =
    12年4月22⽇日星期⽇日

    View Slide

  90. LazyRecord
    + Roller Router
    + RESTful Plugin
    + Backbone.js
    12年4月22⽇日星期⽇日

    View Slide

  91. Install LazyRecord
    12年4月22⽇日星期⽇日

    View Slide

  92. sudo bash -c "$(curl -s -L https://raw.github.com/c9s/LazyRecord/master/install.sh)"
    12年4月22⽇日星期⽇日

    View Slide

  93. Define config file
    12年4月22⽇日星期⽇日

    View Slide

  94. ---
    bootstrap:
    - bootstrap.php
    schema:
    paths:
    - model
    data_sources:
    default:
    dsn: 'sqlite:/tmp/todos.db'
    config/database.yml
    12年4月22⽇日星期⽇日

    View Slide

  95. $ lazy build-conf config/database.yml
    Convert YAML to PHP.
    $config = require '.lazy.php';
    APC caches this automatically.
    12年4月22⽇日星期⽇日

    View Slide

  96. Define model
    12年4月22⽇日星期⽇日

    View Slide

  97. class TodoSchema extends LazyRecord\Schema\SchemaDeclare
    {
    function schema()
    {
    $this->column('id')
    ->primary()
    ->autoIncrement()
    ->integer();
    $this->column('title')
    ->text();
    $this->column('done')
    ->boolean()
    ->default(false);
    $this->column('created_on')
    ->defaultBuilder( function() { return date('c'); } )
    ->timestamp();
    }
    function bootstrap($model)
    {
    $model->create(array(
    'title' => 'Foo',
    ));
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  98. Create static schema
    files
    12年4月22⽇日星期⽇日

    View Slide

  99. 12年4月22⽇日星期⽇日

    View Slide

  100. $ lazy build-schema model/TodoSchema.php
    12年4月22⽇日星期⽇日

    View Slide

  101. $ lazy build-schema model/TodoSchema.php
    ...
    Classmap:
    ! TodoSchemaProxy => model/TodoSchemaProxy.php
    ! TodoBase => model/TodoBase.php
    ! Todo => model/Todo.php
    ! TodoCollectionBase => model/TodoCollectionBase.php
    ! TodoCollection => model/TodoCollection.php
    Done
    12年4月22⽇日星期⽇日

    View Slide

  102. Initialize database
    12年4月22⽇日星期⽇日

    View Slide

  103. 12年4月22⽇日星期⽇日

    View Slide

  104. $ lazy build-sql model/TodoSchema.php
    12年4月22⽇日星期⽇日

    View Slide

  105. $ lazy build-sql model/TodoSchema.php
    Building SQL for TodoSchema
    --- SQL for TodoSchema
    CREATE TABLE todos (
    id integer primary key autoincrement,
    title text,
    done boolean default 0,
    created_on timestamp
    );
    12年4月22⽇日星期⽇日

    View Slide

  106. Integrate with your
    application
    12年4月22⽇日星期⽇日

    View Slide

  107. use LazyRecord\ConfigLoader;
    $config = new ConfigLoader;
    $config->load( __DIR__ . '/.lazy.php');
    $config->init();
    12年4月22⽇日星期⽇日

    View Slide

  108. Define routes
    12年4月22⽇日星期⽇日

    View Slide

  109. Roller Router
    High performance router for PHP
    12年4月22⽇日星期⽇日

    View Slide

  110. Roller Router
    • APC cache
    • FileSystem cache
    • Use Array to store routes
    • through PHP extension, can dispatch 1607% faster than
    pure php version
    • Annotation reader support
    • RESTful plugin
    12年4月22⽇日星期⽇日

    View Slide

  111. $router = new Roller\Router;
    $router->get( '/blog/:id/:title' , function($id,$title) {
    return 'Blog';
    });
    $router->post( '/blog/:year/:month/:id/:title',
    array('Controller','method') );
    $router->any( '/path/to/:year' , array('Callback','method') , array(
    'year' => '\d+',
    ));
    12年4月22⽇日星期⽇日

    View Slide

  112. $subroutes = new Roller\RouteSet;
    $subroutes->add( '/subitem' , $cb );
    $routes = new Roller\RouteSet;
    $routes->mount( '/item' , $subroutes );
    /item/subitem => $cb
    RouteSet
    12年4月22⽇日星期⽇日

    View Slide

  113. Dispatch
    $r = $router->dispatch( isset($_SERVER['PATH_INFO'])
    ? $_SERVER['PATH_INFO']
    : '/' );
    if( $r ) {
    echo $r();
    } else {
    die('Page not found');
    }
    12年4月22⽇日星期⽇日

    View Slide

  114. RESTful Plugin
    12年4月22⽇日星期⽇日

    View Slide

  115. $router = new Roller\Router( null, array(
    'cache_id' => 'router_demo'
    ));
    $restful = new Roller\Plugin\RESTful(array(
    'prefix' => '/=/restful' ));
    $restful->setGenericHandler( 'MyGenericHandler' );
    $router->addPlugin($restful);
    12年4月22⽇日星期⽇日

    View Slide

  116. GET /=/restful/posts
    GET /=/restful/posts.json
    GET /=/restful/posts.yml
    GET /=/restful/posts/23
    GET /=/restful/posts/23.json
    POST /=/restful/posts/23
    DELETE /=/restful/posts/23
    Auto-generated routes
    12年4月22⽇日星期⽇日

    View Slide

  117. Define Your Resource
    Handler
    12年4月22⽇日星期⽇日

    View Slide

  118. use Roller\Plugin\RESTful\ResourceHandler;
    use Roller\Plugin\RESTful\GenericHandler;
    class MyGenericHandler extends GenericHandler
    {
    public function create($resource) {
    }
    public function load($resource,$id) {
    }
    public function update($resource,$id) {
    }
    public function delete($resource,$id) {
    }
    public function find($resource) {
    }
    }
    12年4月22⽇日星期⽇日

    View Slide

  119. namespace LazyBone\Resource;
    use Roller\Plugin\RESTful\ResourceHandler;
    use Todo;
    use TodoCollection;
    class TodoResource extends ResourceHandler
    {
    public function create()
    {
    $vars = json_decode($this->readInput(),true);
    $todo = new Todo;
    $ret = $todo->create($vars);
    if( $ret->success ) {
    return $todo->toArray();
    }
    $this->codeBadRequest();
    return array( 'error' => $ret->message );
    }
    public function update($id)
    {
    $todo = new Todo( $id );
    if( ! $todo->id ) {
    return $this->codeNotFound();
    }
    $vars = json_decode($this->readInput(),true);
    unset( $vars['created_on'] ); // lazy record bug
    if($vars) {
    $todo->update( $vars );
    return $todo->toArray();
    }
    return $this->codeBadRequest();
    }
    ....
    12年4月22⽇日星期⽇日

    View Slide

  120. Backbone.js
    12年4月22⽇日星期⽇日

    View Slide

  121. Todo = Backbone.Model.extend({
    // Default attributes for the todo item.
    defaults: function() {
    return {
    title: "empty todo...",
    done: false
    // order: Todos.nextOrder(),
    };
    },
    // Toggle the `done` state of this todo item.
    toggle: function() {
    this.save({done: !this.get("done")});
    },
    clear: function() {
    this.destroy();
    }
    });
    12年4月22⽇日星期⽇日

    View Slide

  122. TodoList = Backbone.Collection.extend({
    // Reference to this collection's model.
    model: Todo,
    url:"/=/todos",
    done: function() {
    return this.filter(function(todo){
    return todo.get('done'); });
    },
    remaining: function() {
    return this.without.apply(this, this.done());
    },
    });
    12年4月22⽇日星期⽇日

    View Slide

  123. 12年4月22⽇日星期⽇日

    View Slide

  124. Hacking
    forks welcome!
    http://github.com/c9s/LazyRecord.git
    12年4月22⽇日星期⽇日

    View Slide

  125. Q & A ?
    12年4月22⽇日星期⽇日

    View Slide