eftec / pdooneorm
单类中的过程式PDO数据访问类
Requires
- php: ^7.4 || ^8.0
- ext-json: *
- ext-pdo: *
- eftec/clione: ^1.32.1
- eftec/pdoone: ^4.7
Requires (Dev)
- phpunit/phpunit: ^8.5.13
Suggests
- eftec/validationone: For keeping and storing the messages
README
PdoOneORM。它是一个简单的ORM包装器,适用于PHP的PDO库,与SQL Server(2008 R2或更高版本)、MySQL(5.7或更高版本)和Oracle(12.1或更高版本)兼容。
这个库试图以尽可能快和尽可能简单的方式工作。整个库(包括依赖项)少于100个文件(其中少于30个是代码)
使用ORM将其
$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();
转换为这样
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.
它将构建我们的Repository类。
<?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 { //.... }
创建多个仓库类
在这个例子中,我们有两个类,messages和users
// 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()。
- Repo类是作为抽象类的占位符的类。这些类是安全的,因此我们可以添加自己的方法和逻辑。
- 注意:如果您再次运行 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);
验证模型
可以验证模型。模型使用数据库的信息、列的类型、长度、值是否允许为NULL以及是否为标识(自动数字)进行验证。
$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 的包装器。
错误FAQ
未捕获错误:未定义常量 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 修复了一个未扫描的 bug。
- 更新 composer.json 依赖项。
- 1.2.2 2023-08-11
- [PdoOneORMCli] 1.8.2 修复了一个未扫描的 bug。
- 将依赖项更新到 eftec/pdoone 4.3。
- 1.2.1 2023-05-21
- [PdoOneORMCli] 1.8.1 修复了 "按类型配置" 中的一个小 bug。
- 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