sanovskiy/simple-object

简单的SQL数据库ORM


README

简介

SimpleObject是一个PHP库,旨在通过提供直观且易于使用的接口来简化数据库记录的交互。

安装

您可以通过Composer安装SimpleObject

composer require sanovskiy/simple-object

数据库连接配置

在您可以使用SimpleObject之前,您需要配置到数据库的连接。这包括指定数据库驱动程序、主机、用户凭据、数据库名称和其他相关设置。SimpleObject支持MySQL、PostgreSQL和MSSQL数据库驱动程序。

添加连接

您可以使用ConnectionManager类和ConnectionConfig对象添加数据库连接。以下是如何添加MySQL数据库连接的示例

use Sanovskiy\SimpleObject\ConnectionManager;
use Sanovskiy\SimpleObject\ConnectionConfig;

ConnectionManager::addConnection(ConnectionConfig::factory([
    'connection' => [
        'driver' => 'mysql',
        'host' => 'localhost',
        'user' => 'sandbox_user',
        'password' => 'letmein',
        'database' => 'sandbox',
        'charset' => 'utf8',
        'port' => '53306'
    ],
    'path_models' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'Models',
    'models_namespace' => 'Project\\Models\\',
    'sub_folders_rules' => [
        'acl_' => [
            'folder' => 'Acl' . DIRECTORY_SEPARATOR,
            'strip' => true
        ],
        'shop_' => [
            'folder' => 'Shop' . DIRECTORY_SEPARATOR,
            'strip' => false
        ]
    ],
    'base_class_extends' => 'MyOwnClassThatExtendsActiveRecordAbstract', 
], 'default'));

配置参数说明

  • driver:指定数据库驱动程序(例如,'mysql'、'pgsql'、'mssql')。
  • host:指定数据库服务器的地址。
  • user:指定连接到数据库的用户名。
  • password:指定连接到数据库的密码。
  • database:指定数据库名称。
  • charset:指定连接的字符集。
  • port:指定端口号(可选)。
  • path_models:指定存储生成的模型的位置。
  • models_namespace:指定生成模型的命名空间。
  • sub_folders_rules:指定根据表前缀组织模型到子文件夹的规则(可选)。
  • base_class_extends:指定生成模型的自定义父类。请注意,此类必须扩展ActiveRecordAbstract。 (可选)
  • read_connection:指定一个单独的只读连接(目前未使用)。
  • write_connection:指定一个单独的只写连接(目前未使用)。

子文件夹规则配置

sub_folders_rules配置选项允许您根据表前缀将生成的模型组织到子文件夹中。这对于在具有大量数据库表的项目中更逻辑和组织地构建模型非常有用。

添加子文件夹规则

您可以使用关联数组指定子文件夹规则,其中键是表前缀,值是包含文件夹名称和指示是否从模型类名称中删除前缀的标志的数组。

示例

'sub_folders_rules' => [
    'acl_' => [
        'folder' => 'Acl' . DIRECTORY_SEPARATOR,
        'strip' => true
    ],
    'shop_' => [
        'folder' => 'Shop' . DIRECTORY_SEPARATOR,
        'strip' => false
    ]
]

在这个例子中

  • 具有前缀'acl_'的表将它们对应的模型存储在'Acl'子文件夹中。
  • 具有前缀'shop_'的表将它们对应的模型存储在'Shop'子文件夹中。
  • 如果strip设置为true,则将从模型类名称中删除前缀。否则,将保留前缀。

使用说明

  • 子文件夹规则是可选的,您可以根据项目的具体需求来定义它们。
  • 此功能对于具有大量表或可以根据前缀逻辑分组表的项目特别有用。
  • 请确保规则中指定的文件夹结构与项目的命名空间约定和组织偏好相匹配。

模型生成

添加连接后,只要数据库结构发生变化,或者在项目的初始设置期间,您需要调用ModelsGenerator类的reverseEngineerModels()方法来根据现有的数据库表生成模型。

忽略自动生成的文件

由于Base文件夹包含自动生成的代码,建议将其添加到项目的.gitignore文件中,以避免不必要地版本控制生成的文件。

/Project/Models/Base/

示例

假设您有名为acl_useracl_roleshop_productshop_order的表。使用上面的示例中的子文件夹规则会导致以下文件夹结构

Project
│
└── Models
    ├── Base
    │   ├── Acl
    │   │   ├── User.php
    │   │   └── Role.php
    │   └── Shop
    │       ├── Product.php
    │       └── Order.php
    └── Logic
        ├── Acl
        │   ├── User.php
        │   └── Role.php
        └── Shop
            ├── Product.php
            └── Order.php

这种组织有助于在项目的模型目录内维持清晰的有序结构。

use SimpleObject\ModelsWriter\ModelsGenerator;

ModelsGenerator::reverseEngineerModels();

此服务方法通常在项目部署期间或数据库结构修改后执行。如果您的代码正在正常运行,并且模型已经生成,则不需要调用此方法。

use Sanovskiy\SimpleObject\ModelsWriter\ModelsGenerator;

ModelsGenerator::reverseEngineerModels();

这将创建与数据库表对应的模型,使它们可以与SimpleObject一起使用。

模型属性命名和表主键

所有模型和模型属性均采用驼峰命名法命名,通过下划线分割表名或列名。例如:字段model_id变为属性ModelId。列SomeLongFieldName变为Somelongfieldname。限制:表主键应命名为id,以确保与SimpleObject完全兼容。

用法

SimpleObject支持基本的CRUD操作(创建、读取、更新、删除),用于与数据库记录交互。

SimpleObject允许您使用面向对象模型与数据库记录交互。以下是使用SimpleObject的基本示例

模型名称及其属性与相应的数据库对象名称精确对应,但转换为驼峰命名法。然而,可以使用相应的数据库字段名称访问属性。尽管这不会导致错误,但这些属性在基类中并未显式定义为@property,因此可能不会被IDE识别。

// Inserting a new record
$user = new User();
$user->Name = 'John Doe';
$user->Email = 'john@example.com';
$user->save();

// Instantly update value in database and in model property
$user->store('Name','Jane Doe');
// You also can use table column name
$user->store('email','jane@example.com');

// Retrieving records
$users = User::find(['status' => 'active']); // result: QueryResult - immutable version of Collection

// or just by PK
$user = User::one(['id'=>1]);
// Alternative method
$user = new User();
$user->Id = 1;
$user->load();

// Updating a record
$user = User::one(['id' => 1]); // result: Your\Models\Namespace\Logic\User
$user->Name = 'Jane Doe';
$user->save(); // result: bool

// Deleting a record
$user = User::one(['id' => 1]);
$user->delete();

// Fetching certain orders
$user = Person::one(['id'=>1]);
$userOrders = $user->getShopOrders(); // QueryResult that contain all user's orders
$orders2024 = $user->getShopOrders(['created_at'=>['>','2024-01-01']]); // all user's orders created after 2024-01-01
$last5orders = $user->getShopOrders([':LIMIT'=>5,':ORDER'=>['created_at','DESC']]); // last 5 orders

过滤器类文档

SimpleObject库中的Filter类负责根据指定条件构建SQL查询。它允许用户有效地过滤数据库记录。以下是关于其功能及其构建查询使用的过滤器数组格式的详细说明。

过滤器数组格式

传递给构造函数的过滤器数组遵循特定的格式来定义过滤记录的条件。数组的每个元素对应一个过滤条件。

$filters = [
    'column_name' => 'value',                     // Simple equality comparison means "where column_name equals 'value'" 
    'column_name' => ['operator', 'value'],       // Comparison with specified operator. I.e. 'column_name'=> ['in',[1,2,3]]
    'column_name' => ['>=' , 18],                 // Example: Greater than or equal comparison
    ':AND' => [                                   // Logical AND group
        'column_name' => 'value',
         ['column_name','operator', 'value']      // Use this method if you need to make several conditions on one field in one level
    ],
    ':OR' => [                                    // Logical OR group
        ['column_name','<','value',]
        ['column_name', '>', 'value']
    ],
    ':ORDER' => ['column_name', 'ASC'],           // Order by clause
    ':LIMIT' => [5],                              // Limit number of records
    ':GROUP' => 'column_name'                     // Group by clause
];

过滤器列表的顶层始终使用'AND'逻辑组合。

  • 键值对
    • 键:表示数据库表中的列名或指令。
      • 对于简单的相等比较:一个标量值。
      • 对于与指定运算符比较:一个包含两个元素(比较运算符和值)的数组。
      • 对于逻辑AND或OR组:包含嵌套过滤条件的数组。
      • 对于排序子句:包含列名和排序方向('ASC'或'DESC')的数组。
      • 对于限制子句:包含限制值(可选地,还包括偏移值)的数组。
      • 对于分组子句:表示列名的字符串。

示例用法

$filters = [
    'status' => 'active',
    ':OR' => [
        ':AND' => [
            'age' => ['>=', 18],
            'country' => 'USA'
        ],
        [
            ':AND' => [
                'age' => ['>=', 21],
                'country' => 'Japan'
            ]
        ]
    ],
    ':ORDER' => ['created_at', 'DESC'],
    ':LIMIT' => [10, 5]
];

$filter = new Filter(User::class, $filters);
$sql = $filter->getSQL();        // Retrieve the constructed SQL query
$bind = $filter->getBind();      // Retrieve the bind values for prepared statements
  • 在这个例子中,过滤器数组定义了检索来自美国且年龄为18岁或以上或来自日本且年龄为21岁或以上的活跃用户的条件。结果按创建日期降序排列,并仅获取前5条记录,从第10条记录开始。
  • Filter类构建SQL查询并检索绑定值,然后可以使用这些值来执行查询。

通常不需要直接实例化Filter类。相反,可以将过滤器数组传递给相关模型类的静态方法::find(array $filterArray)::one(array $filterArray)::getCount(array $filterArray)

SimpleObject库中的自动数据转换

SimpleObject库提供了一种自动数据转换机制,确保在从数据库读取和写入数据时进行验证和转换。以下是关于此功能的详细信息。

目标

自动数据转换的目标是验证并将数据库格式中的数据转换为在PHP中方便工作格式的数据,反之亦然。这包括将日期字符串转换为DateTime类对象,以及其他数据类型,如数字、字符串和JSON。

内置数据类型

SimpleObject库为各种数据类型提供内置转换器

  • BooleanTransformer:将值转换为布尔值。
  • DateTimeTransformer:将日期字符串转换为DateTime类对象。
  • EnumTransformer:将ENUM值转换为相应的PHP值。
  • FloatTransformer:将值转换为浮点数。
  • IntegerTransformer:将值转换为整数。
  • JsonTransformer:将JSON字符串转换为PHP数组或对象。
  • UUIDTransformer:将UUID转换为适当的格式。

用法

您可以通过使用模型类的setDataTransformRule()方法添加自己的自定义转换器并将它们绑定到模型。例如

Project\Models\Logic\CustomTable::setDataTransformRule('custom_type_column_name', [
    'propertyType' => 'DateTime',
    'transformerClass' => MyCustomTransformer::class,
    'transformerParams' => ['param' => 'param_value']
]);

如果字段已经有一个转换器,则新转换器将覆盖它。

转换器方法在从数据库读取或写入时自动调用,确保自动数据转换。

示例

在基模型中使用内置的DateTimeTransformer的示例

protected static array $dataTransformRules = [
    'created_at' => [
        'transformerClass' => DateTimeTransformer::class,
        'transformerParams' => ['format' => 'Y-m-d H:i:s'],
    ],
    'updated_at' => [
        'transformerClass' => DateTimeTransformer::class,
        'transformerParams' => ['format' => 'Y-m-d H:i:s'],
    ],
];

此代码将DateTimeTransformer转换器绑定到created_atupdated_at字段,允许在读取和写入数据库时自动转换日期值。

优点

  • 确保在读取和写入数据库时进行数据验证。
  • 允许将数据转换为在PHP中方便工作的工作格式。
  • 为将自定义转换器绑定到模型字段提供灵活的配置。

限制

  • 转换器必须正确配置以满足应用程序的要求。
  • 可能需要额外的设置来处理数据转换的特殊情况。
  • 尽量在自定义转换器的方法中避免添加过多的逻辑,因为它们在每次从数据库读取或写入数据时都会被调用。

模型之间的自动关系

SimpleObject库中模型的自动关系限于一对一和多对一关系。如果一个表有一个定义的外键,它将在模型生成过程中处理,并创建相应的方法。

一对一关系

在一个一对一关系中,如果一个表有一个指向另一个表的外键,SimpleObject库将自动在引用模型中生成一个方法来检索相关记录。

例如,考虑表personshop_order,其中shop_order有一个外键person_id引用person(id)。在基模型Person中,库自动生成一个方法

public function getShopOrders(?array $filters = []): QueryResult

此方法检索与人员相关联的所有商店订单。您可以选择传递筛选器来缩小结果集。

一对多关系

同样,在一对一关系中,如果一个表有一个指向另一个表的外键,SimpleObject库将在引用模型中生成一个方法来检索相关记录。

继续以表personshop_order的例子,假设shop_order有一个外键person_id引用person(id)。在基模型ShopOrder中,库自动生成一个方法

public function getPerson(): ?Person

此方法检索商店订单关联的人员。

用法

这些自动生成的方法提供了方便访问相关记录的途径,无需手动编码。您可以使用它们来简化数据库查询和简化应用程序逻辑。

注意

这些自动关系基于数据库模式中定义的外键约束。确保您的数据库模式准确反映了SimpleObject库正确生成这些方法的表之间的预期关系。

使用运行时缓存进行缓存

SimpleObject支持使用运行时缓存来缓存模型数据。这意味着数据仅在脚本执行期间存储,不会在不同的应用程序请求之间保留。

缓存实现

ActiveRecordAbstract类中,loadsavedelete方法的示例演示了在执行数据操作时的缓存。以下是具体的工作方式:

方法load:从数据库加载数据时,首先检查记录是否在缓存中存在。如果记录已存在于缓存中,则从缓存中检索,不执行数据库查询。如果没有在缓存中找到记录或需要强制加载($forceLoad = true),则执行数据库查询以加载数据,并将检索到的数据缓存以供后续使用。

方法save:将记录保存到数据库时,数据也存储在缓存中。如果记录已存在于数据库中,其数据将在数据库和缓存中更新。如果记录是第一次插入到数据库中,其数据也将添加到缓存中。

方法delete:从数据库中删除记录时,它也会从缓存中删除。

缓存的使用示例

$user = new User();
$user->Id = 1;

// Loads the user from the database if it exists there. If not, it will load data from the database.
$user->load();

$user1 = new User();
$user1->Id = 1;
$user1->load(); // Here, SimpleObject skips querying the database and gets data from the cache.

// If you want to skip cache check, you can pass `true` to this method.
$user1->load(true); // Force load from the database

重要提示

  • SimpleObject中的运行时缓存提供了高效的缓存管理,以增强您应用程序的性能。
  • 对于频繁执行且可以缓存以供重用的数据操作,建议使用缓存。

贡献

我们欢迎对SimpleObject的贡献!如果您发现任何问题或有改进建议,请随时在GitHub上提交问题或发送拉取请求。

谢谢

特别感谢ChatGPT在常规任务中的帮助。

许可

SimpleObject在自定义条件下根据MIT许可证分发。有关更多信息,请参阅LICENSE文件。

目录结构

Directory structure:
├─ src
│  ├─ Collections                            [Directory containing classes for work with collections]
│  │  ├─ Collection.php                      [Class for working with collections of objects]
│  │  ├─ ImmutableCollection.php             [Class for immutable collections of objects. Extends Collection]
│  │  └─ QueryResult.php                     [Class for query results. Extends ImmutableCollection]
│  ├─ DataTransformers                       [Directory containing classes for data transformation]
│  │  ├─ BooleanTransformer.php              [Class for transforming boolean data]
│  │  ├─ DataTransformerAbstract.php         [Abstract class for data transformers]
│  │  ├─ DateTimeTransformer.php             [Class for transforming datetime data]
│  │  ├─ EnumTransformer.php                 [Class for transforming enum data]
│  │  ├─ FloatTransformer.php                [Class for transforming float data]
│  │  ├─ IntegerTransformer.php              [Class for transforming integer data]
│  │  ├─ JsonTransformer.php                 [Class for transforming JSON data]
│  │  └─ UUIDTransformer.php                 [Class for transforming UUID data]
│  ├─ Interfaces                             [Directory containing interfaces]
│  │  ├─ DataTransformerInterface.php        [Interface for data transformers]
│  │  ├─ ModelWriterInterface.php            [Interface for model writers]
│  │  └─ ParserInterface.php                 [Interface for database structure parsers]
│  ├─ ModelsWriter                           [Directory containing classes for generating models]
│  │  ├─ ModelsGenerator.php                 [Class for generating models]
│  │  ├─ Parsers                             [Directory containing classes for parsing database structures]
│  │  │  ├─ ParserAbstract.php               [Abstract class for database structure parsers]
│  │  │  ├─ ParserMSSQL.php                  [Class for parsing MSSQL database structures]
│  │  │  ├─ ParserMySQL.php                  [Class for parsing MySQL database structures]
│  │  │  ├─ ParserPostgreSQL.php             [Class for parsing PostgreSQL database structures]
│  │  ├─ Schemas                             [Directory containing classes for database schema]
│  │  │  ├─ ColumnSchema.php                 [Class for database table column schema]
│  │  │  └─ TableSchema.php                  [Class for table schema]
│  │  └─ Writers                             [Directory containing classes for writing models]
│  │     ├─ AbstractWriter.php               [Abstract class for model writers]
│  │     ├─ Base.php                         [Class for basic model writing]
│  │     └─ Logic.php                        [Class for logic model writing]
│  ├─ Query                                  [Directory containing classes for query operations]
│  │  ├─ Filter.php                          [Class for filtering queries]
│  │  ├─ FilterTypeDetector.php              [Class for query part type detection]
│  │  └─ QueryExpression.php                 [Class for query expressions]
│  ├─ Traits                                 [Directory containing traits]
│  │  └─ ActiveRecordIteratorTrait.php       [Trait for ActiveRecordAbstract with implementation of Iterator, ArrayAccess and Countable interfaces]
│  ├─ ActiveRecordAbstract.php               [Abstract class for active record pattern]
│  ├─ ConnectionConfig.php                   [Class for connection configurations]
│  ├─ ConnectionManager.php                  [Class for managing connections]
│  ├─ ExtendedCLIMate.php                    [Class for extending CLIMate library]
│  └─ RuntimeCache.php                       [Class for runtime caching]
├─ .gitignore                                [Git ignore file]
├─ README.md                                 [*This file*]
└─ composer.json                             [Composer configuration file]