vlucas/spot

PHP 5.3+ 的 DataMapper ORM

v2.x-dev 2014-06-18 21:34 UTC

README

适用于 RDBMS(目前有 MySQL 和 Sqlite 适配器)

弃用通知

本项目的开发已停止。请使用 Spot v2 代替 - 它正在积极维护,并且基于更稳固的 Doctrine DBAL(数据库抽象层,不是 Doctrine ORM)基础,并具有更广泛和更强大的数据库支持(MySQL、SQLite、PostgreSQL、SQLServer、Oracle 等)。

在您的项目中使用 Spot

Spot 是一个独立的 ORM,可以用于任何项目。按照以下说明在您的项目中设置 Spot,或使用您正在使用的框架的预编码插件

Silex 提供者@psamatt

连接到数据库

Spot\Config 对象通过名称存储和引用数据库连接。创建一个新的 Spot\Config 实例,并添加带有 DSN 字符串的数据库连接,以便 Spot 可以建立数据库连接。

$cfg = new \Spot\Config();
// MySQL
$adapter = $cfg->addConnection('test_mysql', 'mysql://user:password@localhost/database_name');
// Sqlite
$adapter = $cfg->addConnection('test_sqlite', 'sqlite://path/to/database.sqlite');

如果您使用 Sqlite,则 Sqlite 文件名必须是数据库的名称后跟扩展名,例如 blogs.sqlite

访问 Mapper

由于 Spot 遵循 DataMapper 设计模式,您将需要一个 mapper 实例来处理对象实体和数据库表。

$mapper = new \Spot\Mapper($cfg);

由于您必须在任何使用数据库的地方访问您的 mapper,大多数人会创建一个辅助方法来创建 mapper 实例一次,然后在需要时再次返回相同的实例。这样的辅助方法可能看起来像这样

function get_mapper() {
    static $mapper;
    if($mapper === null) {
        $mapper = new \Spot\Mapper($cfg);
    }
    return $mapper;
}

创建实体

实体类可以根据您在项目结构中的设置进行命名和命名空间。以下示例中,实体将仅使用 Entity 命名空间进行前缀,以方便 psr-0 兼容的自动加载。

namespace Entity;

class Post extends \Spot\Entity
{
    protected static $_datasource = 'posts';

    public static function fields()
    {
        return array(
            'id' => array('type' => 'int', 'primary' => true, 'serial' => true),
            'title' => array('type' => 'string', 'required' => true),
            'body' => array('type' => 'text', 'required' => true),
            'status' => array('type' => 'int', 'default' => 0, 'index' => true),
            'date_created' => array('type' => 'datetime')
        );
    }

    public static function relations()
    {
        return array(
            // Each post entity 'hasMany' comment entites
            'comments' => array(
                'type' => 'HasMany',
                'entity' => 'Entity_Post_Comment',
                'where' => array('post_id' => ':entity.id'),
                'order' => array('date_created' => 'ASC')
            )
        );
    }
}

内置字段类型

所有基本字段类型都内置了所有默认功能,为您提供了便利

  • string
  • int
  • float/double/decimal
  • boolean
  • text
  • date
  • datetime
  • timestamp
  • year
  • month
  • day

注册自定义字段类型

如果您想在 get/set 上注册自己的自定义字段类型并添加自定义功能,请查看 Spot\Type 命名空间中的类,创建自己的类,并在 Spot\Config 中注册它

$config->typeHandler('string', '\Spot\Type\String');

迁移/创建和更新表

Spot 提供了一个在实体上运行迁移的方法,它将根据当前实体的 fields 定义自动创建和更改表。

$mapper->migrate('Post');

您的数据库现在应该有一个 posts 表,其中包含您在 Post 实体中描述的所有字段。

查找器(Mapper)

最常用的主要查找器是 all,用于返回实体集合,以及 firstget,用于返回匹配条件的单个实体。

all(entityName, [conditions])

查找匹配给定条件的所有 entityName,并返回一个包含已加载 Spot\Entity 对象的 Spot\Entity\Collection

// Conditions can be the second argument
$posts = $mapper->all('Entity\Post', array('status' => 1));

// Or chained using the returned `Spot\Query` object - results identical to above
$posts = $mapper->all('Entity\Post')->where(array('status' => 1));

由于返回了一个 Spot\Query 对象,因此可以根据您希望的任何方式或顺序链式添加条件和其他语句。查询将在迭代或 count 时懒加载执行,或通过调用 execute() 来手动结束链式调用。

first(entityName, [conditions])

查找并返回符合条件的一个Spot\Entity对象。

$post = $mapper->first('Entity\Post', array('title' => "Test Post"));

或者可以使用first在先前的查询中使用all来获取仅有的第一个匹配记录。

$post = $mapper->all('Entity\Post', array('title' => "Test Post"))->first();

条件查询

# All posts with a 'published' status, descending by date_created
$posts = $mapper->all('Entity\Post')
    ->where(array('status' => 'published'))
    ->order(array('date_created' => 'DESC'));

# All posts created before today
$posts = $mapper->all('Entity\Post')
    ->where(array('date_created <' => new \DateTime()));

# Posts with 'id' of 1, 2, 5, 12, or 15 - Array value = automatic "IN" clause
$posts = $mapper->all('Entity\Post')
    ->where(array('id' => array(1, 2, 5, 12, 15)));

搜索

Spot支持使用LIKE、REGEX和全文搜索(如果适配器支持)进行搜索查询。

Query#search(text)

Spot\Query上的search方法将使用LIKE搜索,如果启用则使用全文搜索。关于使用全文搜索的详细信息,请参阅搜索的维基页面

$q = "walrus"; // Text to search for
$results = $mapper->all('Entity\Post')
    ->search('body', $q)
    ->order(array('date_created' => 'DESC'));

LIKE搜索

您可以使用:like查询运算符来执行LIKE搜索。

$results = $mapper->all('Entity\Post')
    ->where('body :like', 'walrus%')
    ->order(array('date_created' => 'DESC'));

REGEX搜索

您可以使用:regex~=查询运算符来执行REGEX搜索。

$results = $mapper->all('Entity\Post')
    ->where('body ~=', 'walrus.+')
    ->order(array('date_created' => 'DESC'));

注意:大多数REGEX搜索非常慢,因此请谨慎使用。

关系

关系是从另一个已加载实体对象访问相关、父和子实体的一种便捷方式。一个例子可能是使用$post->comments来查询与当前$post对象相关的所有评论。

关系类型

实体关系类型包括:

  • HasOne
  • HasMany
  • HasManyThrough

HasOne

HasOne是最简单的关系 - 例子可能是Post有一个Author

class Entity\Post extends \Spot\Entity
{
    protected static $_datasource = 'posts';

    public static function fields()
    {
        return array(
            'id' => array('type' => 'int', 'primary' => true, 'serial' => true),
            'author_id' => array('type' => 'int', 'required' => true),
            'title' => array('type' => 'string', 'required' => true),
            'body' => array('type' => 'text', 'required' => true),
            'status' => array('type' => 'int', 'default' => 0, 'index' => true),
            'date_created' => array('type' => 'datetime')
        );
    }

    public static function relations()
    {
        return array(
            // Each post 'hasOne' author
            'author' => array(
                'type' => 'HasOne',
                'entity' => 'Entity\Author',
                'where' => array('id' => ':entity.author_id')
            )
        );
    }
}

HasMany

HasMany用于单条记录与多条其他记录相关联的情况 - 例子可能是Post有许多Comments

我们首先向我们的Post对象添加一个comments关系

class Entity\Post extends \Spot\Entity
{
    protected static $_datasource = 'posts';

    public static function fields()
    {
        return array(
            'id' => array('type' => 'int', 'primary' => true, 'serial' => true),
            'author_id' => array('type' => 'int', 'required' => true),
            'title' => array('type' => 'string', 'required' => true),
            'body' => array('type' => 'text', 'required' => true),
            'status' => array('type' => 'int', 'default' => 0, 'index' => true),
            'date_created' => array('type' => 'datetime')
        );
    }

    public static function relations()
    {
        return array(
            // Each post 'hasMany' comments
            'comments' => array(
                'type' => 'HasMany',
                'entity' => 'Entity\Post\Comment',
                'where' => array('post_id' => ':entity.id'),
                'order' => array('date_created' => 'ASC')
            )
        );
    }
}

然后添加一个具有“hasOne”关系的Entity\Post\Comment对象,该关系指向帖子

class Entity\Post\Comment extends \Spot\Entity
{
    // ... snip ...

    public static function relations() {
      return array(
          // Comment 'hasOne' post (belongs to a post)
          'post' => array(
              'type' => 'HasOne',
              'entity' => 'Entity\Post',
              'where' => array('id' => ':entity.post_id')
          )
      );
    }
}

HasManyThrough

HasManyThrough用于多对多关系。一个好的例子是标签。帖子可以有多个标签,标签也可以有多个帖子。这个关系比其他关系更复杂,因为HasManyThrough需要一个连接表和模型。

我们需要向我们的Post实体添加一个tags关系,指定关系的两边的查询条件。

class Entity\Post extends \Spot\Entity
{
    // ... snip ...

    public static function relations()
    {
        return array(
            // Each post 'hasMany' tags `Through` a post_tags table
            'tags' => array(
                'type' => 'HasManyThrough',
                'entity' => 'Entity_Tag',
                'throughEntity' => 'Entity_PostTag',
                'throughWhere' => array('post_id' => ':entity.id'),
                'where' => array('id' => ':throughEntity.tag_id'),
            )
        );
    }
}

说明

我们想要的结果是ID等于post_tags.tag_id列的Entity\Tag对象集合。我们通过使用当前加载的帖子ID与post_tags.post_id匹配,通过Entity\Post\Tags实体来获取这个结果。

有关另一个场景和更详细的说明,请参阅HasManyThrough维基页面