simple-crud/simple-crud

轻松实现创建/读取/更新/删除


README

Build Status Scrutinizer Code Quality

PHP库,用于在MySQL/Sqlite数据库中(创建、读取、更新、删除)数据,无需配置和少量魔法。

命名约定

此库依赖于一些约定以避免配置。

  • 表名应使用单数驼峰式
  • 字段名应使用单数驼峰式
  • 所有表的主键必须是id
  • 外键必须是[tableName]_id。例如,post表使用post_id作为外键。
  • 关联表必须使用下划线将两个表按字母顺序连接起来。例如,posttag之间的关系是post_tag,而postcategorycategory_post

安装

此包可通过Composer安装和自动加载,名称为simple-crud/simple-crud

$ composer require simple-crud/simple-crud

SimpleCrud有以下类

  • Database: 管理数据库连接。内部使用Atlas.PDO
  • Query: 创建数据库查询。SimpleCrud与MySQL和SQLite进行了测试,但由于内部使用Atlas.Query,理论上还应支持Postgres和Microsoft SQL。
  • Table: 管理数据库表
  • Field: 管理数据库字段。用于格式化和验证值
  • Row: 用于存储和修改一行
  • RowCollection: 是行的集合

使用示例

假设我们有以下数据库模式

CREATE TABLE "post" (
    `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    `title` TEXT,
    `category_id` INTEGER,
    `type`  TEXT,

    FOREIGN KEY(`category_id`) REFERENCES category(id)
);

CREATE TABLE `category` (
    `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    `name`  TEXT
);

CREATE TABLE `tag` (
    `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    `name`  TEXT
);

CREATE TABLE `post_tag` (
    `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    `tag_id`   INTEGER NOT NULL,
    `post_id`  INTEGER NOT NULL,

    FOREIGN KEY(`tag_id`) REFERENCES tag(id),
    FOREIGN KEY(`post_id`) REFERENCES post(id)
);

首先,创建一个SimpleCrud\Database实例,传入PDO连接。

use SimpleCrud\Database;

$pdo = new PDO($dsn, $username, $password);

$db = new Database($pdo);

//To get any table, use magic properties, they will be instantiated on demand:
$post = $db->post;

SimpleCrud加载数据库模式并自动检测表中所有关系,使用上述命名约定。例如,表“post”有一个名为“category_id”的字段,因此SimpleCrud知道每个帖子都有一个类别。

注意:在生产环境中,您可能希望缓存模式以避免执行这些查询并提高性能。您可以这样做

use SimpleCrud\Scheme\Cache;
use SimpleCrud\Scheme\Mysql;

if ($cache->has('db_scheme')) {
    $array = $cache->get('db_scheme');
    $scheme = new Cache($array);
} else {
    $scheme = new Mysql($pdo);
    $cache->save('db_scheme', $scheme->toArray());
}

$db = new Database($pdo, $scheme);

使用库

基本CRUD

您可以直接与表交互以插入/更新/删除/选择数据

使用ArrayAccess接口通过id访问数据

//Get the post id = 3;
$post = $db->post[3];

//Check if a row exists
if (isset($db->post[3])) {
    echo 'exists';
}

//Delete a post
unset($db->post[3]);

//Update a post
$db->post[3] = [
    'title' => 'Hello world'
];

//Insert a new post
$db->post[] = [
    'title' => 'Hello world 2'
];

//Tables implements the Countable interface
$totalPost = count($db->post);

通过其他字段选择

如果您想通过除id之外的其他键选择行,只需使用get方法即可

$post = $db->post->get(['slug' => 'post-slug']);

选择或创建

有时,您可能想要获取一个行或创建它(如果不存在)。您可以使用getOrCreate方法轻松完成此操作

$post = $db->post->getOrCreate(['slug' => 'post-slug']);

Row对象表示数据库行,用于读取和修改其数据

//get a row by id
$post = $db->post[34];

//Get/modify fields values
echo $post->title;

$post->title = 'New title';

//Update the row into database
$post->save();

//Remove the row in the database
$post->delete();

//Create a new row
$newPost = $db->post->create(['title' => 'The title']);

//Insert the row in the database
$newPost->save();

查询

Query对象表示数据库查询。SimpleCrud使用魔法方法创建查询。例如,$db->post->select()返回一个在post表中的新Select查询实例。其他示例:$db->comment->update()$db->category->delete()等。每个查询都有修饰符,如orderBy()limit()

//Create an UPDATE query with the table post
$updateQuery = $db->post->update(['title' => 'New title']);

//Add conditions, limit, etc
$updateQuery
    ->where('id = ', 23)
    ->limit(1);

//get the query as string
echo $updateQuery; //UPDATE `post` ...

//execute the query and returns a PDOStatement with the result
$PDOStatement = $updateQuery();

方法 get() 执行查询并返回查询的处理结果。例如,使用 insert() 返回新行的 id。

//insert a new post
$id = $db->post
    ->insert([
        'title' => 'My first post',
        'text' => 'This is the text of the post'
    ])
    ->get();

//Delete a post
$db->post
    ->delete()
    ->where('id = ', 23)
    ->get();

//Count all posts
$total = $db->post
    ->selectAggregate('COUNT')
    ->get();
//note: this is the same like count($db->post)

//Sum the ids of all posts
$total = $db->post
    ->selectAggregate('SUM', 'id')
    ->get();

select()->get() 返回一个包含结果的 RowCollection 实例。

$posts = $db->post
    ->select()
    ->where('id > ', 10)
    ->orderBy('id ASC')
    ->limit(100)
    ->get();

foreach ($posts as $post) {
    echo $post->title;
}

如果您只需要第一行,请使用修饰符 one()

$post = $db->post
    ->select()
    ->one()
    ->where('id = ', 23)
    ->get();

echo $post->title;

select() 有一些有趣的修饰符,如 relatedWith(),可自动添加所需的 WHERE 子句来选择与其他行或行集合相关的数据。

//Get the post id = 23
$post = $db->post[23];

//Select the category related with this post
$category = $db->category
    ->select()
    ->relatedWith($post)
    ->one()
    ->get();

查询 API

查询使用 Atlas.Query 库构建最终查询,因此您可以查看所有可用选项的文档。

选择 / 选择聚合

更新

插入

删除

懒加载

两者 RowRowCollection 都可以自动加载其他相关行。只需使用名为相关表的属性。例如

//Get the category id=34
$category = $db->category[34];

//Load the posts of this category
$posts = $category->post;

//This is equivalent to:
$posts = $db->post
    ->select()
    ->relatedWith($category)
    ->get();

//But the result is cached so the database query is executed only the first time
$posts = $category->post;

这允许这样做

$titles = $db->post[34]->tag->post->title;

//Get the post id=34
//Get the tags of the post
//Then the posts related with these tags
//And finally, the titles of all these posts

使用魔法方法获取返回相关行的 Select 查询

$category = $db->category[34];

//Magic property: Returns all posts of this category:
$posts = $category->post;

//Magic method: Returns the query instead the result
$posts = $category->post()
    ->where('pubdate > ', date('Y-m-d'))
    ->limit(10)
    ->get();

解决 n+1 问题

可以通过以下方式解决 n+1 问题

//Get some posts
$posts = $db->post
    ->select()
    ->get();

//preload all categories
$posts->category;

//now you can iterate with the posts
foreach ($posts as $post) {
    echo $post->category;
}

您可以自行执行选择以包含修饰符

//Get some posts
$posts = $db->post
    ->select()
    ->get();

//Select the categories but ordered alphabetically descendent
$categories = $posts->category()
    ->orderBy('name DESC')
    ->get();

//Save the result in the cache and link the categories with each post
$posts->link($categories);

//now you can iterate with the posts
foreach ($posts as $post) {
    echo $post->category;
}

对于多对多关系,您需要执行一个额外的步骤

//Get some posts
$posts = $db->post
    ->select()
    ->get();

//Select the post_tag relations
$tagRelations = $posts->post_tag()->get();

//And now the tags of these relations
$tags = $tagRelations->tag()
    ->orderBy('name DESC')
    ->get();

//Link the tags with posts using the relations
$posts->link($tags, $tagRelations);

//now you can iterate with the posts
foreach ($posts as $post) {
    echo $post->tag;
}

关联和解关联数据

要保存相关行到数据库,您需要这样做

//Get a comment
$comment = $db->comment[5];

//Get a post
$post = $db->post[34];

//Relate
$post->relate($comment);

//Unrelate
$post->unrelate($comment);

//Unrelate all comments of the post
$post->unrelateAll($db->comment);

分页

查询 select 有一个特殊的修饰符用于分页结果

$query = $db->post->select()
    ->page(1)
    ->perPage(50);

$posts = $query->get();

//To get the page info:
$pagination = $query->getPageInfo();

echo $pagination['totalRows']; //125
echo $pagination['totalPages']; //3
echo $pagination['currentPage']; //1
echo $pagination['previousPage']; //NULL
echo $pagination['nextPage']; //2

事件

SimpleCrud 使用 PSR-14 Event Dispatcher 来分发事件。事件附加到表上,允许验证数据、修改查询等。

use SimpleCrud\Events\BeforeSaveRow;
use SimpleCrud\Events\CreateSelectQuery;

//Get the event dispatcher
$dispatcher = $db->post->getEventDispatcher();

//Assign the BeforeSaveRow event listener
$dispatcher->listen(BeforeSaveRow::class, function (BeforeSaveRow $event) {
    $row = $event->getRow();

    if (!$row->createdAt) {
        $row->createdAt = new Datetime();
    }
});

//Assign a CreateSelectQuery
$dispatcher->listen(CreateSelectQuery::class, function (CreateSelectQuery $event) {
    $query = $event->getQuery();

    //Add automatically a where clause in all selects
    $query->where('active = true');
});

//Create a new post
$post = $db->post->create(['title' => 'Hello world']);

//Save the post, so BeforeSaveRow event is triggered
$post->save();

$post->createdAt; //This field was filled and saved

//Select a post, so CreateSelectQuery is triggered and only active posts are selected
$posts = $db->post->select()->get();

您可以提供自己的事件分发器

$myDispatcher = new Psr14EventDispatcher();

$db->post->setEventDispatcher($myDispatcher);

可用的事件有

  • SimpleCrud\Events\BeforeSaveRow:在执行 $row->save() 保存行之前执行。
  • SimpleCrud\Events\BeforeCreateRow:在执行 $table->create() 创建新行之前执行。
  • SimpleCrud\Events\CreateDeleteQuery:在执行 $table->delete() 创建 DELETE 查询时执行。
  • SimpleCrud\Events\CreateInsertQuery:在执行 $table->insert() 创建 INSERT 查询时执行。
  • SimpleCrud\Events\CreateSelectQuery:在执行 $table->select() 创建 SELECT 查询时执行。
  • SimpleCrud\Events\CreateUpdateQuery:在执行 $table->update() 创建 UPDATE 查询时执行。

字段

SimpleCrud\Fields 类的目的是将数据从数据库转换为用于其使用的格式。例如,在 MySQL 中,存储 datetime 值的格式是 "Y-m-d H:i:s",因此 SimpleCrud\Fields\Datetime 类将任何字符串或 Datetime 实例转换为该格式,并在选择此值时返回一个 Datetime 实例。可用的字段有

  • 布尔值:用于管理布尔值
  • 日期:用于管理日期值。将数据库值转换为 Datetime
  • 日期时间:用于管理日期时间值。将数据库值转换为 Datetime
  • 十进制:将值转换为浮点数或 NULL
  • 字段:这是默认字段,不转换值
  • 整数:将值转换为整数或 NULL
  • JSON:用于存储 JSON 结构。
  • 可序列化:用于存储数组或任何其他可序列化数据结构。
  • 集合:管理多个值。例如:['red', 'blue', 'green'] 将在数据库中存储为 "red,blue,green"。
  • 点:管理几何点 更多信息
  • 其他高级字段可以在这里找到:https://github.com/oscarotero/simple-crud-extra-fields

字段类会根据数据库中的字段类型自动分配。还有一些具有特定类型分配的“特殊名称”。

  • 任何命名为 id 或以 _id 结尾的字段将被分配整型格式。
  • 任何命名为 pubdate 或以 At 结尾的字段(例如:createdAtupdatedAt 等)将被分配日期时间格式。
  • 任何命名为 active 或以 ishas 开头的字段(例如:isActivedhasContent、等)将被分配布尔格式。

示例

$post = $db->post->create([
    'title' => 'My post',
    'text' => 'My post text',
    'createdAt' => new Datetime('now'),
    'isActive' => true
]);

$post->save();

//Use magic properties to get the Field instance
$titleField = $db->post->title;

配置

您可能想要存储一些数据库配置,例如默认语言或存储资源的基路径。为此,可以使用 getConfigsetConfig 方法。

$db->setConfig('name', 'value');

echo $db->getConfig('name'); //value

可本地化字段

如果您需要在多种语言中保存值,只需为每种语言创建一个带有语言后缀的字段即可。例如,要保存英文(en)和加利西亚语(gl)的标题,只需创建字段 title_entitle_gl

然后,您必须使用 SimpleCrud::ATTR_LOCALE 属性配置当前语言。

//Set the current language as "en"
$db->setConfig(SimpleCrud::CONFIG_LOCALE, 'en');

//Select a post
$post = $db->post[23];

//Get the title in the current language
echo $post->title; //Returns the value of title_en

//You can access to any languages using the full name:
echo $post->title_en;
echo $post->title_gl;

//And assign a diferent value to the current language
$post->title = 'New title in english';

调试

SimpleCrud 在内部使用 Atlas.PDO 来管理连接并在数据库中执行查询。有关更多详细信息,请参阅文档。

$db->getConnection()->logQueries(true);

//-- Run queries --//

$queries = $db->getConnection()->getQueries();

定制

您可以使用自己的自定义类来处理表、行和行集合。

自定义表

使用 setTableClasses 为表分配自定义类。

$db = new SimpleCrud\Database($pdo);

$db->setTableClasses([
    'post' => CustomPost::class,
    'comment' => CustomComment::class,
]);

$db->post; //Returns an instance of CustomPost

FieldFactory

为了创建字段实例,SimpleCrud 使用 SimpleCrud\Field\FieldFactory 工厂类,您可以对其实例化或用您自己的工厂替换。

use SimpleCrud\Fields\FieldFactory;
use SimpleCrud\Fields\Boolean;

$db = new SimpleCrud\Database($pdo);

//Create a factory for your custom field
$factory = new FieldFactory(
    Year::class,          //Your custom field class name
    ['integer'],          //All fields of type integer will use this class
    ['year', '/$year/'],  //All fields named "year" or matching this regex will use this class
    ['min' => 2000],      //Default config
);

$db->setFieldFactory($factory);

//Modify a existing field
$db->getFieldFactory(Boolean::class)->addNames('enabled');

//Use it:
$db->post->fields['year']; //returns an instance of Year
$db->post->fields['enabled']; //returns an instance of SimpleCrud\Fields\Boolean

创建您的 Rows 和 RowCollections

要定义特定表中使用的 Rows 和 RowCollections 类,首先创建一个自定义表,并使用 ROW_CLASSROWCOLLECTION_CLASS 受保护常量来设置类。

namespace MyModels;

use SimpleCrud\Table;

class Post extends Table
{
    protected const ROW_CLASS = PostRow::class;
    protected const ROWCOLLECTION_CLASS = PostRowCollection::class;

    protected function init()
    {
        //Insert code to be executed after the instantion
    }

    public function selectLatest()
    {
        return $this->select()
            ->orderBy('createdAt DESC')
            ->limit(10);
    }
}

现在配置数据库以使用此类处理表 post

$db = new SimpleCrud\Database($pdo);
$db->setTableClasses([
    'post' => MyModels\Post::class,
]);


$latests = $db->post->selectLatest()->get(); //Returns an instance of MyModels\PostRowCollection

foreach ($latests as $post) {
    //Instances of MyModels\PostRow
}