未加星标

A First Look at Atlas the ORM That Delivers - SitePoint PHP

字体大小 | |
[开发(php) 所属分类 开发(php) | 发布者 店小二03 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

This article was peer reviewed byPaul M. Jones andScott Molinari. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

“ AtlasORM is a data mapper implementation for your persistence model (not your domain model)”

More from this author Quick Intro:phpCompatibility for PHPCS - Are You PHP7 Ready? Static analysis with PHPSA: PHP Smart Analyzer

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!

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.


A First Look at Atlas   the ORM That Delivers - SitePoint PHP

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).

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.”

Altas has its own set of characteristics that differentiates it from others.

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. 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.

Now that we have a general idea of Atlas and what it stands for, let’s have a look at it in use.

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:

{
"require": {
"atlas/orm": "@dev"
},
"require-dev": {
"atlas/cli": "@dev"
},
"autoload": {
"psr-4": {"App\\": "src/App/"
}
}
}

We also have somePSR-4 there for autoloading, which we will use further down the line.

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.

We will use a very simple database design. We will have products and categories in separate database tables. Usingmysql, let’s create our database:

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.

<?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.

./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.

Now, let’s finally see it in action!

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.

<?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.

Reading

To read a Record or a RecordSet from our database we can use the following:

$categoryRecord = $atlas->fetchRecord(CategoryMapper::CLASS, '2');

This will return a single Record, the one with ID 2. For a 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 .

Creating, updating and deleting

For creating, updating and deleting, Atlas uses the same principle. Obtaining a Record and manipulating it:

// 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.

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.

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:

<?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:

$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.

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.

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?

<?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).

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 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.

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.

What do you think of Atlas? Will you give it a spin?

本文开发(php)相关术语:php代码审计工具 php开发工程师 移动开发者大会 移动互联网开发 web开发工程师 软件开发流程 软件开发工程师

主题: PHPInnoDBSQLMySQLAUAUTUTRIMRY
分页:12
转载请注明
本文标题:A First Look at Atlas the ORM That Delivers - SitePoint PHP
本站链接:http://www.codesec.net/view/484687.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(php) | 评论(0) | 阅读(26)