这与我的另一个问题有关:Persisting entities using a REST API。
对于Symfony2中的项目,我需要能够使用远程(第三方)RESTful API 来持久化实体。我还希望能够使用该API中的数据检索实体。
换句话说,我的对象保存在第三方数据库中。他们不保存在我自己的数据库中。每当我需要保存数据或查找数据时,我都会使用他们的REST API。
我被指向了几个库,包括one made by Doctrine itself。但是,它们都没有为我提供我正在寻找的东西。由Doctrine制作的那个是最好的选择,但是使用Active Record模式并没有提供所有甜蜜的Doctrine 2。不要误会我的意思,我一直在使用Active Record实现,但我现在已经爱上了Doctrine的Data Mapper模式。
理想情况下,我希望能够使用Doctrine的ORM,只需用使用API调用保存实体的逻辑替换特定于数据库的部分。 (当然,使用相同的API检索它们)。这样我就可以使用大致相同的语法保存我的实体:
// current way to save $entity in database:
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
// desired way to save $entity using REST API:
// (just an example, it doesn't have to be exactly like this)
$em = $this->getDoctrine()->getManager('rest');
$em->persist($entity);
$em->flush();
请注意,我不是要尝试构建自己的API,而只是尝试与第三方API进行通信以保存我的实体。我对Doctrine很新,但到目前为止我还是喜欢它。我非常喜欢从实体中分离持久性逻辑的想法,但到目前为止,我无法找到如何使用API来保存它们。
Symfony的文档中有一篇文章,描述了how to work with multiple Entity Managers。我正在寻找类似于此的解决方案,但是使用实体管理器可以使用REST而不是数据库。
我一直在努力调整Doctrine的ORM,但我最终只重写了一半代码,因为它(似乎)与数据库特定的逻辑紧密耦合。我当然可能会做一些愚蠢的事情。
所以我的问题是,有没有办法用自定义的替换/覆盖Doctrine ORM的数据库特定部分?没有重写很多应该对所有持久化方法都很常见的事情?以前做过吗?或者它是不可能的,因为Doctrine旨在用于数据库并且不够灵活以用于其他用途?
CakePHP似乎可以通过让您定义自定义DataSource来实现此目的。这样你就可以使用SQL数据库保存模型,但也可以使用API,会话等。我想大致相同,但使用Doctrine而不是CakePHP。
实际的数据库查询似乎是由执行的
Doctrine\ORM\Persisters\BasicEntityPersister
class。还有其他几个xxxPersister类,用于处理不同类型的继承。可能可以用我们自己的类替换xxxPersister类,因此我们可以用REST API代码替换数据库代码。
persister对象是在Doctrine\ORM\UnitOfWork
类的getEntityPersister()
方法中创建的。类名是硬编码的,因此如果我们想使用自己的持久性,我们需要覆盖Doctrine\ORM\UnitOfWork
。
Doctrine\ORM\UnitOfWork
似乎被硬编码到Doctrine\ORM\EntityManager
,因此我们也需要覆盖那个。但是,此类似乎包含一些特定于数据库的部分。例如,它的构造函数需要Doctrine\DBAL\Connection
对象作为参数。也许最好创建我们自己的EntityManger(实现Doctrine\Common\Persistence\ObjectManager
接口),只要这不需要花费太多时间/精力。
用于检索/加载/查找对象的特定于数据库的代码与用于持久化/删除等的代码位于同一类中:Doctrine\ORM\Persisters\xxxPersister
类。因此,如果我们能够用自己的替换它们,为了持久化对象,我们也可以检索对象。例如,当您致电$entityRepository->findAll()
时,它会返回$entityRepository->findBy(array())
(因为findAll()
只是findBy(array())
的别名),它将运行以下代码:
$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->loadAll($criteria, $orderBy, $limit, $offset);
换句话说,一旦我们EntityManager
创建了正确的UnitOfWork
和xxxPersister
个对象,我们就可以使用{{1}中的find
方法}}
我发现为Doctrine开发了一个新功能:custom persisters(另见this)。这应该可以更容易地使用自定义持久性类。我还不知道它是否能让我们创建一个非DB持久性,但看起来很有希望。但是,最后的更新是在八月,所以我不确定它是否仍在积极开发中。
答案 0 :(得分:10)
您可以使用https://github.com/doctrine/rest构建一个与目标服务器通信的REST客户端。这里的关键部分是从实体(本地)到REST API(目标)的映射。
简而言之:Doctrine2(本地数据库) - >休息客户端(实体休息映射) - >请求(目标服务器)
Doctrine / Rest还提供了另一种方式:Doctrine Rest Server,通过REST公开本地实体(向服务器发出请求)。但这不是你想要的。
答案 1 :(得分:7)
DoctrineRestDriver正在做你想要的。 https://github.com/CircleOfNice/DoctrineRestDriver
配置Doctrine:
doctrine:
dbal:
driver_class: "Circle\\DoctrineRestDriver\\Driver"
host: "http://www.your-url.com/api"
port: 80
user: "Circle"
password: "CantRenember"
构建实体:
/**
* This annotation marks the class as managed entity:
*
* @ORM\Entity
*
* You can either only use a resource name or the whole url of
* the resource to define your target. In the first case the target
* url will consist of the host, configured in your options and the
* given name. In the second one your argument is used as it is.
* Important: The resource name must begin with its protocol.
*
* @ORM\Table("products|http://www.yourSite.com/api/products")
*/
class Product {
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\Column(type="string", length=100)
*/
private $name;
public function getId() {
return $this->id;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
}
假设我们已将值http://www.yourSite.com/api/products用于产品实体的@Table注释。
控制器:
<?php
namespace CircleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\HttpFoundation\Response;
class UserController extends Controller {
/**
* Sends the following request to the API:
* POST http://www.yourSite.com/api/products HTTP/1.1
* {"name": "Circle"}
*
* Let's assume the API responded with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* Response body is "1"
*/
public function createAction() {
$em = $this->getDoctrine()->getManager();
$entity = new CircleBundle\Entity\Product();
$entity->setName('Circle');
$em->persist($entity);
$em->flush();
return new Response($entity->getId());
}
/**
* Sends the following request to the API by default:
* GET http://www.yourSite.com/api/products/1 HTTP/1.1
*
* which might respond with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* Response body is "Circle"
*/
public function readAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
return new Response($entity->getName());
}
/**
* Sends the following request to the API:
* GET http://www.yourSite.com/api/products HTTP/1.1
*
* Example response:
* HTTP/1.1 200 OK
* [{"id": 1, "name": "Circle"}]
*
* Response body is "Circle"
*/
public function readAllAction() {
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('CircleBundle\Entity\Product')->findAll();
return new Response($entities->first()->getName());
}
/**
* After sending a GET request (readAction) it sends the following
* request to the API by default:
* PUT http://www.yourSite.com/api/products/1 HTTP/1.1
* {"name": "myName"}
*
* Let's assume the API responded the GET request with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "Circle"}
*
* and the PUT request with:
* HTTP/1.1 200 OK
* {"id": 1, "name": "myName"}
*
* Then the response body is "myName"
*/
public function updateAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
$entity->setName('myName');
$em->flush();
return new Response($entity->getName());
}
/**
* After sending a GET request (readAction) it sends the following
* request to the API by default:
* DELETE http://www.yourSite.com/api/products/1 HTTP/1.1
*
* If the response is:
* HTTP/1.1 204 No Content
*
* the response body is ""
*/
public function deleteAction($id = 1) {
$em = $this->getDoctrine()->getManager();
$entity = $em->find('CircleBundle\Entity\Product', $id);
$em->remove($entity);
$em->flush();
return new Response();
}
}
您甚至可以使用DQL或本机查询。
答案 2 :(得分:3)
由于没有现成的解决方案,我决定自己编写。我叫它RAPL。它受到Doctrine的ORM的启发(事实上,它使用了Doctrine Common提供的许多接口)。
使用RAPL我可以简单地编写一个小的YAML文件来配置我的实体和Web服务之间的映射,允许我使用自定义EntityManager来持久化/检索实体。
答案 3 :(得分:2)
我认为你的方式不对 我现在还没准备好深入研究文档,但我理解教义堆栈为:
ORM - &gt; DQL(学说查询语言) - &gt; dbal - &gt;某些数据库sql
并指出您在DBAL中作为自定义数据库驱动程序的功能。
我认为创建常见的REST-Driver真正有趣的功能,它将与第三方服务轻松集成。
答案 4 :(得分:1)
我不确定,但您可以尝试使用lifecycle callback events实体通过REST执行持久逻辑。
答案 5 :(得分:0)
我想做类似的事情,所以我构建了这个库来帮助将教义实体暴露为RESTful资源。它具有相当多的功能,允许您通过pull(GET)和push(POST / PUT / PATCH)方法准确定义您想要显示的内容。
http://leedavis81.github.io/drest/
https://github.com/leedavis81/drest
希望有所帮助