open-tiny-orm
This article was peer reviewed by Paul M. Jones and Scott Molinari. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!
本文由Paul M. Jones和Scott Molinari进行同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!
“AtlasORM is a data mapper implementation for your persistence model (not your domain model)”
“ AtlasORM是针对您的持久性模型(而不是您的域模型)的数据映射器实现”
By definition, a Data Mapper moves data between objects and a database and isolates them from one another. With a Data Mapper, the in memory objects don’t even need to know that a database exists. It does not need to know the SQL interface or database schema; it doesn’t even need the domain layer to know it exists!
根据定义,数据映射器在对象和数据库之间移动数据,并将它们彼此隔离。 使用数据映射器,内存对象甚至不需要知道数据库的存在。 它不需要知道SQL接口或数据库模式; 它甚至不需要域层就知道它的存在!
This might lead us to thinking that, in Atlas, the persistence layer is totally disconnected from the database, but that is not quite what happens. Atlas uses the term Record
to indicate that its objects are not domain entities. An Atlas Record is passive; not an active record. Unlike most ORMs, its objects represent the persistence model, not the domain model. Think of it as representing how the data is stored and not as real world representations.
这可能使我们想到,在Atlas中 ,持久层与数据库完全断开连接,但是事实并非如此。 Atlas使用术语“ Record
来表示其对象不是域实体。 Atlas记录是被动的; 不是活动记录。 与大多数ORM不同,其对象代表持久性模型,而不是域模型。 可以将其视为代表如何存储数据,而不是作为现实世界的代表。
What’s the reasoning behind it?
它背后的原因是什么?
The creator of Atlas, Paul Jones, based it on this article from Mehdi Khalili. The idea was to create an alternative to the Active Record pattern (an object that carries both data and behavior, and is intrinsically connected to the database).
Atlas的创建者Paul Jones基于Mehdi Khalili的这篇文章 。 这个想法是创建Active Record模式(一种既包含数据又包含行为,并固有地连接到数据库的对象)的替代方法。
Citing the author:
引用作者:
“I wanted an alternative to Active Record that would allow you to get started about as easily as [with] Active Record for your persistence model, and then refactor more easily towards a richer domain model as needed.”
“我想要Active Record的替代品,它可以让您像持久性模型一样简单地开始使用Active Record,然后根据需要更轻松地重构为更丰富的域模型。”
Altas has its own set of characteristics that differentiates it from others.
Altas具有自己的一套特色,这使其与众不同。
No annotations – Under the premise that the code should be in the code and not in the comments.
无注释 –在代码应位于代码中而不是注释中的前提下。
No migrations or database modification logic – Since Atlas is decoupled from the database (not entirely, the package, as a whole, needs to interact with the database to be able to move data back and forth), it makes sense that it only acts as a model of the schema and not has a creator and manager of it.
无需迁移或数据库修改逻辑 –由于Atlas与数据库解耦(整体而言,该软件包需要与数据库进行交互才能来回移动数据)(并非完全如此),因此,它仅起到以下作用:模式的模型,没有模型的创建者和管理者。
No lazy-loading – The creator thought of lazy loading as being useful, but ultimately too much trouble for what it is worth.
没有延迟加载 –创作者认为延迟加载是有用的,但最终却为它带来了太多麻烦。
No data type abstractions – The database types are exposed and available where possible.
没有数据类型抽象 -数据库类型公开并且在可能的情况下可用。
Composite key support – Atlas supports both composite primary keys and composite foreign keys.
复合键支持 – Atlas支持复合主键和复合外键。
Now that we have a general idea of Atlas and what it stands for, let’s have a look at it in use.
现在,我们对Atlas及其含义有了一个大致的了解,让我们来看看它的使用情况。
安装 (Installation)
Because Atlas is still in development at the time of this writing, we will install the latest, cutting edge version. Also, we will install the CLI package for ease of development. That leaves us with the following composer.json
file:
由于撰写本文时Atlas仍在开发中,因此我们将安装最新的最新版本。 另外,我们将安装CLI软件包以简化开发。 剩下下面的composer.json
文件:
{
"require": {
"atlas/orm": "@dev"
},
"require-dev": {
"atlas/cli": "@dev"
},
"autoload": {
"psr-4": {
"App\\": "src/App/"
}
}
}
We also have some PSR-4 there for autoloading, which we will use further down the line.
我们也有一些PSR-4用于自动加载,我们将在以后的代码中使用它们。
设定 (Setting it up)
Earlier on, we said that Atlas doesn’t act like a schema creator and manager. That means that we don’t have the tools for database creation. In this example, we are creating a new and simple database from scratch, but Atlas will work with any database you already have in place.
之前,我们说过Atlas不像架构创建者和管理者那样工作。 这意味着我们没有用于数据库创建的工具。 在此示例中,我们将从头开始创建一个新的简单数据库,但是Atlas可以与您已有的任何数据库一起使用。
We will use a very simple database design. We will have products and categories in separate database tables. Using MySQL, let’s create our database:
我们将使用非常简单的数据库设计。 我们将在单独的数据库表中提供产品和类别。 使用MySQL ,创建数据库:
CREATE DATABASE atlasorm;
USE DATABASE atlasORM;
CREATE TABLE `category` (`category_id` int(11) NOT NULL, `name` varchar(255) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `products` (`product_id` int(11) NOT NULL, `name` varchar(255) NOT NULL, `category_id` int(11) NOT NULL) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `category` ADD PRIMARY KEY (`category_id`);
ALTER TABLE `products` ADD PRIMARY KEY (`product_id`);
ALTER TABLE `category` MODIFY `category_id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `products` MODIFY `product_id` int(11) NOT NULL AUTO_INCREMENT;
After this, we add some values to both tables:
之后,我们向两个表添加一些值:
INSERT INTO `category` (`category_id`, `name`) VALUES (1, 'Steel'), (2, 'Wood'), (3, 'Leather');
INSERT INTO `products` (`product_id`, `name`, `category_id`) VALUES (1, 'Nails', 1), (2, 'Pipe', 1), (3, 'Chair', 2), (4, 'Screwdriver', 1);
With this information laid out, we can now set up our ORM. Atlas has very little code generation. To generate the skeleton classes (both the Table class and the Mapper class), we will first have to create a .php
file where we will have our database connection information. This way, the generator will know what properties it should generate.
有了这些信息,我们现在可以设置我们的ORM。 Atlas几乎没有代码生成。 为了生成框架类(Table类和Mapper类),我们首先必须创建一个.php
文件,在其中我们将拥有数据库连接信息。 这样,生成器将知道应该生成什么属性。
<?php
// connection.php
return ['mysql:dbname=testdb;host=localhost', 'username', 'password'];
Then, lets run the atlas-skeleton
generator. The atlas-skeleton
generator is a CLI tool that will use the database connection information on the connection.php
file, the class name, and the given namespace to generate the skeleton classes. Please note that we have to run the generator for each individual table in the database.
然后,运行atlas-skeleton
生成器。 atlas-skeleton
生成器是一个CLI工具,它将使用connection.php
文件,类名和给定名称空间上的数据库连接信息来生成骨架类。 请注意,我们必须为数据库中的每个表运行生成器。
./vendor/bin/atlas-skeleton.php --dir=./src/App/DataSource --conn=/path/to/connection.php --table=products App\\DataSource\\Products
./vendor/bin/atlas-skeleton.php --dir=./src/App/DataSource --conn=/path/to/connection.php --table=category App\\DataSource\\Category
After running the atlas-skeleton
command, we can check the /src/App/Datasource
folder and see that skeletons for both products
and category
were created. Both of these skeletons contain two files. A Mapper
file, that’s almost empty and a Table
class, which contains a description of the database table. This is all the setup we need.
运行atlas-skeleton
命令后,我们可以检查/src/App/Datasource
文件夹,并查看是否已创建products
和category
框架。 这两个框架都包含两个文件。 一个Mapper
文件(几乎为空)和一个Table
类,其中包含对数据库表的描述。 这就是我们需要的所有设置。
Now, let’s finally see it in action!
现在,让我们终于看到它的实际效果!
CRUD操作 (CRUD Operations)
With everything set up, lets focus on using our ORM. For this, we need an Atlas
instance, and we create one using the AtlasContainer
class.
完成所有设置后,让我们专注于使用我们的ORM。 为此,我们需要一个Atlas
实例,并使用AtlasContainer
类创建一个实例。
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Atlas\Orm\Mapper\Mapper;
use Atlas\Orm\AtlasContainer;
use App\DataSource\Category\CategoryMapper;
use App\DataSource\Products\ProductsMapper;
$atlasContainer = new AtlasContainer('mysql:host=host;dbname=atlasorm','username', 'password' );
$atlasContainer->setMappers([ProductsMapper::CLASS, CategoryMapper::CLASS]);
$atlas = $atlasContainer->getAtlas();
Nothing too fancy here, we autoload our vendor classes, include our mappers, and create an Atlas
instance with both our Products
and Category
Mapper information.
这里没什么花哨的,我们会自动加载供应商类,包括映射器,并使用Products
和Category
映射器信息创建Atlas
实例。
读 (Reading)
To read a Record
or a RecordSet
from our database we can use the following:
要从数据库中读取Record
或RecordSet
,我们可以使用以下方法:
$categoryRecord = $atlas->fetchRecord(CategoryMapper::CLASS, '2');
This will return a single Record, the one with ID 2. For a RecordSet:
这将返回一个记录,即ID为2的记录。对于RecordSet:
$categoryRecordSet = $atlas
->select(CategoryMapper::CLASS)
->orderBy(['category_id DESC'])
->limit(10)
->fetchRecordSet();
This will fetch a RecordSet of the last 10 categories ordered by category_id
.
这将获取由category_id
排序的最后10个类别的RecordSet。
创建,更新和删除 (Creating, updating and deleting)
For creating, updating and deleting, Atlas uses the same principle. Obtaining a Record and manipulating it:
对于创建,更新和删除,Atlas使用相同的原理。 获取记录并进行操作:
// start insert
$newCategory = $atlas->newRecord(CategoryMapper::CLASS);
$newCategory->name = "Bone";
// create a transaction
$transaction = $atlas->newTransaction();
$transaction->insert($newCategory);
// execute the transaction
$ok = $transaction->exec();
if ($ok) {
echo "Transaction success.";
} else {
echo "Transaction failure. ";
}
As we can see, creating is pretty straightforward. For updating and deleting we can use the same principle:
如我们所见,创建非常简单。 对于更新和删除,我们可以使用相同的原理:
//Start updating
$newCategory = $atlas->fetchRecord(CategoryMapper::CLASS, '2');
$newCategory->name = "Wood";
// create a transaction
$transaction = $atlas->newTransaction();
// plan work for the transaction
$transaction->update($newCategory);
//$transaction->delete($newCategory);
// execute the transaction plan
$ok = $transaction->exec();
if ($ok) {
echo "Transaction success.";
} else {
echo "Transaction failure. ";
}
//End updating
In this case, we can use either update
together with new table values to update a Record, or delete
to delete this same Record.
在这种情况下,我们既可以将update
与新表值一起使用来更新Record,也可以使用delete
删除相同的Record。
关于关系的简要说明 (A quick note on Relationships)
Atlas supports all four kinds of relationships: OneToMany
, ManyToOne
, OneToOne
and MantToMany
. To add relationships, we have to add them in the Mapper classes.
Atlas支持所有四种关系: OneToMany
, ManyToOne
, OneToOne
和MantToMany
。 要添加关系,我们必须将它们添加到Mapper类中。
In our particular case, let’s imagine that each category can have many products. A one to many relationship. Our CategoryMapper
would look like this:
在我们的特定情况下,让我们想象每个类别可以有许多产品。 一对多的关系。 我们的CategoryMapper
如下所示:
<?php
namespace App\DataSource\Category;
use Atlas\Orm\Mapper\AbstractMapper;
use App\DataSource\Products\ProductsMapper;
/**
* @inheritdoc
*/
class CategoryMapper extends AbstractMapper
{
/**
* @inheritdoc
*/
protected function setRelated()
{
$this->oneToMany('products', ProductsMapper::CLASS);
}
}
By default, the relationship will take the primary key on the table of origin and map it to the corresponding column in the destination table. Of course, in the case where we have a many to one relationship this is not possible. In that case the reverse will be done. The primary key on the destination table will be mapped to the corresponding column on the table of origin.
默认情况下,该关系将采用原始表上的主键,并将其映射到目标表中的相应列。 当然,在我们有多对一关系的情况下,这是不可能的。 在这种情况下,将相反。 目标表上的主键将映射到源表上的相应列。
So, if we want to fetch a RecordSet of our categories with their related product records, we can use the with()
method, just like this:
因此,如果我们想获取带有相关产品记录的类别的RecordSet,则可以使用with()
方法,如下所示:
$newRecord = $atlas
->select(CategoryMapper::CLASS)
->with([
'products'
])
->fetchRecordSet();
With the knowledge of how the basic CRUD operations work, we can now take a look at some more practical examples of code using Atlas.
了解了基本CRUD操作的工作原理之后,我们现在来看一下使用Atlas的一些更实际的代码示例。
一些实际的例子 (Some practical examples)
It’s great to have a tool like Atlas and to see how it works. But ultimately, we want to see it being used in more practical scenarios. While we will not build a full fledged application, we will look at some possible uses.
拥有Atlas之类的工具并查看其工作原理非常棒。 但最终,我们希望看到它在更实际的场景中使用。 虽然我们不会构建完整的应用程序,但我们将研究一些可能的用途。
There are some operations that we use every day without even noticing, like fetching information, inserting and deleting database records. How would those look using Atlas?
我们每天都会使用一些操作,甚至没有引起注意,例如获取信息,插入和删除数据库记录。 使用Atlas的外观如何?
<?php
require_once __DIR__ . '/vendor/autoload.php';
use Atlas\Orm\Mapper\Mapper;
use Atlas\Orm\AtlasContainer;
use App\DataSource\Category\CategoryMapper;
use App\DataSource\Products\ProductsMapper;
/**
* This function will create and return our Atlas instance set up
* with both our Mappers
*
* @return $atlasContainer
*/
function getAtlasContainer(){
$atlasContainer = new AtlasContainer('mysql:host=localhost;dbname=atlasorm','root', '' );
$atlasContainer->setMappers([ProductsMapper::CLASS, CategoryMapper::CLASS]);
return $atlasContainer->getAtlas();
}
/**
* This function will return a RecordSet of all our products
*
* @return RecordSet
*/
function getAllProducts(){
$atlas = getAtlasContainer();
$productsRecordSet = $atlas
->select(ProductsMapper::CLASS)
->fetchRecordSet();
return $productsRecordSet;
}
/**
* This function will return a Record of the Product with the specified id
*
* @param int
* @return Record
*/
function getProductById( $id ){
$atlas = getAtlasContainer();
$productRecord = $atlas->fetchRecord(ProductsMapper::CLASS, $id);
return $productRecord;
}
/**
* This function will insert a new product Record
*
* @param string $product_name
* @param int $category_name
*/
function addProduct( $product_name, $category_id ){
$atlas = getAtlasContainer();
//First we check if the category exists
$categoryRecord = $atlas->fetchRecord(CategoryMapper::CLASS, $category_id);
//if our category exists we will insert our product
if( $categoryRecord ){
//Start insert
$newProduct = $atlas->newRecord(ProductsMapper::CLASS);
$newProduct->name = $product_name;
$newProduct->category_id = $category_id;
// create a transaction
$transaction = $atlas->newTransaction();
$transaction->insert($newProduct);
// execute the transaction
$ok = $transaction->exec();
if ($ok) {
echo "Transaction success.";
} else {
// get the exception that was thrown in the transaction
$exception = $transaction->getException();
$work = $transaction->getFailure();
echo "Transaction failure. ";
echo $work->getLabel() . ' threw ' . $exception->getMessage();
}
//End insert
}else{
echo 'The category is not valid.';
}
}
/**
* This function will delete a product Record
*
* @param id $product_id
*/
function deleteProduct( $product_id ){
$atlas = getAtlasContainer();
//First, lets check if the product with $product_id exists
$productRecord = $atlas->fetchRecord(ProductsMapper::CLASS, $product_id);
if( $productRecord ){
//Delete the product
$transaction = $atlas->newTransaction();
$transaction->delete($productRecord);
// execute the transaction
$ok = $transaction->exec();
if ($ok) {
echo "Transaction success.";
} else {
// get the exception that was thrown in the transaction
$exception = $transaction->getException();
$work = $transaction->getFailure();
echo "Transaction failure. ";
echo $work->getLabel() . ' threw ' . $exception->getMessage();
}
}else{
echo 'The product you are trying to delete does not exist.';
}
}
A very basic skeleton of operations using Atlas (void of OOP and modern programming concepts due this being just a demo).
使用Atlas的基本操作框架(由于这只是一个演示,因此没有OOP和现代编程概念)。
注意事项 (Caveats)
Finally, let’s look at two caveats.
最后,让我们看一下两个警告。
Atlas uses code generation. If you change a database table that already had its code generated and you re-generate it, the mapper class will not be overridden. This is specially helpful if you wrote custom code in the mapper class.
Atlas使用代码生成 。 如果更改已经生成了其代码的数据库表,然后重新生成它,则映射器类将不会被覆盖。 如果您在mapper类中编写了自定义代码,这将特别有用。
Atlas is a work in progress, 1.0.0-alpha1 was very recently released. Be very careful when using it, especially if you try to use it in a production environment, as breaking changes may affect your projects.
Atlas是一项正在进行的工作 ,1.0.0-α1是最近发布的。 在使用它时要特别小心,尤其是如果您尝试在生产环境中使用它,因为破坏更改可能会影响您的项目。
结论 (Conclusion)
The concept behind Atlas is original, and it’s easy to understand and use. The no annotations and very little code generation aspects are really strong selling points, especially if you, just like me, like to keep your code as decoupled and independent as possible.
Atlas背后的概念是原始的,易于理解和使用。 没有注释和很少的代码生成方面是真正的卖点,尤其是如果您像我一样喜欢保持代码尽可能地分离和独立。
What do you think of Atlas? Will you give it a spin?
您如何看待Atlas? 你会旋转吗?
翻译自: https://www.sitepoint.com/a-first-look-at-atlas-the-orm-that-delivers/
open-tiny-orm