Symfony2 API開発の考慮事項

Symfony2でモバイルアプリケーションとサイト用のAPIを開発してきた短いキャリアのすべてが判明しました。 誰かに明らかであると思われる新しい知識を発見するたびに、誰かが多くの時間を節約するのを助けます。 この知識について説明します。



フォーム



一般に、APIにデフォルトのフォームを使用することはお勧めできませんが、それでも決定する場合は、いくつかの機能を覚えておく必要があります。 最初に、symfonyのフォームはフロントエンドとバックエンドが組み合わされた通常のサイト用に作成されました。



最初の問題は、 エンティティタイプで発生します 。 フォームでエンティティタイプを使用するメソッドにリクエストを送信する場合、最初に指定されたクラスのすべてのエンティティを取得し、次に送信されたIDで目的のエンティティを取得するリクエストのみを取得します。 多くの人はこれについて知らず、なぜこの方法がこれほど長く機能するのか、非常に驚​​いています。



ソリューション例
EntityType.php

<?php namespace App\CommonBundle\Form\Type; use App\CommonBundle\Form\DataTransformer\EntityDataTransformer; use Doctrine\ORM\EntityManager; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface; class EntityType extends AbstractType { private $em; public function __construct(EntityManager $em) { $this->em = $em; } public function setDefaultOptions(OptionsResolverInterface $resolver) { $resolver->setDefaults([ 'field' => 'id', 'class' => null, 'compound' => false ]); $resolver->setRequired([ 'class', ]); } public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addModelTransformer(new EntityDataTransformer($this->em, $options['class'], $options['field'])); } public function getName() { return 'entity'; } }
      
      







EntityDataTransformer.php

 <?php namespace App\CommonBundle\Form\DataTransformer; use Doctrine\ORM\EntityManager; use Symfony\Component\Form\DataTransformerInterface; class EntityDataTransformer implements DataTransformerInterface { private $em; private $entityName; private $fieldName; public function __construct(EntityManager $em, $entityName, $fieldName) { $this->em = $em; $this->entityName = $entityName; $this->fieldName = $fieldName; } public function transform($value) { return null; } public function reverseTransform($value) { if (!$value) { return null; } return $this->em->getRepository($this->entityName)->findOneBy([$this->fieldName => $value]); } }
      
      







services.yml

  common.form.type.entity: class: App\CommonBundle\Form\Type\EntityType arguments: [@doctrine.orm.entity_manager] tags: - { name: form.type, alias: entity }
      
      









2番目の問題は、ブール値に使用しようとするチェックボックスタイプで発生しますが、このタイプの作業の特性は、キーが存在し、空でない場合はtrueが返されることです。



ソリューション例
BooleanType.php

 <?php namespace App\CommonBundle\Form\Type; use App\CommonBundle\Form\DataTransformer\BooleanDataTransformer; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; class BooleanType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options) { $builder->addViewTransformer(new BooleanDataTransformer()); } public function getParent() { return 'text'; } public function getName() { return 'boolean'; } }
      
      







BooleanDataTransformer.php

 <?php namespace App\CommonBundle\Form\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; class BooleanDataTransformer implements DataTransformerInterface { public function transform($value) { return null; } public function reverseTransform($value) { if ($value === "false" || $value === "0" || $value === "" || $value === 0) { return false; } return true; } }
      
      







services.yml

  common.form.type.boolean: class: App\CommonBundle\Form\Type\BooleanType tags: - { name: form.type, alias: boolean }
      
      









JMSシリアライザー



APIの作成に関するすべての記事は、この特定のすばらしい拡張機能を推奨しています。 エンティティは詳細とリストの2つのシリアル化グループがあり、各エンティティがこれらの名前を使用し始め、グループがまったく同じ名前の関連エンティティに出会うまですべてが正常に機能する単純な例を見てみましょう。多くの不必要な、不必要な情報。 また、両方のモデルが相互に通信を表示する場合、シリアル化中に無限ループにつながる可能性があります。



誤用の例
News.php

 <?php use JMS\Serializer\Annotation as Serialization; class News { /** * @Serialization\Groups({"details", "list"}) */ protected $id; /** * @Serialization\Groups({"details", "list"}) */ protected $title; /** * @Serialization\Groups({"details", "list"}) */ protected $text; /** *    User * * @Serialization\Groups({"details", "list"}) */ protected $author; }
      
      







User.php

 <?php use JMS\Serializer\Annotation as Serialization; class User { /** * @Serialization\Groups({"details", "list"}) */ protected $id; /** * @Serialization\Groups({"details", "list"}) */ protected $name; /**      list  details */ }
      
      







NewsController.php

 <?php class NewsController extends BaseController { /** * @SerializationGroups({"details"}) * @Route("/news/{id}", requirements={"id": "\d+"}) */ public function detailsAction(Common\Entity\News $entity) { return $entity; } }
      
      











この例は、ニュースを受信すると、作成者フィールドに、詳細グループを持つユーザー内のすべてのフィールドが含まれることを示していますが、これは明らかに計画の一部ではありません。 明らかに、これはできないように思えますが、驚いたことに、多くの人がそうしています。



グループに%entity_name%_details、%entity_name%_list、%entity_name%_embedとして名前を付けることをお勧めします。 後者は、関連するエンティティがあり、リストに何らかの関連するエンティティを表示したい場合にのみ必要です。



適切な使用例
News.php

 <?php use JMS\Serializer\Annotation as Serialization; class News { /** * @Serialization\Groups({"news_details", "news_list"}) */ protected $id; /** * @Serialization\Groups({"news_details", "news_list"}) */ protected $title; /** * @Serialization\Groups({"news_details", "news_list"}) */ protected $text; /** *    User * * @Serialization\Groups({"news_details", "news_list"}) */ protected $author; }
      
      







User.php

 <?php use JMS\Serializer\Annotation as Serialization; class User { /** * @Serialization\Groups({"user_details", "user_list", "user_embed"}) */ protected $id; /** * @Serialization\Groups({"user_details", "user_list", "user_embed"}) */ protected $name; /**   ,    user_list  user_details */ }
      
      







NewsController.php

 <?php class NewsController extends BaseController { /** * @SerializationGroups({"news_details", "user_embed"}) * @Route("/news/{id}", requirements={"id": "\d+"}) */ public function detailsAction(Common\Entity\News $entity) { return $entity; } }
      
      











このアプローチでは、必要なフィールドのみが存在し、さらに、ユーザーに関する簡単な情報を表示する必要がある他の場所でも使用できます。



終わり



実際、このようなヒントはまだたくさんあります。興味があれば、喜んで共有します。



All Articles