Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

.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⽇日星期⽇日

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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 1 12年4月22⽇日星期⽇日

Slide 15

Slide 15 text

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 1 12年4月22⽇日星期⽇日

Slide 16

Slide 16 text

12年4月22⽇日星期⽇日

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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⽇日星期⽇日

Slide 21

Slide 21 text

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⽇日星期⽇日

Slide 22

Slide 22 text

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⽇日星期⽇日

Slide 23

Slide 23 text

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⽇日星期⽇日

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

12年4月22⽇日星期⽇日

Slide 34

Slide 34 text

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⽇日星期⽇日

Slide 35

Slide 35 text

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⽇日星期⽇日

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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⽇日星期⽇日

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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⽇日星期⽇日

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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⽇日星期⽇日

Slide 44

Slide 44 text

table('authors')->insert([ 'name' => 'Mary', 'address' => 'Paris', ])->build(); 12年4月22⽇日星期⽇日

Slide 45

Slide 45 text

-- 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⽇日星期⽇日

Slide 46

Slide 46 text

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⽇日星期⽇日

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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⽇日星期⽇日

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

'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⽇日星期⽇日

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

name , "\n" } Iterator 12年4月22⽇日星期⽇日

Slide 55

Slide 55 text

where() ->equal('name','Foo') ->groupBy('name','address'); ?> SQLBuilder Mix-In 12年4月22⽇日星期⽇日

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

Relationship 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⽇日星期⽇日

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Slide 63

Slide 63 text

column('address') ->varchar(128); } } 12年4月22⽇日星期⽇日

Slide 64

Slide 64 text

column('address') ->integer(); } } 12年4月22⽇日星期⽇日

Slide 65

Slide 65 text

column('address') ->timestamp(); } } 12年4月22⽇日星期⽇日

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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⽇日星期⽇日

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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⽇日星期⽇日

Slide 86

Slide 86 text

// 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⽇日星期⽇日

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

$ lazy build-conf config/database.yml Convert YAML to PHP.

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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⽇日星期⽇日

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

12年4月22⽇日星期⽇日

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

$ 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⽇日星期⽇日

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

12年4月22⽇日星期⽇日

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

$ 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⽇日星期⽇日

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

load( __DIR__ . '/.lazy.php'); $config->init(); 12年4月22⽇日星期⽇日

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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⽇日星期⽇日

Slide 111

Slide 111 text

$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⽇日星期⽇日

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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⽇日星期⽇日

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

Slide 119

Slide 119 text

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⽇日星期⽇日

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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⽇日星期⽇日

Slide 122

Slide 122 text

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⽇日星期⽇日

Slide 123

Slide 123 text

12年4月22⽇日星期⽇日

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

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