web-dev-qa-db-ja.com

REST 2を使用して、DBではなくDoctrineAPIにエンティティを保存します

これは私の他の質問に関連しています: REST APIを使用してエンティティを永続化する

Symfony2のプロジェクトの場合、エンティティを永続化できる必要がありますリモート(サードパーティ)RESTfulAPIを使用。また、そのAPIからデータを使用してエンティティを取得できるようにしたいです。

つまり、私のオブジェクトはサードパーティのデータベースに保存されます。それらは自分のデータベースに保存されていません。データを保存したり、データを検索したりする必要があるときはいつでも、REST APIを使用します。

Doctrine自体によって作成されたもの を含むいくつかのライブラリを指摘されました。しかし、それらのどれも私が探しているものを私に提供しません。 Doctrineによって作成されたものが最良のオプションですが、Active Recordパターンを使用し、すべての甘いDoctrine 2のものを提供するわけではありません。誤解しないでください。私は長い間ActiveRecordの実装を使用してきましたが、今では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と通信しようとしているだけであることに注意してください。私はDoctrineに比較的慣れていませんが、今のところ気に入っています。永続性ロジックをエンティティから分離するというアイデアは本当に気に入っていますが、これを使用してAPIを使用してエンティティを保存する方法がわかりません。

Symfonyのドキュメントには、 複数のエンティティマネージャーを操作する方法 について説明している記事があります。これに似たソリューションを探していますが、DBの代わりにRESTを使用できるエンティティマネージャーを使用しています。

DoctrineのORMを自分で微調整しようとしていますが、データベース固有のロジックと緊密に結合されているため、コードの半分しか書き直せません。もちろん、私は愚かなことをしているかもしれません。

だから私の質問は、DoctrineのORMのデータベース固有の部分をカスタムのものに置き換える/オーバーライドする方法はありますか?すべての永続化メソッドに共通するはずの多くのことを書き直さずに?以前に行われたことがありますか?それとも、Doctrineはデータベースでの使用を目的としており、他の用途には十分な柔軟性がないため、単純に不可能ですか?

私自身の進歩

CakePHPは、カスタム DataSource を定義できるようにすることで、これを実行できるようです。このようにして、SQLデータベースを使用してモデルを保存できますが、API、セッションなども使用できます。ほぼ同じことを行いますが、CakePHPの代わりにDoctrineを使用します。

アップデート1

実際のデータベースクエリは _Doctrine\ORM\Persisters\BasicEntityPersister_ class によって実行されているようです。さまざまなタイプの継承を処理するために、他にもいくつかのxxxPersisterクラスがあります。 xxxPersisterクラスを独自のクラスに置き換えることができる場合があるため、DBコードをREST APIコードに置き換えることができます。

パーシスタオブジェクトは、 _Doctrine\ORM\UnitOfWork_ クラスのgetEntityPersister()メソッド内に作成されます。クラス名はハードコードされているため、独自のパーシスターを使用する場合は、_Doctrine\ORM\UnitOfWork_をオーバーライドする必要があります。

アップデート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);
_

つまり、EntityManagerを取得して適切なUnitOfWorkオブジェクトとxxxPersisterオブジェクトを作成すると、findEntityRepositoryメソッドを使用できるようになります。

アップデート4

Doctrine用に新しい機能が開発されていることを発見しました: カスタムパーシスターthis も参照)。これにより、カスタムパーシスタークラスを使いやすくなります。 DB以外のパーシスターを作成できるかどうかはまだわかりませんが、有望に見えます。ただし、最後の更新は8月であったため、まだ活発に開発されているかどうかはわかりません。

26
Nic Wortel

すぐに使えるソリューションが利用できなかったので、私は自分で書くことにしました。私はそれを [〜#〜] rapl [〜#〜] と呼んだ。これは、DoctrineのORMに大きく影響を受けています(実際、Doctrine Common)によって提供されるインターフェイスの多くを使用しています)。

RAPLを使用すると、小さなYAMLファイルを記述して、エンティティとWebサービス間のマッピングを構成できるため、カスタムEntityManagerを使用してエンティティを永続化/取得できます。

3
Nic Wortel

https://github.com/doctrine/rest を使用して、ターゲットサーバーと通信するRESTクライアントを構築します。ここで重要な部分はマッピングです。エンティティ(ローカル)からREST API(ターゲット)へ。

つまり、Doctrine2(ローカルDB)-> Restクライアント(エンティティからRESTマッピング)->リクエスト(ターゲットサーバー)

Doctrine/Restは、その逆も提供します。aDoctrine Rest Server、REST(サーバーへのリクエスト)を介してローカルエンティティを公開します。しかし、そうではありません。あなたが探しているもの。

10
Jens A. Koch

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:
 *
 * @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;
    }
}

製品エンティティの@Tableアノテーションに値 http://www.yourSite.com/api/products を使用したと仮定します。

コントローラ:

<?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またはネイティブクエリを使用することもできます。

8
Tobias

私はあなたが正しい方法ではないと思います。
今はドキュメントを掘り下げる準備ができていませんが、doctrineスタックは次のように理解しています:

ORM-> DQL(ドクトリンクエリ言語)-> dbal->いくつかのデータベースSQL

そして、カスタムデータベースドライバーとしてDBALで機能する実装のポイント。

共通のREST-Driverは本当に興味深い機能を作成し、サードパーティのサービスと簡単に統合できると思います。

2
nonlux

よくわかりませんが、エンティティがRESTを介して永続化ロジックを実行するために ライフサイクルコールバックイベント を使用してみることができます。

1
lisachenko

同様のことをしたかったので、doctrineエンティティをRESTfulリソースとして公開するのに役立つこのライブラリを構築しました。かなりの量の機能があり、必要なものを正確に定義できます。プル(GET)メソッドとプッシュ(POST/PUT/PATCH)メソッドの両方を介して公開されます。

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

https://github.com/leedavis81/drest

それが役に立てば幸い

0
Lee Davis