使用Doctrine 2将实体保存到REST API而不是DB



这与我的另一个问题有关:使用REST API持久化实体。

对于Symfony2中的项目,我需要能够使用远程(第三方)RESTful API持久化实体。我还希望能够从API中检索具有数据的实体。

换句话说,我的对象保存在第三方数据库中。它们没有保存在我自己的数据库中。每当我需要保存数据或查找数据时,我都会使用他们的REST API。

有人向我介绍了几个图书馆,其中包括一个由Doctrine自己制作的图书馆。然而,他们都没有提供我想要的东西。由Doctrine制作的是最好的选择,但使用了Active Record模式,并没有提供所有甜蜜的Doctrine 2内容。不要误解我的意思,我已经使用Active Record实现很长时间了,但我现在已经爱上了Doctrine的数据映射器模式。

理想情况下,我希望能够使用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通信以保存我的实体。我对教义比较陌生,但到目前为止我很喜欢它。我非常喜欢将持久性逻辑与实体分离的想法,但到目前为止,我还不知道如何使用它来使用API保存它们。

Symfony的文档中有一篇文章描述了如何使用多个实体经理。我正在寻找一个类似的解决方案,但有一个实体管理器,使我能够使用REST而不是DB。

我自己一直在尝试调整Doctrine的ORM,但我最终只重写了他们一半的代码,因为它(似乎)与特定于数据库的逻辑耦合得太紧密了。当然,我可能在做一些愚蠢的事情。

因此,我的问题是,有没有一种方法可以用自定义的部分来取代/覆盖Doctrine的ORM的数据库特定部分?如果不重写许多对于所有持久性方法都应该通用的东西?以前做过吗?或者这根本不可能,因为条令旨在与数据库一起使用,而对于其他用途来说不够灵活?

我自己的进步

CakePHP似乎能够做到这一点,它允许您定义一个自定义的DataSource。通过这种方式,您可以使用SQL数据库保存模型,也可以使用API、会话等。我想做大致相同的事情,但使用Doctrine而不是CakePHP。

更新1

实际的数据库查询似乎是由DoctrineORMPersistersBasicEntityPersister类。还有其他几个xxxPersister类,用于处理不同类型的继承。可以用我们自己的类替换xxxPersister类,这样我们就可以用RESTneneneba API代码替换DB代码。

持久化对象是在DoctrineORMUnitOfWork类的getEntityPersister()方法中创建的。类名是硬编码的,所以如果我们想使用自己的持久器,我们需要重写DoctrineORMUnitOfWork

更新2

DoctrineORMUnitOfWork似乎被硬编码为DoctrineORMEntityManager,所以我们也需要覆盖它。但是,这个类似乎包含一些特定于数据库的部分。例如,它的构造函数需要一个DoctrineDBALConnection对象作为参数。也许最好创建我们自己的EntityManger(实现DoctrineCommonPersistenceObjectManager接口),只要这不需要太多时间/精力。

更新3

用于检索/加载/查找对象的特定于数据库的代码与用于持久化/删除等的代码位于同一类中:DoctrineORMPersistersxxxPersister类。因此,如果我们能够用自己的对象替换它们,为了持久化对象,我们也可以检索对象。例如,当您调用$entityRepository->findAll()时,它将返回$entityRepository->findBy(array())(因为findAll()只是findBy(array())的别名),它将运行以下代码:

$persister = $this->_em->getUnitOfWork()->getEntityPersister($this->_entityName);
return $persister->loadAll($criteria, $orderBy, $limit, $offset);

换句话说,一旦我们让EntityManager创建正确的UnitOfWorkxxxPersister对象,我们就可以在EntityRepository中使用find方法。

更新4

我发现Doctrine开发了一个新功能:自定义持久性(也可以参见此)。这样可以更容易地使用自定义的persister类。我还不知道它是否能让我们创建一个非DB持久器,但它看起来很有希望。然而,上一次更新是在8月份,所以我不确定它是否仍在积极开发中。

您可以使用https://github.com/doctrine/rest以构建一个REST客户端,该客户端与目标服务器进行对话。这里的关键部分是从实体(本地)到RESTneneneba API(目标)的映射。

简而言之:Doctrine2(本地DB)->Rest客户端(实体到Rest映射)->Request(目标服务器)

Doctrine/Rest还提供了另一种方法:Doctrine-Rest服务器,通过Rest(对服务器的请求)公开您的本地实体。但这不是你想要的。

DoctrineRestDriver正是在做您想要做的事情。https://github.com/CircleOfNice/DoctrineRestDriver

配置原则:

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:
 *
 * @ORMEntity
 *
 * 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.
 *
 * @ORMTable("products|http://www.yourSite.com/api/products")
 */
class Product {
    /**
     * @ORMColumn(type="integer")
     * @ORMId
     * @ORMGeneratedValue(strategy="AUTO")
     */
    private $id;
    /**
     * @ORMColumn(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 CircleBundleController;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyHttpFoundationResponse;
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 CircleBundleEntityProduct();
        $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('CircleBundleEntityProduct', $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('CircleBundleEntityProduct')->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('CircleBundleEntityProduct', $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('CircleBundleEntityProduct', $id);
        $em->remove($entity);
        $em->flush();
        return new Response();
    }
}

您甚至可以使用DQL或本机查询。

由于没有现成的解决方案,我决定编写自己的解决方案。我称之为RAPL。它深受Doctrine的ORM的启发(事实上,它使用了Doctrine Common提供的许多接口)。

使用RAPL,我可以简单地编写一个小的YAML文件来配置实体和web服务之间的映射,从而允许我使用自定义EntityManager持久化/检索实体。

我认为你的做法不对
我现在还没有准备好深入研究文档,但我将条令堆栈理解为:

ORM->DQL(条令查询语言)->dbal->一些数据库sql

并指出您在DBAL中作为自定义数据库驱动程序的实现。

我认为创建一个通用的REST驱动程序非常有趣的功能,它将很容易与第三方服务集成。

我不确定,但您可以尝试使用实体的生命周期回调事件来通过REST执行持久化逻辑。

p>我想做类似的事情,所以我构建了这个库来帮助将条令实体公开为RESTful资源。它有相当多的功能,并允许您通过pull(GET)和push(POST/PUT/PATCH)方法准确地定义您想要公开的内容。

http://leedavis81.github.io/drest/

https://github.com/leedavis81/drest

希望它能帮助

相关内容

  • 没有找到相关文章

最新更新