More Related Content Similar to LazyRecord: The Fast ORM for PHP Similar to LazyRecord: The Fast ORM for PHP (20) LazyRecord: The Fast ORM for PHP1. LazyRecord
The Fast PHP ORM
林佑安
Yo-An Lin (c9s)
12年4月22⽇日星期⽇日
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⽇日星期⽇日
5. PHP ORMs
• Doctrine
• Propel
12年4月22⽇日星期⽇日
6. PHP ORMs
• Doctrine
• Propel
• Idiorm / Paris
12年4月22⽇日星期⽇日
8. Propel / Doctrine
• Propel uses XML Schema file.
• Doctrine uses XML/YAML/Annotations.
12年4月22⽇日星期⽇日
9. Propel / Doctrine
• Propel uses XML Schema file.
• Doctrine uses XML/YAML/Annotations.
• Slow & Fat.
12年4月22⽇日星期⽇日
10. Propel / Doctrine
• Propel uses XML Schema file.
• Doctrine uses XML/YAML/Annotations.
• Slow & Fat.
• Doctrine is too complicated.
12年4月22⽇日星期⽇日
12. • XML for configuration file.
• XML for schema file.
• XML for everything.
• Concepts are from Java, too complicated.
12年4月22⽇日星期⽇日
14. <?xml version="1.0"?>
<config>
<log>
<ident>propel-bookstore</ident>
<type>console</type>
<level>7</level>
</log>
<propel>
<datasources default="bookstore">
<datasource id="bookstore">
<adapter>sqlite</adapter>
<connection>
<classname>DebugPDO</classname>
<dsn>mysql:host=localhost;dbname=bookstore</dsn>
<user>testuser</user>
<password>password</password>
<options>
<option id="ATTR_PERSISTENT">false</option>
</options>
<attributes>
<option id="ATTR_EMULATE_PREPARES">true</option>
</attributes>
<settings>
<setting id="charset">utf8</setting>
<setting id="queries">
<query>set search_path myschema, public</query><!-- automatically set postgresql's search_path -->
<query>INSERT INTO BAR ('hey', 'there')</query><!-- execute some other query -->
</setting>
</settings>
</connection>
<slaves>
<connection>
<dsn>mysql:host=slave-server1; dbname=bookstore</dsn>
</connection>
<connection>
<dsn>mysql:host=slave-server2; dbname=bookstore</dsn>
</connection>
</slaves>
</datasource>
</datasources>
<debugpdo>
<logging>
<details>
<method>
<enabled>true</enabled>
</method>
<time>
<enabled>true</enabled>
<precision>3</precision>
</time>
<mem>
<enabled>true</enabled>
<precision>1</precision>
12年4月22⽇日星期⽇日 </mem>
15. <datasource id="bookstore">
<adapter>sqlite</adapter>
<connection>
<classname>DebugPDO</classname>
<dsn>mysql:host=localhost;dbname=bookstore</dsn>
<user>testuser</user>
<password>password</password>
<options>
<option id="ATTR_PERSISTENT">false</option>
</options>
<attributes>
<option id="ATTR_EMULATE_PREPARES">true</option>
</attributes>
<settings>
<setting id="charset">utf8</setting>
<setting id="queries">
<query>set search_path myschema, public</query><!-- automatically set postgresql's search_path -->
<query>INSERT INTO BAR ('hey', 'there')</query><!-- execute some other query -->
</setting>
</settings>
</connection>
<slaves>
<connection>
<dsn>mysql:host=slave-server1; dbname=bookstore</dsn>
</connection>
<connection>
<dsn>mysql:host=slave-server2; dbname=bookstore</dsn>
</connection>
</slaves>
</datasource>
</datasources>
<debugpdo>
<logging>
<details>
<method>
<enabled>true</enabled>
</method>
<time>
<enabled>true</enabled>
<precision>3</precision>
</time>
<mem>
<enabled>true</enabled>
<precision>1</precision>
</mem>
</details>
</logging>
</debugpdo>
</propel>
</config>
12年4月22⽇日星期⽇日
18. Inspirations
• JiftyDBI / Perl
• KiokuDB / Perl
• ActiveRecord / Ruby
• Propel / PHP
12年4月22⽇日星期⽇日
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⽇日星期⽇日
21. Object::Declare
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
Audrey Tang
唐鳳
2006
12年4月22⽇日星期⽇日
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⽇日星期⽇日
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⽇日星期⽇日
24. Jifty
Model Schema
12年4月22⽇日星期⽇日
25. Jifty
Model Schema ⇛ Action
12年4月22⽇日星期⽇日
26. Jifty
Model Schema ⇛ Action ⇛
CRUD
12年4月22⽇日星期⽇日
27. Jifty
App::Model::Phone ☚ write once
12年4月22⽇日星期⽇日
28. Jifty
App::Model::Phone
App::Model::PhoneCollection
12年4月22⽇日星期⽇日
29. Jifty
App::Model::Phone
App::Model::PhoneCollection
App::Action::CreatePhone
12年4月22⽇日星期⽇日
30. Jifty
App::Model::Phone
App::Model::PhoneCollection
App::Action::CreatePhone
App::Action::UpdatePhone
12年4月22⽇日星期⽇日
31. Jifty
App::Model::Phone
App::Model::PhoneCollection
App::Action::CreatePhone
App::Action::UpdatePhone
App::Action::DeletePhone
12年4月22⽇日星期⽇日
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⽇日星期⽇日
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⽇日星期⽇日
35. PHP 5.3 Characteristic
• APC is fast.
• json_encode / json_decode (file) are slower than
require a simple array from php source code.
• function is faster than class method.
• class method is slower than properties.
• magic method is slower than normal class method.
• array is faster than object.
12年4月22⽇日星期⽇日
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⽇日星期⽇日
41. LazyRecord
• Lazy schema
loader
• SQL Generator
• Lazy attribute for MySQL,
PgSQL, SQLite
• Lazy class loader
• Lazy connection • SplClassLoader
• Static class • ... etc
generator
12年4月22⽇日星期⽇日
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⽇日星期⽇日
44. <?php
$sqlbuilder = new SQLBuilderQueryBuilder( $driver );
$sql = $sqlbuilder->table('authors')->insert([
'name' => 'Mary',
'address' => 'Paris',
])->build();
12年4月22⽇日星期⽇日
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⽇日星期⽇日
46. <?php
$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⽇日星期⽇日
49. <?php
$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⽇日星期⽇日
50. <?php
$ret = $author->update(array( 'name' => 'Bar' ));
if( $ret->success ) {
echo "Updated!";
}
else {
echo $ret; // __toString support
}
12年4月22⽇日星期⽇日
51. <?php
$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⽇日星期⽇日
52. $author->toJson();
$author->toArray();
$author->toXml();
$author->toYaml();
12年4月22⽇日星期⽇日
54. Iterator
<?php
$authors = new AuthorCollection;
foreach( $authors as $author ) {
echo $author->name , "n"
}
12年4月22⽇日星期⽇日
55. SQLBuilder Mix-In
<?php
$names = new NameCollection;
$names->where()
->equal('name','Foo')
->groupBy('name','address');
?>
12年4月22⽇日星期⽇日
56. Filter
<?php
$newCollection = $names->filter(function($item) {
// do something else
})->filter(function($item) {
return $item->confirmed;
});
?>
12年4月22⽇日星期⽇日
57. Each
<?php
$names->each(function($item) {
$item->update([ .... ]);
});
?>
12年4月22⽇日星期⽇日
58. Collection Pager
<?php
/* 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
12年4月22⽇日星期⽇日
59. Relationship
<?php
// 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⽇日星期⽇日
61. Powered by
CascadingAttribute.php
12年4月22⽇日星期⽇日
62. <?php
use LazyRecordSchemaSchemaDeclare;
class AddressSchema extends SchemaDeclare
{
function schema()
{
}
}
12年4月22⽇日星期⽇日
63. <?php
use LazyRecordSchemaSchemaDeclare;
class AddressSchema extends SchemaDeclare
{
function schema()
{
$this->column('address')
->varchar(128);
}
}
12年4月22⽇日星期⽇日
64. <?php
use LazyRecordSchemaSchemaDeclare;
class AddressSchema extends SchemaDeclare
{
function schema()
{
$this->column('address')
->integer();
}
}
12年4月22⽇日星期⽇日
65. <?php
use LazyRecordSchemaSchemaDeclare;
class AddressSchema extends SchemaDeclare
{
function schema()
{
$this->column('address')
->timestamp();
}
}
12年4月22⽇日星期⽇日
70. $this->column('name')
->varchar(30)
->default('Default')
->default( array('current_timestamp') )
->defaultBuilder(function() {
return date('c');
})
12年4月22⽇日星期⽇日
77. $this->column('name')
->varchar(30)
->filter( function($val) {
return preg_replace('#word#','zz',$val);
});
12年4月22⽇日星期⽇日
79. use LazyRecordSchemaSchemaDeclare;
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⽇日星期⽇日
84. database.yml
data_sources:
master:
dsn: 'mysql:host=localhost;dbname=lazy_test'
user: root
pass: 123123
12年4月22⽇日星期⽇日
85. database.yml
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 }
12年4月22⽇日星期⽇日
86. database.yml
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 }
schema
// data source for writing
$this->writeTo('master');
// data source for reading
$this->readFrom('slave');
12年4月22⽇日星期⽇日
88. LazyBone
http://github.com/c9s/LazyBone.git
12年4月22⽇日星期⽇日
90. LazyRecord
+ Roller Router
+ RESTful Plugin
+ Backbone.js
12年4月22⽇日星期⽇日
92. sudo bash -c "$(curl -s -L https://raw.github.com/c9s/LazyRecord/master/install.sh)"
12年4月22⽇日星期⽇日
94. config/database.yml
---
bootstrap:
- bootstrap.php
schema:
paths:
- model
data_sources:
default:
dsn: 'sqlite:/tmp/todos.db'
12年4月22⽇日星期⽇日
95. $ lazy build-conf config/database.yml
Convert YAML to PHP.
<?php
$config = require '.lazy.php';
APC caches this automatically.
12年4月22⽇日星期⽇日
97. <?php
class TodoSchema extends LazyRecordSchemaSchemaDeclare
{
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⽇日星期⽇日
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⽇日星期⽇日
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⽇日星期⽇日
107. <?php
use LazyRecordConfigLoader;
$config = new ConfigLoader;
$config->load( __DIR__ . '/.lazy.php');
$config->init();
12年4月22⽇日星期⽇日
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⽇日星期⽇日
111. $router = new RollerRouter;
$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⽇日星期⽇日
112. RouteSet
<?php
$subroutes = new RollerRouteSet;
$subroutes->add( '/subitem' , $cb );
$routes = new RollerRouteSet;
$routes->mount( '/item' , $subroutes );
/item/subitem => $cb
12年4月22⽇日星期⽇日
113. Dispatch
$r = $router->dispatch( isset($_SERVER['PATH_INFO'])
? $_SERVER['PATH_INFO']
: '/' );
if( $r ) {
echo $r();
} else {
die('Page not found');
}
12年4月22⽇日星期⽇日
115. <?php
$router = new RollerRouter( null, array(
'cache_id' => 'router_demo'
));
$restful = new RollerPluginRESTful(array(
'prefix' => '/=/restful' ));
$restful->setGenericHandler( 'MyGenericHandler' );
$router->addPlugin($restful);
12年4月22⽇日星期⽇日
116. Auto-generated routes
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
12年4月22⽇日星期⽇日
118. <?php
use RollerPluginRESTfulResourceHandler;
use RollerPluginRESTfulGenericHandler;
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⽇日星期⽇日
119. <?php
namespace LazyBoneResource;
use RollerPluginRESTfulResourceHandler;
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⽇日星期⽇日
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⽇日星期⽇日
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⽇日星期⽇日
124. Hacking
forks welcome!
http://github.com/c9s/LazyRecord.git
12年4月22⽇日星期⽇日