level-2 / maphper
Maphper 是一个轻量级的 ORM,采用 DataMapper 模式,可以动态创建数据库表。
Requires
- php: >=5.4.0
This package is auto-updated.
Last update: 2024-09-17 02:28:51 UTC
README
Maphper - 使用 Data Mapper 模式的 PHP ORM
仍在开发中!
功能
-
动态创建数据库表
-
目前支持数据库表,但将支持 XML 文件、Web 服务甚至 Twitter 流作为数据源
-
支持任何两个数据源之间的关系
-
复合主键
Maphper 采用简单和极简的方法进行数据映射,旨在为最终用户提供直观且易于使用的 API。
Maphper 的主要哲学是程序员应该处理数据集,然后能够操作这个集合并从中提取其他数据。
基本用法
这设置了一个数据映射器,将其映射到 MySQL 数据库中的 'blog' 表。每个映射器都有一个数据源。这个源可以是任何东西,比如数据库表、XML 文件、充满 XML 文件的文件夹、CSV、Twitter 流、Web 服务或任何东西。
目标是给开发者提供一个一致且简单的 API,该 API 在任何数据源之间共享。
然后可以像数组一样处理 $blogs 对象。
要使用数据库表作为其数据源设置 Maphper 对象,首先创建一个标准的 PDO 实例
$pdo = new PDO('mysql:dbname=maphpertest;host=127.0.0.1', 'username', 'password');
然后创建一个 \Maphper\DataSource\Database 的实例,传入 PDO 实例、表名和主键
$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id');
最后创建一个 \Maphper\Maphper 的实例,传入数据源
$blogs = new \Maphper\Maphper($blogSource);
您可以使用以下方式遍历所有博客
// Equivalent to SELECT * FROM blogs foreach ($blogs as $blog) { echo $blog->title . '<br />'; }
数据库表(或 XML 文件、Web 服务等)中的任何字段都将可用在 $blog 对象中
或者您可以使用 ID 查找特定的博客
//Equivalent to SELECT * FROM blogs WHERE id = 142 echo $blogs[142]->title;
这将找到 id 为 142 的博客并显示标题。
过滤器
Maphper 支持过滤数据
//find blogs that were posted on a specific date //Equivalent to SELECT * FROM blogs WHERE date = '2015-04-09' $filteredBlogs = $blogs->filter(['date' => '2014-04-09']); //this will only retrieve blogs that were matched by the filter foreach ($filteredBlogs as $blog) { echo $blog->title; }
过滤器可以扩展并串联在一起
//find blogs that were posted with the title "My Blog" by the author with the id of 7 //Equivalent to SELECT * FROM blogs WHERE title = 'My Blog' AND authorId = 7 $filteredBlogs = $blogs->filter(['title' => 'My Blog'])->filter(['authorId' => 7]); //this will only retrieve blogs that were matched by both filters foreach ($filteredBlogs as $blog) { echo $blog->title; }
除了过滤之外,还有 sort() 和 limit() 方法,都可以串联使用
foreach ($blogs->filter(['date' => '2014-04-09']) as $blog) { echo $blog->title; }
要找到最新的 5 篇博客,可以使用
//Equivalent to SELECT * FROM blogs LIMIT 5 ORDER BY date DESC foreach ($blogs->limit(5)->sort('date desc') as $blog) { echo $blog->title; }
像任何数组一样,您可以使用以下方法计算博客的总数
//Equivalent to SELECT count(*) FROM lblogs echo 'Total number of blogs is ' . count($blogs);
这将计算表中的博客总数。您还可以计算过滤结果的数量
//Count the number of blogs in category 3 //Equivalent to SELECT count(*) FROM blogs WHERE categoryId = 3 echo count($blogs->filter(['categoryId' => 3]);
保存数据
要将对象保存回数据映射器,只需创建一个 stdClass 实例
$pdo = new PDO('mysql:dbname=maphpertest;host=127.0.0.1', 'username', 'password'); $blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id'); $blogs = new \Maphper\Maphper($blogSource); $blog = new stdClass; $blog->title = 'My Blog Title'; $blog->content = 'This is my first blog entry'; //Store the blog using the next available ID $blogs[] = $blog; echo 'The new blog ID is :' . $blog->id;
或者,您可以通过指定索引将记录写入特定的 ID
$blog = new stdClass; $blog->title = 'My Blog Title'; $blog->content = 'This is my first blog entry'; //Store the blog with the primary key of 7 $blogs[7] = $blog;
注意:这种行为与直接在 $blog 对象上设置 id 属性的行为相同
$blog = new stdClass; $blog->id = 7; $blog->title = 'My Blog Title'; $blog->content = 'This is my first blog entry'; //Store the blog with the primary key of 7 $blogs[] = $blog;
关系
如果有存储关于博客作者信息的 author
表,可以以类似的方式设置
$authorSource = new \Maphper\DataSource\Database($pdo, 'author', 'id'); $authors = new \Maphper\Maphper($authorSource);
可以使用类似的方式进行使用
$author = $authors[123]; echo $author->name;
一旦定义了博客和作者映射器,您可以在它们之间创建一个关系。
这是一个一对一关系(一个博客有一个作者),可以使用博客映射器上的 addRelation 方法实现
//Create a one-to-one relationship between blogs and authors (a blog can only have one author) $relation = new \Maphper\Relation\One($authors, 'authorId', 'id'); $blogs->addRelation('author', $relation);
使用 \Maphper\Relation\One 实例告诉 Maphper 这是一个一对一的关系
第一个参数 $authors
告诉 Maphper 该关系是对 $authors
映射器(在这种情况下,是作者数据库表,尽管您可以在不同的数据源之间定义关系)
authorId
是博客表中要连接的字段
id
是作者表中要连接的字段
关系构建完成后,可以使用以下方法将其添加到博客映射器:
$blogs->addRelation('author', $relation);
第一个参数(此处为'author')是相关数据可用的属性名称。因此,从 $blogs 映射器检索到的任何对象现在都将有一个包含博客作者的 'author' 属性,可以使用如下方式使用:
foreach ($blogs as $blog) { echo $blog->title . '<br />'; echo $blog->author->name . '<br />'; }
类似地,您可以定义作者和博客之间的反向关系。这是一个一对一关系,因为一个作者可以发表多篇博客。
//Create a one-to-many relationship between blogs and authors (an author can have multiple blog entries) //Joining from the 'id' field in the authors mapper to the 'authorId' field in the blogs mapper $relation = new \Maphper\Relation\Many($blogs, 'id', 'authorId'); $authors->addRelation('blogs', $relation);
这是通过使用 $authors
映射器中的 id
字段到 $blogs
映射器中的 authorId
字段来创建 $authors
和 $blogs
映射器之间的 One:Many 关系,并为 $authors
映射器返回的任何对象提供一个可用的 blogs
属性。
//Count all the blogs by the author with id 4 $authors[4]->name . ' has posted ' . count($authors[4]->blogs) . ' blogs:<br />'; //Loop through all the blogs created by the author with id 4 foreach ($authors[4]->blogs as $blog) { echo $blog->title . '<br />'; }
保存具有关系的值
创建映射器和定义它们之间的关系后,您可以使用关系写入数据。这将在幕后自动设置任何相关字段。
$authors = new \Maphper\Maphper(new \Maphper\DataSource\Database($pdo, 'author')); $blogs = new \Maphper\Maphper(new \Maphper\DataSource\Database($pdo, 'blog', 'id')); $blogs->addRelation('author', new \Maphper\Relation\One($authors, 'authorId', 'id')); $blog = new stdClass; $blog->title = 'My First Blog'; $blog->date = new \DateTime(); $blog->author = new stdClass; $blog->author->name = 'Tom Butler'; $blogs[] = $blog;
这将同时将 $blog
对象保存到 blog
表中,将 $author
对象保存到 author
表中,并将博客记录的 authorId 列设置为生成的 id。
您也可以用一对一关系做同样的事情。
$authors = new \Maphper\Maphper(new \Maphper\DataSource\Database($pdo, 'author')); $blogs = new \Maphper\Maphper(new \Maphper\DataSource\Database($pdo, 'blog', 'id')) $authors->addRelation('blogs', new \Maphper\Relation\Many($blogs, 'id', 'authorId')); //Find the author with id 4 $author = $authors[4]; $blog = new stdClass; $blog->title = 'My New Blog'; $blog->date = new \DateTime(); //Add the blog to the author. This will save to the database at the this point, you do not need to explicitly //Save the $author object after adding a blog to it. $author->blogs[] = $blog;
复合主键
Maphper 允许复合主键。例如,如果您有一个产品表,您可以使用制造商 id 和制造商零件号作为主键(两个制造商可能使用相同的零件号)。
为此,使用一个数组定义主键的数据源。
$pdo = new PDO('mysql:dbname=maphpertest;host=127.0.0.1', 'username', 'password'); $productSource = new \Maphper\DataSource\Database($pdo, 'products', ['manufacturerId', 'partNumber']); $products = new \Maphper\Maphper($productSource);
一旦您定义了使用多个键的源,您就可以像处理二维数组一样处理 $products
变量。
//Get the product with manufacturerId 7 and partNumber AC294 echo $products[7]['AC294']->name;
要使用复合键写入数据,您只需将一个对象写入指定的索引即可。
$pdo = new PDO('mysql:dbname=maphpertest;host=127.0.0.1', 'username', 'password'); $productSource = new \Maphper\DataSource\Database($pdo, 'products', ['manufacturerId', 'partNumber']); $products = new \Maphper\Maphper($productSource); $product = new stdClass; $product->name 'Can of cola'; $products[1]['CANCOLA'] = $product;
日期
Maphper 使用内置的 PHP \DateTime
类来存储和按日期搜索。
$blog = new stdClass; $blog->title = 'A blog entry'; //You can construct the date object using any of the formats availble in the inbuilt PHP datetime class $blog->date = new \DateTime('2015-11-14');
这也可以用于筛选器。
//Find all blogs posted on 2015-11-14 $maphper->filter(['date' => new \DateTime('2015-11-14')]);
建议使用 DateTime 类而不是传递日期值作为字符串,因为并非所有映射器都使用相同的内部日期格式。
自动数据库表创建/修改
Maphper 可以指示自动构建数据库表。这是动态进行的,您只需告诉 Maphper 您想使用这种行为。当您构建数据库数据源时,将 editmode 设置为 true。
$pdo = new PDO('mysql:dbname=maphpertest;host=127.0.0.1', 'username', 'password'); $blogs = new \Maphper\DataSource\Database($pdo, 'blogs', 'id', ['editmode' => true]);
n.b. 'editmode => true
' 是 \Maphper\DataSoruce\Database::EDIT_STRUCTURE | \Maphper\DataSoruce\Database::EDIT_INDEX | \Maphper\DataSoruce\Database::EDIT_OPTIMISE;
的缩写。
可用的标志可以互换使用位或,例如 \Maphper\DataSoruce\Database::EDIT_STRUCTURE | \Maphper\DataSoruce\Database::EDIT_INDEX
将启用结构和索引修改,但不会允许列优化。
editmode
的三个选项是
\Maphper\DataSoruce\Database::EDIT_STRUCTURE
- 当此选项设置时,Maphper 自动创建不存在的表,在写入尚不存在属性的列时创建列,并在越界列上更改数据类型。
$blog = new stdClass; $blog->title = 'A blog'; $blog->date = new \DateTime(); $blogs[] = $blog;
对于数据库映射器,这将发出一个 CREATE TABLE
语句,创建一个名为 blogs
的表,具有列 id INT auto_increment
、title VARCHAR
、date DATETIME
。
类型处理
Maphper 将在创建表时使用最严格的类型。例如
$blog = new stdClass; $blog->title = 1; $blogs[] = $blog;
这将创建一个作为整数的 title
列,因为只有整数被存储在其中。然而,如果在创建后向表中添加了不同类型的记录
$blog = new stdClass; $blog->title = 'Another blog'; $blogs[] = $blog;
这将发出一个 ALTER TABLE
查询,将 title
列更改为 varchar
。同样,如果添加了非常长的字符串作为标题,该列将更改为 LONGBLOG
。所有这些都是在幕后动态完成的,作为开发者,您根本不需要担心表结构。
索引
\Maphper\DataSoruce\Database::EDIT_INDEX
当设置此选项时,Maphper将自动为在WHERE、ORDER和GROUP语句中使用的列添加索引。如果执行多列WHERE查询,还会添加多列索引。
数据库优化
\Maphper\DataSoruce\Database::EDIT_OPTIMISE
当设置此选项时,Maphper会自动定期优化数据库表。例如,一个设置为VARCHAR(255)的列,其中最长条目为7个字符,将被更改为VARCHAR(7);或者一个包含3条记录(值分别为1、2、3)的VARCHAR(255)列将被转换为INT(11)。这还会自动删除所有记录中都包含NULL值的任何列。
目前优化在创建DataSource的500次后发生一次。在未来的版本中,此值将是可配置的。
映射对象的具体类
您可以使用自己的类代替stdClass来管理Maphper的任何对象。当对象被创建时,其属性将自动设置。
例如,如果您有一个产品类
class Product { private $name; private $price; const TAX_RATE = 0.2; public function getTax() { return $this->price * self::TAX_RATE; } public function getTotalPrice() { return $this->price + $this->getTax(); } public function setName($name) { $this->name = $name; } }
您可以通过在创建Maphper实例时,在$options
数组中使用resultClass
选项来指示Maphper使用此类
$dataSource = new \Maphper\DataSource\Database($pdo, 'product', 'id'); $products = new \Maphper\Maphper($dataSource, ['resultClass' => 'Product']); $product = $products[123]; echo get_class($product); //"Product" instance //And as expected, the methods from Product are available: $tax = $product->getTax(); $total = $product->getTotalPrice();
私有属性在保存和加载时与任何正常属性相同。
同样,您也可以创建Product
类的实例并将其保存到映射器中。
$product = new Product; $product->setName('A Product'); //Write the product to the mapper. Even though $product->name is private it will still be stored $products[] = $product;
新对象的工厂创建
有时您的结果类可能存在依赖关系。在这种情况下,您可以指定一个方法而不是类名作为工厂。考虑以下示例
class TaxCalculator { const TAX_RATE = 0.2; public function getTax($price) { return $price * self::TAX_RATE; } } class Product { private $name; private $price; private $taxCalculator; public function __construct(TaxCalculator $taxCalculator) { $this->taxCalculator = $taxCalculator; } public function getTax() { return $this->taxCalculator->getTax($this->price); } public function getTotalPrice() { return $this->price + $this->getTax(); } }
在这种情况下使用
$dataSource = new \Maphper\Maphper($database, ['resultClass' => 'Product']);
将报错,因为当构造Product
实例时,Maphper不足以猜测需要将TaxCalculator
实例作为构造函数参数。相反,您可以通过传递一个返回完全构造对象的闭包来解决问题
$taxCalculator = new TaxCalculator; $dataSource = new \Maphper\Maphper($database, ['resultClass' => function() use ($taxCalculator) { return new Product($taxCalculator); }]);
或者,如果您想要对依赖关系有更多控制,可以使用依赖注入容器,例如Dice
$dice = new \Dice\Dice; $dataSource = new \Maphper\Maphper($database, ['resultClass' => function() use ($dice) { return $dice->create('Product'); }]);
多对多关系
考虑movie
和actor
表,一个演员可以出现在多部电影中,而一部电影也有多个演员。仅使用两个表和主键/外键关系无法建模这种关系。
在关系型数据库(和Maphper)中,这需要存储actorId
和movieId
的中间表。
要使用Maphper建模此关系,首先设置标准的actor
和movie
映射器
$actors = new \Maphper\Maphper(new \Maphper\Datasource\Database($pdo, 'actor', 'id')); $movies = new \Maphper\Maphper(new \Maphper\Datasource\Database($pdo, 'movie', 'id'));
然后添加一个中间表的表。请注意,这需要两个主键,一个用于actorId
,一个用于movieId
$cast = new \Maphper\Maphper(new \Maphper\Datasource\Database($pdo, 'cast', ['movieId', 'actorId']));
请注意,Maphper可以在开启editmode的情况下为您创建此表
现在,您可以使用\Maphper\Relation\ManyMany
类来设置多对多关系,作为关系的第一个实例
$actors->addRelation('movies', new \Maphper\Relation\ManyMany($cast, $movies, 'id', 'movieId'));
这将在actor
对象上创建一个名为movies
的关系。
ManyMany类的第一个构造函数参数是中间表。
第二个构造函数参数是要映射的表。
第三个是movies
表的主键。
第四个是中间表中的键。
这将连接$actors映射器到$cast映射器,然后是$cast映射器到$movies映射器。
完成此操作后,您还可以设置反向关系,以相同的方式将电影映射到出演它们的演员
$movies->addRelation('actors', new \Maphper\Relation\ManyMany($cast, $actors, 'id', 'actorId'));
一旦这两个关系都设置好了,您就可以添加一个带有电影的演员
$actor = new \stdclass; $actor->id = 123; $actor->name = 'Samuel L. Jackson'; //save the actor $actors[] = $actor; //now add some movies to the actor $movie1 = new \stdclass; $movie1->title = 'Pulp Fiction'; $actor->movies[] = $movie1; //now add some movies to the actor $movie2 = new \stdclass; $movie2->title = 'Snakes on a Plane'; $actor->movies[] = $movie2;
完成此操作后,您可以使用以下方法获取一个演员出演的所有电影
$actor = $actors[123]; echo $actor->name . ' was in the movies:' . "\n"; foreach ($actor->movies as $movie) { echo $movie->title . "\n"; }
这将打印
Samuel L. Jackson was in the movies:
Pulp Fiction
Snakes on a Plane
当然,使用正常的一对多关系也可以做到这一点。多对多关系只有在有更多演员时才有用
$actor = new \stdclass; $actor->id = 124; $actor->name = 'John Travolta'; $actors[] = $actor; //Find the movie 'Pulp Fiction and add it to John Travolta $movie = $movies->filter(['title' =>'Pulp Fiction'])->item(0); $actor->movies[] = $movie;
现在,您可以使用以下方法找到所有出演《低俗小说》的演员
$movie = $movies->filter(['title' =>'Pulp Fiction'])->item(0); echo 'The actors in ' . $movie->title . ' are :' . "\n"; foreach ($movie->actors as $actor) { echo $actor->name . "\n"; }
这将打印
The actors in Pulp Fiction are:
Samuel L. Jackson
John Travolta
在中间表中存储数据
有时在中间表中存储额外信息很有用。在上面的例子中,了解演员在特定电影中所扮演的角色名称将很有帮助。为此,在设置关系时使用了两个额外字段。
而不是
$actors->addRelation('movies', new \Maphper\Relation\ManyMany($cast, $movies, 'id', 'movieId')); $movies->addRelation('actors', new \Maphper\Relation\ManyMany($cast, $actors, 'id', 'actorId'));
您可以使用
$actors->addRelation('roles', new \Maphper\Relation\ManyMany($cast, $movies, 'id', 'movieId', 'movie'); $movies->addRelation('cast', new \Maphper\Relation\ManyMany($cast, $actors, 'id', 'actorId', 'actor');
为《\Maphper\Relation\ManyMany》构造函数的第5个参数添加了。
此参数是用于中间表对象的字段名称。当提供此参数时,中间映射器不会自动遍历,而是像父表(例如演员)和中间表(例如演员表)之间的正常一对多关系一样访问:现在可以为演员分配一些角色
在这个例子中,演员有roles
,电影有cast
。现在这个设置完成后,可以为演员分配一些角色
$actor = $actors[123]; //Find the movie $movie = $movies->filter(['title' =>'Pulp Fiction'])->item(0); //Create a role $role = new \stdClass; //Set the character name for the role $role->characterName = 'Jules Winnfield'; //Assign the movie to the role $role->movie = $movie; //Assign the role to the actor $actor->roles[] = $role; //Find the movie $movie = $movies->filter(['title' =>'Snakes on a Plane'])->item(0); //Create a role $role = new \stdClass; //Set the character name for the role $role->characterName = 'Neville Flynn'; //Assign the movie to the role $role->movie = $movie; //Assign the role to the actor $actor->roles[] = $role;
这将为Samuel L. Jackson分配了Pulp Fiction和Snakes on a Plane电影的角色。现在可以显示电影了
$actor = $actors[123]; echo $actor->name . ' has the roles:' . "\n" foreach ($actors[123]->roles as $role) { echo $role->characterName . ' in the movie . ' . $role->movie->title . "\n"; }
这将打印出
Samuel L. Jackson has the roles: Jules Winnfield in Pulp Fiction Neville Flynn in Snakes on a plane