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

Nic*_*tel 26 php rest persistence symfony doctrine-orm

这与我的另一个问题有关:使用REST API保持实体.

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

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

我被指向了几个库,包括一个由Doctrine本身制作的库.但是,它们都没有为我提供我正在寻找的东西.由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();
Run Code Online (Sandbox Code Playgroud)

请注意,我不是要尝试构建自己的API,而只是尝试与第三方API进行通信以保存我的实体.我对Doctrine很新,但到目前为止我还是喜欢它.我真的很喜欢从实体中分离持久性逻辑的想法,但到目前为止我无法找到如何使用API​​来保存它们.

Symfony的文档中有一篇文章,描述了如何与多个实体经理合作.我正在寻找类似于此的解决方案,但是使用实体管理器可以使用REST而不是数据库.

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

所以我的问题是,有没有办法用自定义的方式替换/覆盖Doctrine ORM的数据库特定部分?没有重写很多应该对所有持久化方法都很常见的事情?以前做过吗?或者它是不可能的,因为Doctrine旨在与数据库一起使用,并且不够灵活,不能用于其他用途?

我自己的进步

CakePHP似乎可以通过让您定义自定义DataSource来实现此目的.这样你可以使用SQL数据库保存模型,但也可以使用API​​,会话等.我想大致相同,但使用Doctrine代替CakePHP.

更新1

实际的数据库查询似乎是由Doctrine\ORM\Persisters\BasicEntityPersister执行的 .还有其他几个xxxPersister类,用于处理不同类型的继承.有可能用我们自己的类替换xxxPersister类,因此我们可以用REST API代码替换DB代码.

persister对象是在类的getEntityPersister()方法中创建的Doctrine\ORM\UnitOfWork.类名是硬编码的,所以Doctrine\ORM\UnitOfWork如果我们想要使用自己的persisters ,我们需要覆盖.

更新2

Doctrine\ORM\UnitOfWork似乎是硬编码Doctrine\ORM\EntityManager,所以我们也需要覆盖那个.但是,此类似乎包含一些特定于数据库的部分.例如,它的构造函数需要一个Doctrine\DBAL\Connection对象作为参数.也许最好创建我们自己的EntityManger(实现Doctrine\Common\Persistence\ObjectManager接口),只要这不需要花费太多时间/精力.

更新3

用于检索/加载/查找对象的特定于数据库的代码与用于持久化/删除等的代码位于同一类中: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);
Run Code Online (Sandbox Code Playgroud)

换句话说,一旦我们EntityManager创建了权限UnitOfWorkxxxPersister对象,我们就可以使用中的find方法了EntityRepository.

更新4

我发现了一个新功能是为开发原则:自定义的,持久(另见).这应该可以更容易地使用自定义持久性类.我还不知道它是否能让我们创建一个非DB持久性,但看起来很有希望.但是,最后的更新是在八月,所以我不确定它是否还在积极开发中.

Jen*_*och 11

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

简而言之:Doctrine2(本地数据库) - > Rest客户端(实体到休息映射) - >请求(目标服务器)

Doctrine/Rest还提供了另一种方式:Doctrine Rest Server,通过REST公开本地实体(向服务器发出请求).但这不是你想要的.

  • 我越是想到这个问题,我就越会说REST是一个控制器和服务层的东西而且不属于模型.如果你想将模型/实体与REST结合起来,那么编写一个自定义的RestEntityManager并注入一个RestService层(你也可以在控制器中用来做REST请求)和一个EntityToRestMapper(用于构建其余的路由) - 如果你是不用路线信息注释您的实体. (2认同)

Tob*_*ias 8

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

假设我们已将值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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Nic*_*tel 3

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

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