db 是 SQRT 框架的一部分

0.1 2016-03-19 12:32 UTC

This package is not auto-updated.

Last update: 2024-09-28 16:31:04 UTC


README

Build Status Coverage Status Latest Stable Version License

数据库操作可以通过两种方式实现 - 直接在数据库中执行查询,或与对象交互。

术语和使用的类

  • Manager - 数据库管理器。存储数据库连接、仓库,并提供执行数据库直接查询的能力。
  • Schema - 数据库结构的描述,根据该结构生成迁移、Repository 和 Item 对象。
  • Repository - 代表数据库中的表的对象,允许进行 Item 对象的选择。
  • Collection - Item 对象的存储库,实现访问数组的接口,并提供操作集合的方法。
  • Item - 代表数据库中的记录的对象,包含与该记录相关的业务逻辑。

数据库连接

连接设置存储在 Manager 对象中,并通过方法设置

$manager->addConnection($host, $user, $pass, $db_name, $db_charset = 'utf8', $connection_name = null);

如果不指定连接名,它将被分配默认名称,默认使用。如果需要,可以添加多个连接,并指定连接名来访问它们。

$manager->getConnection($name = null); // Возвращает объект PDO

为了方便在受限制的虚拟主机环境中部署项目,提供了为组件使用的所有表设置前缀的选项

$manager->setPrefix($prefix);

这样,所有创建的表及其使用的对象都将使用该前缀。

注意!在用户生成的查询中,需要手动插入前缀!

直接与数据库交互

可以从 Manager 对象中获取 PDO 对象并直接执行查询,或者使用获取数据的方法。

建议通过 PDO 占位符(例如 WHERE id = :id)将所有值插入到查询中,并将值通过 $values 参数传递。

可以将字符串或由 QueryBuilder 创建的 Query 对象传递给查询。在这种情况下,数据将从 Query 对象中自动提取并插入到查询中。

// Выполнить запрос в БД, возвращает \PDOStatement
$manager->query($sql, $values = null, $connection = null)

// Получить все записи в виде списка ассоциативных массивов. Если указать $key, значения этого столбца будет ключами списка.
$manager->fetchAll($sql, $key = null, $values = null, $connection = null)

// Получить одну строку в виде ассоциативного массива
$manager->fetchOne($sql, $values = null, $connection = null)

// Получить одно значение из первой строки ответа. $col - имя столбца, или будет возвращено значение первого столбца в ответе.
$manager->fetchValue($sql, $col = null, $values = null, $connection = null)

// Выбрать один столбец и возвратить список значений этого столбца
$manager->fetchColumn($sql, $col = null, $values = null, $connection = null)

// Получить массив вида ключ => значение из запроса
$manager->fetchPair($sql, $values = null, $connection = null)

// Применить $callable ко всем результатам выборки по очереди.
// Первым аргументом будет передан массив содержащий текущую строку $callable($row)
$manager->each($sql, $callable, $values = null, $connection = null)

为了调试,可以启用所有执行查询的日志记录

$manager->setDebug($debug = true); // Включить отладку

$manager->getQueries(); // Получить список всех выполненных запросов, {query:..., values:..., time:...}
$manager->getQueriesCount(); // Количество запросов к БД
$manager->getQueriesTime(); // Суммарное время выполнения запросов к БД

事务

Manager 对象支持以下方法以方便地处理事务

$manager->beginTransaction($connection = null); // Начать транзакцию на соединении $connection
$manager->commit($connection = null); // Применить транзакцию на соединении $connection
$manager->rollback($connection = null); // Откатить транзакцию на соединении $connection
$manager->inTransaction($connection = null); // Проверка, активна ли транзакция на соединении $connection

在事务中执行代码

$manager->transaction(\Closure $closure, $connection = null); // Выполнить $closure внутри транзакции на соединении $connection

对象 $closure 将以单个参数 - 对象 Manager 被调用。

在执行之前,事务将被打开,执行后完成(commit),并且方法将返回 $closure 执行的结果。

如果 $closure 抛出异常,则事务将被回滚(rollback)并且异常将被进一步抛出。

模式

模式包含数据库字段及其类型的逻辑表示。根据模式生成模型文件,还可以与数据库的当前状态进行比较,自动创建迁移文件。

通过继承基本模式类并重写 init()relations() 方法来设置模式。

可能的字段类型

$schema->addInt($col, $default = 0, $signed = true, $length = 10)
$schema->addBool($col)
$schema->addChar($col, $length = 255)
$schema->addFloat($col, $length = 10, $decimals = 2, $signed = false)
$schema->addText($col, $size = false)
$schema->addTime($col, $unix = true)
$schema->addTimeCreated($col = 'created_at')
$schema->addTimeUpdated($col = 'updated_at')

注意!表格只能包含 addTimeCreatedaddTimeUpdated 中的一个字段,因为 MySQL 不允许创建多个具有 CURRENT_TIME 的字段。其余日期应使用常规的 addTime 方法设置。

除了“常规”字段外,还可以包含包含生成模型时额外逻辑的字段。

// Первичный ключ таблицы
$schema->addId($col = 'id')

// Поле INT и набор методов для работы с битовой маской
$schema->addBitmask($col, array $options, $default = 0) 

// Поле ENUM, содержащее выбор из нескольких вариантов
$schema->addEnum($col, array $options, $default = null) 

// Поле TEXT, содержащее сериализованный массив данных о файле
$schema->addFile($column)

// Поле TEXT, содержащее сериализованный массив данных о изображении
$schema->addImage($column, array $size_arr = null)

ORM 基础类的生成

根据模式生成基本模型类,在其中为所有字段创建获取器和设置器,并考虑其类型,同时创建相关字段、方法和常量。

日期

  • 设置器支持以任何支持 strtotime() 函数的格式指定日期。

    $item->setCreatedAt('2015-01-01 12:45');
    $item->setCreatedAt('-7 days');
  • 获取器支持由 date() 函数接受的格式。

    $item->getCreatedAt(false, 'd.m.Y H:i');
    

Float

  • 获取器支持使用 number_format() 函数的格式。

    $item->getPrice(); // 12345.67
    $item->getPrice(false, 1, ',', ' '); // 12 345,7

ENUM

ENUM 字段包含字段的有效选项列表。例如

$schema->addEnum('status', array('new', 'progress', 'done'));
  • 对于所有选项,将生成形如 [column]_[value] 的常量,例如 STATUS_NEW, STATUS_DONE。

  • 将生成一个常量名称数组,可以在子类中重写并设置易理解的名称。

    protected static $status_arr = array(
        self::STATUS_NEW => 'new',
        self::STATUS_PROGRESS => 'progress',
        self::STATUS_DONE => 'done',
      );
  • 如果尝试传递数组中不存在的值给设置器,将会抛出异常。

  • 将生成额外的方法。

    • 形如 get[column]Name() 的获取器,它将返回数组中包含的值的名称。
    • 静态方法 Get[column]Arr(),返回名称数组。
    • 静态方法 GetNameFor[column]($status),返回指定值的名称。

位掩码 - 位运算符

位掩码允许指定列表中的多个值并保存在一个字段中。值将以2的幂的形式表示数字。与ENUM的主要区别在于可以同时保存多个值。

$schema->addBitmask('status', array('payed', 'delivered', 'happy'))
  • 对于所有选项,将生成形如 [column]_[value] 的常量,常量的值将分配为键数组的2的幂(0, 1, 2, ...)。

    const STATUS_PAYED = 1;
    const STATUS_DELIVERED = 2;
    const STATUS_HAPPY = 4;
  • 将生成一个常量名称数组,可以在子类中重写并设置易理解的名称。

    protected static $status_arr = array(
        self::STATUS_PAYED => 'payed',
        self::STATUS_DELIVERED => 'delivered',
        self::STATUS_HAPPY => 'happy',
    );
  • 设置器有两种类型。

    $item->addStatus(Item::STATUS_PAYED);
    $item->setStatus(array(Item::STATUS_PAYED, Item::STATUS_HAPPY));
  • 获取器返回数组。

    $item->getStatus(); // [1, 4]
  • 检查是否设置了相应的标志。

    $item->hasStatus(Item::STATUS_PAYED);

重要!不应过度使用位掩码来存储频繁变化的数据。对于这种情况,应使用多对多关系和外部参考表。

文件

保存文件信息的典型任务需要生成多个字段,并手动处理文件的上传。通常,这不需要在数据库内部搜索或处理这些数据,因此可以采取反规范化并简化过程。

$schema->addFile('pdf')

将在表中添加一个TEXT字段,用于存储序列化的文件属性数组。

  • 设置器记录文件信息,并执行通过使用 setFilesPath() 在类初始化时指定的目录复制文件。
  • 生成获取器,用于所有文件属性,形如 get[column][property],例如对于 column = "pdf"
    • getPdf($default = false) - 文件的相对路径。服务器上文件夹的路径通过初始化时通过 setPublicPath() 设置。
    • getPdfPath($default = false) - 服务器上文件的路径
    • getPdfUrl($default = false) - URL对象,指向文件的相对路径
    • getPdfSize($human = true) - 文件大小。如果 $human == true,则应用格式化。
    • getPdfName($default = false) - 文件名称
    • getPdfExtension($default = false) - 文件扩展名

图像

加载图像的典型任务需要保存图像,有时需要调整大小和/或添加水印,并保存其多个版本。

$schema->addImage('image', array('thumb', 'medium', 'orig');

将在表中添加一个TEXT字段,用于存储序列化的文件属性数组。

  • 设置器记录文件信息,并执行通过使用 setFilesPath() 在类初始化时指定的目录复制文件。
  • 保存照片时,对每个尺寸调用私有方法 prepareImageFor[column]($file, $size),其中 size 是方案中指定的每个尺寸。如果该方法返回 SQRT\Image 对象,则从该对象保存图像,否则保存原始图像。
  • 生成获取器,用于所有文件属性,类似于File类型,但添加了图像尺寸,形如 get[column][size][property]
  • 此外,创建了特定于图像的方法。例如,对于 column = "image", size = "thumb"
    • getImageThumbWidth($default = false) - 图像宽度
    • getImageThumbHeight($default = false) - 图像高度
    • getImageThumbImg($alt = null, $attr = null, $default = false) - 生成带有替换文件路径、宽度和高度的 Img 标签

索引

方案包括创建一个或多个列的索引。

$schema->addIndex($column, $_ = null)
$schema->addUniqueIndex($column, $_ = null)

表之间的关系

在InnoDB表中创建外键可以使用以下方法

$schema->addForeignKey($col, $schema, $foreign_id = null, $on_delete = null, $on_update = null)

或者创建一个关系,将生成在模型对象中的额外函数,实现此类关系的基本逻辑。

对于每个实体都可以指定不同的关系类型,例如,对于一本书(Book),可能有一个作者(Author),但作者可能有很多书。也就是说,关系 Book -> Author 是一对一的,但 Author -> Book 是一对多的。因此,每个关系及其类型都在其自己的模式中指定。

重要!所有创建关系的表必须是 InnoDB。

一对一

addOneToOne($schema, $col = null, $foreign_id = null, $on_delete = null, $on_update = null, $name = null, $one = null)

这是最简单的关系类型,当外部对象的 ID 明确指在当前对象的某个字段中。关系会在当前表中添加一个名为 $col 的 INT UNSIGNED DEFAULT NULL 字段。

例如:Book 对象包含一个 author_id 字段,它对应一个 Author 对象。

$schema->addOneToOne('Authors', 'author_id', 'id', Schema::FK_RESTRICT, Schema::FK_RESTRICT)

如果遵守列和表的命名规则,则可以为大多数列保留默认值。

$schema->addOneToOne('Authors')

当存在多个关系或需要与模式不同的实体名称时,$name$one 参数允许指定关系的任意名称。

$schema->addOneToOne('Authors', 'author_id', 'id', Schema::FK_RESTRICT, Schema::FK_RESTRICT, 'MyAuthors', 'MyAuthor')

Book 对象将包含方法

/** @return \Author */
public function getMyAuthor($reload = false)

/** @return static */
public function setMyAuthor(\Author $my_author)

/** @return \Author */
protected function findOneMyAuthor($id)

一对多

addOneToMany($schema, $foreign_id = null, $col = null, $name = null, $one = null)

关系允许选择与当前对象有关系的多个对象。在当前数据库表中不进行任何更改。

例如:Author 对象与多个 Book 有关系,这些 Book 在 author_id 字段中指出了这种依赖关系。

$schema->addOneToMany('Books', 'book_id', 'id')

当存在多个关系或需要与模式不同的实体名称时,$name$one 参数允许指定关系的任意名称。

$schema->addOneToMany('Books', null, null, 'MyBooks', 'MyBook')

Author 对象将包含方法

/** @return Collection|\Book[] */
public function getMyBooks($reload = false)

/** @return static */
public function setMyBooks($my_books_arr = null)

/** @return Collection|\Book[] */
protected function findMyBooks()

多对多

addManyToMany($schema, $join_table = null, $foreign_col = null, $my_col = null, $foreign_id = null, $my_id = null, $name = null, $one = null)

通过第三个表连接两个表的关系。

例如:一本书可能有多个作者(Author),一个作者可能有几本书(Book)。为此,我们创建一个包含 book_idauthor_id 字段的 AuthorBook 表,并通过 JOIN 到这个表来获取作者和书的关系。

// Схема AuthorBook
$schema->addOneToOne('Authors') // author_id
$schema->addOneToOne('Books') // book_id

// В схеме Authors:
addManyToMany('Books', 'author_book', 'book_id', 'author_id', 'id', 'id')

// В схеме Books:
addManyToMany('Authors', 'author_book', 'author_id', 'book_id', 'id', 'id')

Author 对象将包含方法

/** @return Collection|\Book[] */
public function getBooks($reload = false)

/** @return static */
public function addBook($book)

/** @return static */
public function removeBook($book)

/** @return static */
public function removeAllBooks()

/** @param $book integer|\Book */ 
protected function getBookPK($book)

/** @return Collection|\Book[] */
protected function findBooks()

迁移

基于模式和数据库的当前状态,可以生成用于 迁移管理器 Phinx 的迁移文件。

如果数据库中尚不存在表,则生成创建该表及其所有列和索引的迁移。

创建新表 Books 的示例

class NewBooksTable extends AbstractMigration
{
  public function up()
  {
    $tbl = $this->table('test_books', array('id' => 'id'));
    $tbl->addColumn("name", "string", array ( 'length' => 255, 'null' => true,));
    $tbl->addColumn("author_id", "integer", array ( 'length' => 11, 'signed' => true, 'null' => true,));
    $tbl->addForeignKey("author_id", "test_authors", "id", array (  'delete' => 'RESTRICT',  'update' => 'CASCADE',));
    $tbl->save();
  }

  public function down()
  {
    $tbl = $this->table('test_books', array('id' => 'id'));
    $tbl->drop();
  }
}

如果数据库中已存在表,则迁移中会生成添加和删除列的命令,这些列在模式和数据库中不同。对于先前创建并存在于数据库中的列,将生成 changeColumn 方法。

更改现有表 Pages 的示例

class MyMigration extends AbstractMigration
{
  public function up()
  {
    $tbl = $this->table('test_pages', array('id' => 'id'));
    $tbl->addColumn("is_active", "boolean", array ( 'default' => 0,));
    $tbl->addColumn("price", "float", array ( 'precision' => 10, 'scale' => 2, 'signed' => false, 'default' => 0,));
    $tbl->removeColumn("name");
    $tbl->changeColumn("created_at", "timestamp", array ( 'default' => 'CURRENT_TIMESTAMP',));
    if (!$tbl->hasForeignKey("parent_id")) {
      $tbl->addForeignKey("parent_id", "test_pages", "id", array ());
    }
    $tbl->save();
  }

  public function down()
  {
    $tbl = $this->table('test_pages', array('id' => 'id'));
    $tbl->removeColumn("is_active");
    $tbl->removeColumn("price");
    // TODO: добавить инструкции для создания столбца name
    $tbl->save();
  }
}

重要!生成的迁移应被视为草稿,需要检查和根据需要进行调整。不要盲目应用迁移!