eftec/pdooneorm

单类中的过程式PDO数据访问类

2.2 2024-06-07 14:59 UTC

This package is auto-updated.

Last update: 2024-09-07 15:33:21 UTC


README

PdoOneORM。它是一个简单的PHP PDO库ORM包装器,兼容SQL Server(2008 R2或更高版本)、MySQL(5.7或更高版本)和Oracle(12.1或更高版本)。

这个库试图以尽可能快和简单的方式工作。整个库(包括依赖项)少于100个文件(其中少于30个是代码)

Packagist Total Downloads Maintenance composer php php CocoaPods

将这个

$stmt = $pdo->prepare("SELECT * FROM myTable WHERE name = ?");
$stmt->bindParam(1,$_POST['name'],PDO::PARAM_STR);
$stmt->execute();
$result = $stmt->get_result();
$products=[];
while($row = $result->fetch_assoc()) {
  $product[]=$row; 
}
$stmt->close();

转换为使用ORM。

ProductRepo // this class was generated with echo $pdoOne()->generateCodeClass(['Product']); or using the cli.
    ::where("name = ?",[$_POST['name']])
    ->toList();

目录

示例

在“examples”文件夹中有一些示例。如果您想运行示例,则必须更改数据库的配置。

其他示例在此处

安装

此库需要PHP 7.1及以上版本,并且需要PDO扩展以及PDO-MYSQL(MySQL)、PDO-SQLSRV(SQL Server)或PDO-OCI(Oracle)扩展

安装(使用composer)

编辑 composer.json 下一个需求,然后更新composer。

  {
      "require": {
        "eftec/PdoOneORM": "^1.0"
      }
  }

或者使用cli安装

composer require eftec/PdoOneORM

安装(手动)

只需下载库中的lib文件夹并将其放入您的项目文件夹中。然后,您必须包含其中的所有文件。

如何创建连接?

创建PdoOne类的实例如下。然后,您可以使用connect()或open()方法打开连接

use eftec\PdoOneORM;
// mysql
$dao=new PdoOneORM("mysql","127.0.0.1","root","abc.123","sakila","");
$conn->logLevel=3; // it is for debug purpose and it works to find problems.
$dao->connect();

// sql server 10.0.0.1\instance or (local)\instance or machinename\instance or machine (default instance)
$dao=new PdoOneORM("sqlsrv","(local)\sqlexpress","sa","abc.123","sakila","");
$conn->logLevel=3; // it is for debug purpose and it works to find problems.
$dao->connect();

// test (mockup)
$dao=new PdoOneORM("test","anyy","any","any","any","");
$dao->connect();

// oci (oracle) ez-connect. Remember that you must have installed Oracle Instant client and add it to the path.

$cs='(DESCRIPTION =(ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521))(CONNECT_DATA =(SERVER = DEDICATED)(SERVICE_NAME = instancia1)))';
$dao=new PdoOneORM("oci",$cs,"sa","abc.123"); // oracle uses the user as the schema
$conn->logLevel=3; // it is for debug purpose and it works to find problems.
$dao->connect();

// oci (oracle) tsnnames (the environment variables TNS_ADMIN and PATH must be correctly configured), also tnsnames.ora must exists.
$cs='instancia1';
$dao=new PdoOneORM("oci",$cs,"sa","abc.123"); // oracle uses the user as the schema
$conn->logLevel=3; // it is for debug purpose and it works to find problems.
$dao->connect();

其中

$dao=new PdoOneORM("mysql","127.0.0.1","root","abc.123","sakila","");

  • "mysql"是MySQL数据库。它还允许sqlsrv(用于SQL Server)或"oci"(Oracle)
  • 127.0.0.1是数据库所在的服务器。
  • root是用户
  • abc.123是root用户的密码。
  • sakila是使用的数据库。
  • ""(可选)它可以是日志文件,例如c:\temp\log.txt

OCI

  • Windows安装。将Oracle Instant Client添加到路径中,并尝试从中运行。
    • 如果失败,将oracle bin文件夹(instant client)复制到apache文件夹中。

ORM

此库允许创建和使用它作为ORM。要将其用作ORM,您必须创建类。

什么是ORM?

ORM将查询转换为可序列化的对象。

让我们看看下一个示例

$result=$pdoOne->runRawQuery('select IdCustomer,Name from Customers where IdCustomer=?',1); 

您也可以使用查询构建器运行

$result=$pdoOne->select('IdCustomer,Name')->from('Customers')->where('IdCustomer=?',1)->toList();

如果反复使用相同的表,您可以生成一个新的类,例如 CustomerRepo,并按如下方式调用代码

$result=CustomerRepo::where('IdCustomer=?',1)::toList();

虽然很简单,但它也隐藏了部分实现。这可能会略微影响性能,但它增加了更多简单性和一致性。

构建和安装ORM

生成仓库代码的方法有多种,可以使用CLI、GUI或使用以下代码生成。

$pdo=new PdoOneORM('mysql','127.0.0.1','root','abc.123','sakila');
$pdo->connect();
$table=$pdo->generateCodeClass('Tablename','repo'); // where Tablename is the name of the table to analyze. it must exsits.
echo $clase;

生成的代码看起来像这样。

class TableNameRepo extends _BasePdoOneRepo
{
// ....
}

创建存储库类

不建议使用此方法。该方法用于创建多个类。

创建类的方法有很多,可以使用UI、CLI或直接通过代码创建。

这是一个创建我们仓库类的示例。

$class = $pdoOne->generateCodeClass('Customer'); // The table Customer must exists in the database
file_put_contents('CustomerRepo.php',$clase); // and we write the code into a file.

它将构建我们的仓库类。

<?php
use eftec\PdoOneORM;
use eftec\_BasePdoOneRepo;

class CustomerRepo extends _BasePdoOneRepo
{
    //....
}
$class = $pdoOne->generateCodeClass('Customer','namespace\anothernamespace'); 

它将生成下一个类。

<?php
namespace namespace\anothernamespace;    
use eftec\PdoOneORM;
use eftec\_BasePdoOneRepo;

class CustomerRepo extends _BasePdoOneRepo
{
    //....
}

创建多个存储库类

在这个示例中,我们有两个类:消息和用户。

// converts all datetime columns into a ISO date.
$pdoOne->generateCodeClassConversions(['datetime'=>'datetime2']);

$errors=$pdoOneORM->generateAllClasses(
    ['messages'=>'MessageRepo','users'=>'UserRepo'] // the tables and their name of classes
    ,'BaseClass' // a base class.
    ,'namespace1\repo' // the namespaces that we will use
    ,'/folder/repo' // the folders where we store our classes
    ,false // [optional] if true the we also replace the Repo classes
    ,[] // [optional] Here we could add a custom relation of conversion per column.
    ,[] // [optional] Extra columns. We could add extra columns to our repo.
    ,[] // [optional] Columns to exclude.    
);
var_dump($errors); // it shows any error or an empty array if success.

它将生成下一个类。

📁 folder
   📁repo
       📃AbstractMessageRepo.php   [namespace1\repo\AbstractMessageRepo] NOT EDIT OR REFERENCE THIS FILE
       📃MessageRepo.php         [namespace1\repo\MessageRepo] EDITABLE
       📃AbstractUserRepo.php     [namespace1\repo\AbstractUserRepo] NOT EDIT OR REFERENCE THIS FILE
       📃UserRepo.php               [namespace1\repo\UserRepo]  EDITABLE
       📃BaseClass.php              [namespace1\repo\BaseClass] NOT EDIT OR REFERENCE THIS FILE      
  • 抽象类是包含所有表、索引等定义的类。它们包含类的完整定义。
    • 如果表发生变化,则应该重建此类。如何操作?您必须再次运行方法 generateAllClasses()。
  • 仓库类是作为抽象类的占位符工作的类。这些类是安全的,因此我们可以添加自己的方法和逻辑。
    • 注意:如果您再次运行 generateAllClasses(),则除非我们强制执行(参数 $forced)或删除这些文件,否则这些类将不会被修改。
  • 基类是唯一的一个类(每个模式一个),其中包含所有表的定义及其关系。
    • 如果表发生变化,则应该重建此类。如何操作?您必须再次运行方法 generateAllClasses()。

创建所有存储库类

我们可以进一步自动化。

$allTablesTmp=$pdoOne->objectList('table',true); // we get all the tables from the current schema.
$allTables=[];
foreach($allTablesTmp as $table) {
    $allTables[$table]=ucfirst($table).'Repo';
}
$errors=$pdoOne->generateAllClasses(
   $allTables // tables=>repo class
    ,'MiniChat' // a base class.
    ,'eftec\minichat\repo' // the namespaces that we will use
    ,'/folder/repo' // the folders where we store our classes
);
echo "Errors (empty if it is ok:)";
var_dump($errors);

使用存储库类。

首先,库必须知道连接的位置,因此您必须设置PdoOne的一个实例,有三种方法可以实例化它。

仓库类很智能,它会执行以下操作。

如果仓库基类没有连接,则它会尝试使用最新的可用连接。

最简单的方法是创建PdoOne()的一个实例;

$pdo=new PdoOneORM('mysql','127.0.0.1','root','abc.123','sakila');
$pdo->connect();
$listing=TableNameRepo::toList(); // it will inject automatically into the Repository base class, instance of PdoOneORM.

您也可以通过创建一个名为 pdoOneORM() 的根函数来实现;

function pdoOneORM() {
   $pdo=new PdoOneORM('mysql','127.0.0.1','root','abc.123','sakila');
   $pdo->connect();
}

或者创建一个名为 $pdoOne 的全局变量;

$pdoOne=new PdoOneORM('mysql','127.0.0.1','root','abc.123','sakila');
$pdoOne->connect();

或者使用静态方法 Class::setPdoOne() 将实例注入到类中;

$pdo=new PdoOneORM('mysql','127.0.0.1','root','abc.123','sakila');
$pdo->connect();
TableNameRepo::setPdoOne($pdo); // TableNameRepo is our class generated. You must inject it once per all schema.

使用多个连接

注意:如果您使用多个连接,则必须使用方法 RepoClass::setPdoOne() 并将其注入到仓库基类中。

每个仓库基类一次只能保留一个连接。

示例

  • BaseAlpha(基类)
    • Table1AlphaRepo(仓库类)
    • Table2AlphaRepo(仓库类)
  • BaseBeta(基类)
    • Table1BetaRepo(仓库类)
    • Table2BetaRepo(仓库类)
$con1=new PdoOneORM('mysql','127.0.0.1','root','abc.123','basealpha');
$con1->connect();
$con2=new PdoOneORM('mysql','127.0.0.1','root','abc.123','basebeta');
$con2->connect();
// every base with its own connection:
Table1AlphaRepo::setPdoOne($pdo); // ✅ Table1AlphaRepo and Table2AlphaRepo will use basealpha
Table1BetaRepo::setPdoOne($pdo); // ✅ Table1BetaRepo and Table2BetaRepo will use basebeta
// however, it will not work as expected
// every base with its own connection:
Table1AlphaRepo::setPdoOne($pdo); // ✅ Table1AlphaRepo and Table2AlphaRepo will use basealpha
Table2AlphaRepo::setPdoOne($pdo); // ❌ And now, Table1AlphaRepo and Table2AlphaRepo will use basebeta

如果您想为不同的连接使用相同的基类怎么办?您不能。但是,您可以复制文件并创建两个不同的基类和仓库(或者您可以生成代码来创建新的基类和仓库类),然后您可以使用多个连接。

DDL数据库设计语言

以下命令通常单独执行(不在方法链中)

TablaParentRepo::createTable();
TablaParentRepo::createForeignKeys();
TablaParentRepo::dropTable();
TablaParentRepo::truncate();
// We don't have a method to alter a table.
$ok=TablaParentRepo::validTable(); // it returns true if the table matches with the definition stored into the clas

嵌套操作符

嵌套运算符是应在我们的方法链之间执行的方法。

ClassRepo::op()::where()::finalop() 是 ✅

ClassRepo::op()::op()::where() 会留下链未闭合 ❌

例如

// select * 
//        from table 
//        inner join table2 on t1=t2 
//        where col=:arg
//        and col2=:arg2
//    group by col
//        having col3=:arg3
//    order by col
//    limit 20,30
$results=$pdo->select('*')
    ->from('table')
    ->innerjoin('table2 on t1=t2')
    ->where('col=:arg and col2:=arg2',[20,30]) 
    // it also works with ->where('col=:arg',20)->where('col2'=>30)
    // it also works with ->where('col=?',20)->where('col2=?'=>30)
    ->group('col')
    ->having('col3=:arg3',400)
    ->order('col')
    ->limit('20,30')
    ->toList(); // end of the chain

DQL数据库查询语言

我们在数据库中生成DQL(查询)命令有不同的方法。

如果操作失败,它们返回FALSE,并且可以触发异常。

以下方法应在链的末尾。例如

ClassRepo::op()::op()::toList() 是 ✅

ClassRepo::op()::toList()::op() 将触发异常 ❌

DML数据库模型语言

以下方法允许在数据库中插入、更新或删除值。

// where obj is an associative array or an object, where the keys are the name of the columns (case sensitive)
$identity=TablaParentRepo::insert($obj); 
TablaParentRepo::update($obj);
TablaParentRepo::delete($obj);
TablaParentRepo::deleteById(id);

验证模型

可以验证模型。模型使用数据库的信息进行验证,使用列的类型、长度、值是否允许为空以及是否为标识符(自动编号)。

$obj=['IdUser'=>1,'Name'='John Doe']; 
UserRepo::validateModel($obj,false,['_messages']); // returns true if $obj is a valid User.

递归

递归数组是包含可以读取、获取或比较的值的字符串数组。例如,条件性地连接一个表。PdoOne不直接使用它,但_BasePdoOneRepo使用它(_BasePdoOneRepo是我们在自动生成仓库服务类时使用的类)。

示例

$this->select('*')->from('table')->recursive(['table1','table1.table2']);
// some operations that involves recursive
if($this->hasRecursive('table1')) {
    $this->innerJoin('table1 on table.c=table1.c');
}
if($this->hasRecursive('table1.table2')) {
    $this->innerJoin('table1 on table1.c=table2.c');
}
$r=$this->toList(); // recursive is resetted.

recursive()

它设置一个递归数组。

该值在每次链式方法结束时重置。

getRecursive()

它获取递归数组。

hasRecursive()

如果递归中有某些“针”,则返回 true。

如果 $this->recursive 是 ['*'],则始终返回 true。

$this->select('*')->from('table')->recursive(['*']);
$this->hasRecursive('anything'); // it always returns true.

基准(mysql,估计)

PdoOne 相较于 PDO 增加了一点点开销,但仅仅是 pdo 的包装。

常见错误

未捕获错误:未定义常量 eftec_BasePdoOneRepo::COMPILEDVERSION

这意味着您已更新 PdoOne,并且正在使用 ORM 生成的一个类。这个类必须重新生成。

变更列表

总的来说

每个主要版本都意味着它可能会破坏旧代码。例如,1.0 -> 2.0

每个小版本都意味着它添加了新的功能,例如 1.5 -> 1.6(新方法)

每个小数版本意味着它修补/修复/重构了先前功能,例如 1.5.0 -> 1.5.1(修复)

  • 2.2 2024-06-07
    • 使用 markdown 更新 phpdoc,不要包含 "php",因为 PHPStorm 与其不兼容。
  • 2.1 2024-03-02
    • 更新依赖到 PHP 7.4。PHP 7.2 的扩展支持在 3 年前已结束。
    • 在代码中添加了更多的类型提示。
  • 2.0 2023-12-13
    • 仓库类:常量字段现在是普通字段。
    • 为什么?常量有点慢,但它也缺乏灵活性。
    • 示例:(以前)CustomerRepo::TABLE,(现在)CustomerREPO::$TABLE
    • 仓库类必须重新生成。
  • 1.3.1 2023-11-13
    • 更新一些模板。
    • 当 update() 失败时,它显示表源。
  • 1.3 2023-09-02
    • [PdoOneORMCli] 1.9 修复了未扫描的漏洞。
    • 更新 composer.json 依赖项。
  • 1.2.2 2023-08-11
    • [PdoOneORMCli] 1.8.2 修复了未扫描的漏洞。
    • 将依赖项更新到 eftec/pdoone 4.3。
  • 1.2.1 2023-05-21
    • [PdoOneORMCli] 1.8.1 修复了“按类型配置”中的一个小漏洞。
  • 1.2 2023-04-7
    • [PdoOneORM] 更新以与 PdoOne 4.2 及更高版本兼容。
    • [composer.json] PHPUnit 减少到 8.5(兼容 PHP 7.2)
    • [PdoOneORMCli] 1.8 现在配置文件存储为 PHP 文件,而不是 JSON。为什么?它更灵活。
  • 1.1 2023-03-21
    • [PdoOneORMCli] 1.7 CLI 菜单已更新,使用来自 CliOne 1.26.1 的新菜单功能。
  • 1.0 2023-03-11
    • 第一个版本。这个版本是从 PdoOne 库中分离出来的。

许可

版权所有:Jorge Castro Castillo 2023。双重许可,商业和 LGPL-3.0