corneltek/lazyrecord

快速的PHP ORM


README

works badge Build Status Coverage Status Latest Stable Version Total Downloads Monthly Downloads Daily Downloads Latest Unstable Version License Join the chat at https://gitter.im/c9s/LazyRecord

LazyRecord 是一个针对 PHP5 的开源对象关系映射(ORM)。

LazyRecord 使用代码生成器生成静态代码,从而降低运行时成本,因此它非常轻量级且快速。

它允许您通过使用 ActiveRecord 模式 API 容易地访问您的数据库。

LazyRecord 与 PropelORM 不同,它不使用丑陋的 XML 作为其模式或配置文件,LazyRecord 使用更简单的 YAML 格式配置文件,并将 YAML 编译成纯 PHP 代码以提高配置加载的性能。

通过简单的模式设计,您可以轻松定义您的模型模式,您甚至可以在模式类中嵌入闭包。

另请参阅

LazyRecord: The Fast ORM for PHP <iframe src="http://www.slideshare.net/slideshow/embed_code/12638921" width="425" height="355" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe> 从 Yo-An Lin 查看更多 演示文稿

自动迁移演示

特性

  • 快速 & 简单
  • 基于 YAML 格式配置并编译成 PHP
  • 支持 PDO、MySQL、Pgsql、SQLite。
  • 多个数据源。
  • 混合模型。
  • 强大的迁移生成器
    • 升级 & 降级当然。
    • 自动迁移:根据模式差异自动生成迁移 SQL。
  • 模式/数据库差异

设计概念

  • PHP 中的函数调用非常慢,因此模型模式数据将静态构建,LazyRecord 将所有定义(默认值、验证器、过滤器、有效值构建器)转换为类和静态 PHP 数组,这使这些模型类非常轻量级且快速。

  • 在运行时,所有相同的模型对象使用相同的模式对象,我们可以重用静态模式类中预构建的数据。

  • 我们保持基础模型类构造函数为空,因此当您从数据库查询数据时,这些模型对象可以无成本地创建。

安装

请参阅 Wiki 中的详细信息

入门

配置数据库

将目录更改为您的项目,运行 init 命令以初始化您的数据库设置。

composer require corneltek/lazyrecord "^3"

如果您喜欢新版本,可以 require "dev-master"

composer require corneltek/lazyrecord "dev-master"

然后创建您的配置文件

$ vim db/config/database.yml

假设您的应用程序代码位于 src/ 目录中,那么您应该提供以下格式的模式路径

---
bootstrap:
- vendor/autoload.php   # load the classloader from composer.
schema:
  auto_id: 1
  paths:
    - src/    # where you store the schema class files.
data_sources:
  default: master
  nodes:
    master:
      dsn: 'sqlite:test'

在上面的配置文件中,auto_id 表示自动插入到每个模式类中的具有自增整数的自动增长主键列,因此您不需要在您的每个模式文件中声明主键 id 列。

编写模型模式

接下来,编写您的模型模式文件

$ vim src/YourApp/Model/UserSchema.php

将内容放入您的文件中

namespace YourApp\Model;
use LazyRecord\Schema;

class UserSchema extends Schema
{
    public function schema()
    {
        $this->column('account')
            ->varchar(16);
        $this->column('password')
            ->varchar(40)
            ->filter('sha1');
    }
}

构建静态模式文件

然后运行 build-schema 命令来构建静态模式文件

$ vendor/bin/lazy lazy schema build
Finding schemas...
Found schema classes
Initializing schema generator...
    YourApp\Model\UserSchemaProxy    => src/YourApp/Model/UserSchemaProxy.php
    YourApp\Model\UserCollectionBase => src/YourApp/Model/UserCollectionBase.php
    YourApp\Model\UserCollection     => src/YourApp/Model/UserCollection.php
    YourApp\Model\UserBase           => src/YourApp/Model/UserBase.php
    YourApp\Model\User               => src/YourApp/Model/User.php
Done

创建数据库

如果您使用的是 postgresql 或 mysql,可以使用 create-db 命令创建您的数据库

$ php vendor/bin/lazy db create

从模型模式构建 SQL

现在您需要将 SQL 模式构建到您的数据库中,只需运行 build-sql-d 是调试模式,它将打印所有生成的 SQL 语句

$ php vendor/bin/lazy sql
Finding schema classes...
Initialize schema builder...
Building SQL for YourApp\Model\UserSchema
DROP TABLE IF EXISTS users;
CREATE TABLE users ( 
  account varchar(16),
  password varchar(40)
);

Setting migration timestamp to 1347439779
Done. 1 schema tables were generated into data source 'default'.

编写应用程序代码

现在您可以编写您的应用程序代码了,但首先您需要编写您的lazyrecord配置加载器代码。

$ vim app.php
require 'vendor/autoload.php';
$config = new LazyRecord\ConfigLoader;
$config->load( __DIR__ . '/db/config/database.yml');
$config->init();

init方法将数据源初始化到ConnectionManager,但除非您需要操作模型,否则它不会创建连接。

操作用户模型对象的示例代码

现在将您的应用程序代码附加到app.php文件的末尾

$user = new YourApp\Model\User;
$ret = $user->create(array('account' => 'guest', 'password' => '123123' ));
if ($ret->error ) {
    echo $ret->message;  // get the error message
    if ($ret->exception) {
        echo $ret->exception;  // get the exception object
    }
    echo $ret; // __toString() is supported
}

请查阅doc/目录以获取更多详细信息。

基本用法

模型访问器

LazyRecord的BaseModel类通过使用__get魔术方法提供了一个简单的方法来从结果数据中检索数据,通过使用魔术方法,您可以检索列值和关系对象。

$record = new MyApp\Model\User( 1 );   // select * from users where id = 1;
$record->name;    // get "users.name" and inflate it.

__get方法将委派到get方法,如果您不想使用魔术方法,

$record->get('name');

魔术方法调用值膨胀器,它可以帮助您膨胀像DateTime对象这样的值,如果需要性能,您可以直接做

$record->getValue('name');

BaseModel也支持迭代,因此您可以使用foreach迭代数据值

foreach( $record as $column => $rawValue ) {

}

模型操作

创建模型记录

$author = new Author;
$ret = $author->create(array(
    'name' => 'Foo'
));
if ( $ret->success ) {
    echo 'created';
}

查找记录

$ret = $author->load(123);
$ret = $author->load(array( 'foo' => 'Name' ));
if ( $ret->success ) {

} else {
    // handle $ret->exception or $ret->message
}

使用(静态)查找记录

$record = Author::load(array( 'name' => 'Foo' ));

使用主键查找记录

$record = Author::load( 1 );

更新记录

$author->update(array(  
    'name' => 'Author',
));

使用(静态)更新记录

$ret = Author::update( array( 'name' => 'Author' ) )
    ->where()
        ->equal('id',3)
        ->execute();

if( $ret->success ) {
    echo $ret->message;
}
else {
    // pretty print error message, exception and validation errors for console
    echo $ret;

    $e = $ret->exception; // get exception
    $validations = $ret->validations; // get validation results
}

集合

创建集合对象

$authors = new AuthorCollection;

执行查询(查询语法由SQLBuilder提供支持)

$authors->where()
    ->equal( 'id' , 'foo' )
    ->like( 'content' , '%foo%' );

或者您可以这样做

$authors->where(array( 
    'name' => 'foo'
));

迭代集合

$authors = new AuthorCollection;
foreach( $authors as $author ) {
    echo $author->name;
}

模型模式

定义模式类

简单地从LazyRecord\Schema扩展类,并在schema方法中定义您的模型列,例如,

<?php
namespace TestApp;
use LazyRecord\Schema;

class BookSchema extends Schema
{

    public function schema()
    {
        $this->column('title')
            ->unique()
            ->varchar(128);

        $this->column('subtitle')
            ->varchar(256);

        $this->column('isbn')
            ->varchar(128)
            ->immutable();

        $this->column('description')
            ->text();

        $this->column('view')
            ->default(0)
            ->integer();

        $this->column('publisher_id')
            ->isa('int')
            ->integer();

        $this->column('published_at')
            ->isa('DateTime')
            ->timestamp();

        $this->column('created_by')
            ->integer()
            ->refer('TestApp\UserSchema');


        // Defining trait for model class
        $this->addModelTrait('Uploader');
        $this->addModelTrait('Downloader')
            ->useInsteadOf('Downloader::a', 'Uploader');

        $this->belongsTo('created_by', 'TestApp\UserSchema','id', 'created_by');

        /** 
         * column: author => Author class 
         *
         * $book->publisher->name;
         *
         **/
        $this->belongsTo('publisher','\TestApp\PublisherSchema', 'id', 'publisher_id');

        /**
         * accessor , mapping self.id => BookAuthors.book_id
         *
         * link book => author_books
         */
        $this->many('book_authors', '\TestApp\AuthorBookSchema', 'book_id', 'id');


        /**
         * get BookAuthor.author 
         */
        $this->manyToMany( 'authors', 'book_authors', 'author' )
            ->filter(function($collection) { return $collection; });
    }
}

定义列类型

$this->column('foo')->integer();
$this->column('foo')->float();
$this->column('foo')->varchar(24);
$this->column('foo')->text();
$this->column('foo')->binary();

文本

$this->column('name')->text();

布尔值

$this->column('name') ->boolean();

整数

$this->column('name')->integer();

时间戳

$this->column('name')->timestamp();

日期时间

$this->column('name')->datetime();

定义混入方法

namespace LazyRecord\Schema\Mixin;
use LazyRecord\Schema\MixinDeclareSchema;

class MetadataMixinSchema extends MixinDeclareSchema
{
    public function schema()
    {
        // ... define your schema here
    }

    public function fooMethod($record, $arg1, $arg2, $arg3, $arg4)
    {
        // ...
        return ...;
    }
}

然后您可以在您的模型对象上使用fooMethod

$record = new FooModal;
$result = $record->fooMethod(1,2,3,4);

定义模型关系

属于

belongsTo(accessor_name, foreign_schema_class_name, foreign_schema_column_name, self_column_name = 'id')

$this->belongsTo( 'author' , '\TestApp\AuthorSchema', 'id' , 'author_id' );
$this->belongsTo( 'address' , '\TestApp\AddressSchema', 'address_id' );

有一个

one(accessor_name, self_column_name, foreign_schema_class_name, foreign_schema_column_name)

$this->one( 'author', 'author_id', '\TestApp\AuthorSchema' , 'id' );

有许多

many(accessor_name, foreign_schema_class_name, foreign_schema_column_name, self_column_name )

$this->many( 'addresses', '\TestApp\AddressSchema', 'author_id', 'id');
$this->many( 'author_books', '\TestApp\AuthorBookSchema', 'author_id', 'id');

定义多对多关系

$this->manyToMany( 'books', 'author_books' , 'book' );

用法

// has many
$address = $author->addresses->create(array( 
    'address' => 'farfaraway'
));

$address->delete();

// create related address
$author->addresses[] = array( 'address' => 'Harvard' );

$addresses = $author->addresses->items();
is( 'Harvard' , $addresses[0]->address );

foreach( $author->addresses as $address ) {
    echo $address->address , "\n";
}

当模型准备就绪时进行一些准备工作

如果您想在模式创建到数据库后做些什么,您可以在您的模式类中定义一个bootstrap方法

namespace User;
class UserSchema extends LazyRecord\Schema { 
    public function schema() {
        // ...
    }
    public function bootstrap($model) {
        // do something you want
    }
}

当您运行时,会触发bootstrap方法

lazy sql

使用多个数据源

您可以在模型模式中为不同的模型定义特定的数据源

use LazyRecord\Schema;
class UserSchema extends Schema {
    public function schema() {
        $this->writeTo('master');
        $this->readFrom('slave');
    }
}

或者您可以指定(读取和写入)两者

use LazyRecord\Schema;
class UserSchema extends Schema {
    public function schema() {
        $this->using('master');
    }
}

定义基础数据种子

基础数据种子脚本在您运行build-sql之后执行,这意味着您的所有表都已准备好在数据库中。

定义基础数据种子脚本

namespace User;
class Seed { 
    public static function seed() {

    }
}

然后通过添加数据种子类的类名来更新您的配置文件

seeds:
  - User\Seed
  - System\Seed
  - System\TestingSeed

迁移

如果您需要修改模式代码,如向表中添加新列,您可以使用惊人的迁移功能轻松地将数据库迁移到最新的更改。

一旦您修改了模式代码,您可以通过执行lazy diff命令来比较当前现有的数据库表

$ lazy diff
+ table 'authors'            tests/tests/Author.php
+ table 'addresses'          tests/tests/Address.php
+ table 'author_books'       tests/tests/AuthorBook.php
+ table 'books'              tests/tests/Book.php
+ table 'users'              tests/tests/User.php
+ table 'publishers'         tests/tests/Publisher.php
+ table 'names'              tests/tests/Name.php
+ table 'wines'              tests/tests/Wine.php

如您所见,我们添加了许多新表(模式),LazyRecord解析数据库表以向您显示差异,让您了解当前状态。

目前LazyRecord支持SQLite、PostgreSQL、MySQL表解析。

现在您可以生成迁移脚本或直接升级数据库模式。

要直接升级数据库模式,您可以简单地运行

$ lazy migrate auto

要通过可定制的迁移脚本来升级数据库模式,您可以生成一个新迁移脚本,例如

$ lazy migrate diff AddUserRoleColumn
Loading schema objects...
Creating migration script from diff
Found 10 schemas to compare.
    Found schema 'TestApp\AuthorSchema' to be imported to 'authors'
    Found schema 'TestApp\AddressSchema' to be imported to 'addresses'
    Found schema 'TestApp\AuthorBookSchema' to be imported to 'author_books'
    Found schema 'TestApp\BookSchema' to be imported to 'books'
    Found schema 'TestApp\UserSchema' to be imported to 'users'
    Found schema 'TestApp\PublisherSchema' to be imported to 'publishers'
    Found schema 'TestApp\NameSchema' to be imported to 'names'
    Found schema 'TestApp\Wine' to be imported to 'wines'
Migration script is generated: db/migrations/20120912_AddUserRoleColumn.php

现在您可以编辑自动生成的迁移脚本

vim db/migrations/20120912_AddUserRoleColumn.php

迁移脚本看起来像这样

class AddUserColumn_1347451491  extends \LazyRecord\Migration\Migration {

    public function upgrade() { 
        $this->importSchema(new TestApp\AuthorSchema);
        $this->importSchema(new TestApp\AddressSchema);

        // To upgrade with new schema:
        $this->importSchema(new TestApp\AuthorBookSchema);
        
        // To create index:
        $this->createIndex($table,$indexName,$columnNames);
        
        // To drop index:
        $this->dropIndex($table,$indexName);
        
        // To add a foreign key:
        $this->addForeignKey($table,$columnName,$referenceTable,$referenceColumn = null) 
        
        // To drop table:
        $this->dropTable('authors');
    }

    public function downgrade() { 

        $this->dropTable('authors');
        $this->dropTable('addresses');
        
    }
}

内置的迁移生成器不仅生成升级脚本,还生成降级脚本,您可以按照自己的意愿修改它。

生成迁移脚本后,您可以检查当前数据库的状态和等待迁移的脚本

$ lazy migrate status
Found 1 migration script to be executed.
- AddUserColumn_1347451491

现在您可以通过迁移脚本来运行升级命令以升级数据库模式

$ lazy migrate up

如果您后悔,可以通过命令运行降级迁移

$ lazy migrate down

但请注意,SQLite不支持列重命名和列删除。

要查看迁移脚本可以做什么,请查看SQLBuilder包的文档。

混合模式

...

集合过滤器

内置的集合过滤器提供了一个强大的功能,通过定义过滤类型和后端的有效值,帮助您将后端集合过滤与前端UI连接起来

use LazyRecord\CollectionFilter\CollectionFilter;
$posts = new PostCollection;
$filter = new CollectionFilter($posts);

$filter->defineEqual('status', [ 'published', 'draft' ]); // valid values are 'published', 'draft'
$filter->defineContains('content');
$filter->defineRange('created_on', CollectionFilter::String );
$filter->defineInSet('created_by', CollectionFilter::Integer );

$collection = $filter->apply([ 
    'status'     => 'published',   // get published posts
    'content'    => ['foo', 'bar'],  // posts contains 'foo' and 'bar'
    'created_on' => [ '2011-01-01', '2011-12-30' ], // posts between '2011-01-01' and '2011-12-30'
    'created_by' => [1,2,3,4],  // created by member 1, 2, 3, 4
]);

$collection = $filter->applyFromRequest('_filter_prefix_');

// use '_filter_' as the parameter prefix by default.
$collection = $filter->applyFromRequest();

生成的SQL语句如下

SELECT m.title, m.content, m.status, m.created_on, m.created_by, m.id FROM posts m  WHERE  (status = published OR status = draft) AND (content like %foo% OR content like %bar%) AND (created_on BETWEEN '2011-01-01' AND '2011-12-30') AND created_by IN (1, 2, 3, 4)

基础数据种子

设置SQL语法QueryDriver

$driver = LazyRecord\QueryDriver::getInstance('data_source_id');
$driver->configure('driver','pgsql');
$driver->configure('quote_column',true);
$driver->configure('quote_table',true);

更高级的模型模式

use LazyRecord\Schema;

class AuthorSchema extends Schema
{
    function schema()
    {
        $this->column('id')
            ->integer()
            ->primary()
            ->autoIncrement();

        $this->column('name')
            ->varchar(128)
            ->validator(function($val) { .... })
            ->filter( function($val) {  
                        return preg_replace('#word#','zz',$val);  
            })
            ->inflator(function($val) {
                return unserialize($val);
            })
            ->deflator(function($val) {
                return serialize($val);
            })
            ->validValues( 1,2,3,4,5 )
            ->default(function() { 
                return date('c');
            })
            ;

        $this->column('email')
            ->required()
            ->varchar(128);

        $this->column('confirmed')
            ->default(false)
            ->boolean();

        $this->seeds('User\\Seed')
    }
}

文档

有关详细内容,请参阅doc/目录。

贡献

每个人都可以为LazyRecord做出贡献。您只需fork它,并发送Pull Requests。

您必须遵循PSR编码标准,并提供尽可能多的单元测试。

黑客攻击

设置环境

使用Composer安装依赖项

composer install --prefer-source

要部署测试环境,您需要安装依赖项。

运行脚本并确保一切正常

php bin/lazy

数据库配置在phpunit.xml文件中编写,以下步骤基于默认配置。您也可以查看.travis.yml作为示例。

使用MySQL数据库进行单元测试

要使用mysql数据库进行测试

mysql -uroot -p

输入SQL初始化数据库

create database testing charset utf8;
create user 'testing'@'localhost';
grant all privileges on testing.* to 'testing'@'localhost';

--- if you want password
grant all privileges on testing.* to 'testing'@'localhost' identified by 'testing';

--- if you want to remove password for root user
SET PASSWORD FOR root@localhost=PASSWORD('');

--- for mysql 5.7, you should run
SET PASSWORD FOR root@localhost='';

使用PostgreSQL数据库进行单元测试

要使用pgsql数据库进行测试,您需要准备数据库

sudo -u postgres createdb -E=utf8 testing

如果您想使用单独的用户,可以使用以下命令创建pgsql用户

sudo -u postgres createuser --no-createrole --no-superuser --no-password testing
sudo -u postgres createdb -E=utf8 --owner=testing testing

如果您意外设置了密码,可以通过运行以下命令删除用户密码

> alter role postgres password null;

要使用PDO连接pgsql,您需要通过套接字配置DSN,如下所示

pgsql:host=localhost;dbname=testing

命令行测试

要使用命令行测试SQL构建器,请复制默认测试配置

$ cp db/config/database.testing.yml db/config/database.yml

自定义phpunit.xml配置

$ cp phpunit.xml.dist phpunit.xml

构建配置

$ php bin/lazy build-conf db/config/database.yml

构建模式文件

php bin/lazy schema build

我们已经定义了3个数据源,它们分别命名为'mysql'、'pgsql'、'sqlite',现在您可以将模式SQL插入这些数据源

bin/lazy sql --rebuild -D=mysql
bin/lazy sql --rebuild -D=pgsql
bin/lazy sql --rebuild -D=sqlite

运行PHPUnit

$ phpunit

性能分析

$ phpunit --group profile

操作模式对象

从模式获取模型类名

$class = $schema->getModelClass();

从模式获取表名

$t = $schema->getTable();

要迭代列对象,您可以调用getColumns,它返回关联数组中的列对象

foreach( $schema->getColumns() as $n => $c ) {
    echo $c->name; // column name
}

性能分析

$ scripts/run-xhprof

$ phpunit -c phpunit-xhprof.xml
$ cd xhprof_html
$ php -S localhost:8888

许可协议

BSD许可协议