7

私は Symfony 2.1 アプリケーションを使用しており、POSTリクエストを介して送信される多くのパラメーターがあり、各リクエストパラメーターを取得してエンティティクラスに入力するよりスマートな方法を探しています。リクエストパラメータ$entity->setMyParam($my_param)の式を書かないようにしています。nたとえば、これは私のエンティティのスニペットです:

namespace Brea\ApiBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Brea\ApiBundle\Entity\Distributions
 *
 * @ORM\Table(name="distributions")
 * @ORM\Entity
 */
class Distributions
{
  /**
   * @var string $recordType
   *
   * @ORM\Column(name="record_type", type="string", nullable=false)
   * @Assert\NotBlank()
   * @Assert\Choice(choices = {"a", "b", "c", "d", "e"}, message = "Choose a valid record type")
   */
  private $recordType;

  /**
   * Set recordType
   *
   * @param string $recordType
   */
  public function setRecordType($recordType)
  {
    $this->recordType = $recordType;
  }

  /**
   * Get recordType
   *
   * @return string 
   */
  public function getRecordType()
  {
    return $this->recordType;
  }
}

私のコントローラーは、各リクエストを受け取り、パラメーターをキャメルケース化し、リクエストパラメーターの値をエンティティに設定しようとします。

public function createRecordAction(Request $request, $id)
{
  $distribution = new Distributions();
  $params = $request->request;

  foreach ($request->request->all() as $param=>$value)
  {
    if ($param == "_method")
      continue;

    $function = "set".str_replace(' ','',ucwords(preg_replace('/[^A-Z^a-z^0-9]+/',' ',$param)));
    $distribution->$function($value);
  }
}

それは機能しますが、このアプローチについての私の懸念は、同様のことを行うすべてのコントローラーでこのコードを実行する必要があるということです。コードの重複を避ける方法として親クラスにリファクタリングすることはできますが、これが良い方法かどうか知りたいです。Symfony フレームワークで既にこれを行っているものを探しましたが、見つかったのはリクエストをフォームにバインドする例だけでした。

4

2 に答える 2

1

まず第一に:警告!!

前にコメントしたように、元の投稿で提供されたコードの使用には非常に注意しますPOST。これは、リクエストからのデータであると言ったためです。つまり、クライアントはあらゆる種類のデータを挿入し、あなたが望んでいない関数を呼び出すことができます。オブジェクトを削除します (または、存在しない関数名を送信して、スクリプトでエラーを引き起こすだけです)。

私は実際に最初に結論を読むだろう..! :) 次に、Alt に戻ります。1 & 2。


代替案 1:

そうは言っても、問題の別の解決策は、オブジェクトに独自のデータをフェッチする責任を与えることです。オブジェクトが十分に細分化されていれば、コードが肥大化することはありません。各クラスで、検索するパラメーターと呼び出す関数を定義できます (また、クラスに変更を加えたときに変更をローカライズします)。

class BookInformation{
  private $publisher;
  private $name;
  private $price;

  public static createFromRequest($req){
    $publisher = Publisher::createFromRequest($req);
    $book = new BookInformation($publisher, $req['book_name'], $req['book_price']);
    $book->setABC($req['abc']);
    //...
    return $book;
  }

  public __construct($publisher, $name, $price){
    //...
  }
}

class Publisher{
  private $name;
  private $address;

  public static createFromRequest($req){
    return new Publisher($req['publisher_name'], $req['publisher_address']);
  }

  public __construct($name, $address){
    //...
  }
}

前に述べたように、このメソッドの大きな利点の 1 つは、それらのクラスのいずれかに新しい属性を追加する必要がある場合、コントローラーをまったく編集する必要がなく、「リクエスト メソッドからの初期化」を編集するだけでよいことです。今後の変更は、変更されたクラスにローカライズされます。

もちろん、ユーザー リクエストから送信されたデータを検証することを忘れないでください (ただし、これは常識です)。


代替案 2:

最初の選択肢はFactory パターン(GoF の Abstract Factory に基づく)に非常に似ており、そのパターンを使用してソリューションを実装することもできることに注意してください。

class BookFactory{
  public createBookInformation($req){
    $publisher = $this->createPublisher($req);
    $book = new BookInformation($publisher, $req['book_name'], $req['book_price']);
    $book->setABC($req['abc']);
    //...
    return $book;
  }

  public createPublisher($req){
    return new Publisher($req['publisher_name'], $req['publisher_address']);
  }

  //createAnythingRelatedToBooks($req)...
}

そうすれば、要求オブジェクトに基づいて特定のオブジェクトファミリーを初期化することだけが責任を持つ、非常にまとまりのあるクラスにすべての初期化手順があります (これは非常に良いことです)。ただし、これらのクラスのいずれかに属性を追加する場合は、適切な Factory メソッドも編集する必要があります。


結論

これらの 2 つの代替案は、実際には代替案ではないことに注意してください...これらは、最初のコード (特に Factory コード) と一緒に使用できます。それらは実際には最後の問題 (「コードをどこに置くか」の問題) を解決するだけです。

ただし、POSTリクエストをサニタイズして、(前述のように) 注釈付きの関数のみを呼び出す場合でも、より複雑なビジネス ルールでは設計がかなり早く台無しになると感じているため、実際にはお勧めしません (しかし、おそらくあなたはすでにすべてカバーされています(?))。つまり、初期化プロセスにビジネス ルールを簡単に組み込むことはできないと思います。これは、すべて自動化されているためです (あらゆる種類の値である可能性があるため、値の検証を行うことはできません)。初期化後に「元に戻す」ものになります(個人的には嫌いです..エラーの余地がたくさんあります)!

たとえば、代替案 1 (BookInformationおよびPublisher) で同じ 2 つのクラスを取り上げます。

は、データベースに既に登録されており、アドレスが確認されている場合にBookのみ持つことができます(新しい出版社は、別のインターフェイスを使用して作成し、書籍にリンクする前にアドレスを確認する必要があります)。PublisherPublisher

それ以外の場合は、要求データに関係なく、publisherXYZ に設定する必要があります。この種のルールをサポートするには、実際にオブジェクトを(自動的に)構築し、特定のルールに一致しない場合は属性破棄/再割り当てする必要があると感じています(私は間違っているかもしれません) 。publisherこれらのPublisherオブジェクトのプールがメモリ内にある場合は、そのプールで誤って作成されたオブジェクトを削除することも忘れないでくださいPublisher。そして、それはたった1つのルールです!

その問題を「修正」するためにコードでできることの 1 つは、各セッター (validXYZ()) に対して検証メソッドを用意することですが、検証が他のメソッドに依存している場合、すぐにバラバラになってしまう設計のように見え始めています。オブジェクト/データ...

そのコードの使用を思いとどまらせるようなことは他にありませんが、使用する場合は、1 年か 2 年後 (メンテナンスや新しい機能が追加された後など) に、それがどのように機能するかについて最新情報を提供してください. .)。

于 2012-12-28T16:12:15.957 に答える
1

Symfony フレームワークで既にこれを行っているものを探しましたが、見つかったのはリクエストをフォームにバインドする例だけでした。

これには s を使用Formします。HTTP リクエストが HTMl フォームから実行されない場合でもRequest、フォーム インスタンスにバインドするだけで済みます。これにより、すべてのデータ インジェクションと検証が処理されます。

さらに、HTML フォームが必要になった場合に備えて、用意しておきましょう ^^.

于 2012-12-28T17:05:14.007 に答える