ops-talent / apibundle
基本的RESTful API包 + oauth2
Requires
- doctrine/annotations: ^1.4
- doctrine/common: ^2.7
- doctrine/orm: ^2.5
- friendsofsymfony/oauth-server-bundle: ^1.5
- friendsofsymfony/user-bundle: ~2.0@dev
- symfony/dependency-injection: ^3.2
- symfony/event-dispatcher: ^3.2
- symfony/form: ^3.2
- symfony/http-foundation: ^3.2
- symfony/http-kernel: ^3.2
- symfony/property-access: ^3.2
- symfony/property-info: ^3.2
- symfony/routing: ^3.2
- symfony/security: ^3.2
- symfony/serializer: ^3.2
Requires (Dev)
- mockery/mockery: ^0.9.9
- phpunit/phpunit: ^5.7
Suggests
- friendsofsymfony/oauth-server-bundle: ^1.5
- friendsofsymfony/user-bundle: ~2.0@dev
This package is not auto-updated.
Last update: 2024-09-29 05:21:11 UTC
README
此包提供各种工具,用于快速开发使用Symfony 3.x的RESTful API和应用程序。
特性
- 端点生成器(只需要实体)
- JSON响应
- 异常处理
- OAuth2 - friendsofsymfony/oauth-server-bundle
安装
composer require opstalent/apibundle "dev-master
包依赖
"friendsofsymfony/oauth-server-bundle": "^1.5", "friendsofsymfony/user-bundle": "~2.0@dev"
手动配置
1. 创建实体
<?php namespace AppBundle\Entity; use AppBundle\Serializer\Annotation as AppSerializer; use Doctrine\ORM\Mapping as ORM; use Gedmo\Mapping\Annotation as Gedmo; use Symfony\Component\Serializer\Annotation as Serializer; use Symfony\Component\Validator\Constraints as Assert; /** * Class Page * @package AppBundle\Entity * * @ORM\Entity(repositoryClass="AppBundle\Repository\PageRepository") * @ORM\Table(name="pages") * @ORM\Table(indexes={@ORM\Index(name="slug_idx", columns={"slug"})}) */ class Page { /** * @ORM\Id * @ORM\Column(type="integer") * @ORM\GeneratedValue(strategy="AUTO") * @Serializer\Groups({"show", "update", "list"}) */ protected $id; /** * @var string * @ORM\Column(type="string") * @Assert\NotBlank() * @Assert\Length( * min = 1, * max = 128, * minMessage = "Your Title must be at least {{ limit }} digits long", * maxMessage = "Your Title cannot be longer than {{ limit }} digits" * ) * @Serializer\Groups({"show", "update", "list"}) */ protected $title; /** * @var string * @Gedmo\Slug(fields={"title"}) * @ORM\Column(length=128, unique=true) * @Serializer\Groups({"show", "update", "list"}) */ protected $slug; /** * @var string * @ORM\Column(type="text", nullable=true) * @Serializer\Groups({"show", "update", "list"}) */ protected $body; /** * @var array * @ORM\Column(type="array", nullable=true) * @Serializer\Groups({"show", "update", "list"}) */ protected $data; /** * @var \DateTime $created * * @Gedmo\Timestampable(on="create") * @ORM\Column(type="datetime") * @Serializer\Groups({"show", "update", "list"}) */ protected $created; /** * @var \DateTime $updated * * @Gedmo\Timestampable(on="update") * @ORM\Column(type="datetime") * @Serializer\Groups({"show", "update", "list"}) */ protected $updated; /** * @return mixed */ public function getId() { return $this->id; } /** * @param mixed $id * @return Page */ public function setId($id) { $this->id = $id; return $this; } /** * @return \DateTime */ public function getCreated(): \DateTime { return $this->created; } /** * @param \DateTime $created * @return Page */ public function setCreated(\DateTime $created): Page { $this->created = $created; return $this; } /** * @return \DateTime */ public function getUpdated(): \DateTime { return $this->updated; } /** * @param \DateTime $updated * @return Page */ public function setUpdated(\DateTime $updated): Page { $this->updated = $updated; return $this; } public function getSlug() { return $this->slug; } /** * @param string $slug */ public function setSlug(string $slug) { $this->slug = $slug; } /** * @return string */ public function getTitle() { return $this->title; } /** * @param string $title * @return Page */ public function setTitle(string $title): Page { $this->title = $title; return $this; } /** * @return string */ public function getBody() { return $this->body; } /** * @param string $body * @return Page */ public function setBody($body): Page { $this->body = $body; return $this; } /** * @return array */ public function getData() { return $this->data; } /** * @param array $data */ public function setData($data) { $this->data = $data; return $this; } }
2. 创建仓库并将其注册为服务
仓库应扩展 Opstalent\ApiBundle\Repository\BaseRepository
<?php namespace AppBundle\Repository; use Doctrine\ORM\QueryBuilder; use Opstalent\ApiBundle\Repository\BaseRepository; class PageRepository extends BaseRepository { protected $filters = []; protected $repositoryName='AppBundle:Page'; protected $repositoryAlias='page'; protected $entityName='\AppBundle\Entity\Page'; }
我们需要在service.yml中将仓库注册为服务
parameters: entity.user: AppBundle\Entity\User services: repository.user: class: AppBundle\Repository\UserRepository factory: ['@doctrine', getRepository] arguments: ['%entity.user%'] calls: [[setEventDispatcher, ['@event_dispatcher']]]
3. 创建表单
生成器会生成3个表单 AddType
、EditType
和 FilterType
。我们建议将添加表单和过滤表单分开。
AddType
<?php namespace AppBundle\Form\Page; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use AppBundle\Entity\Page; class AddType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('title', TextType::class, ['required' => true, 'mapped' => true]) ->add('slug', TextType::class, ['required' => true, 'mapped' => true]) ->add('body', TextType::class, ['required' => false, 'mapped' => true]) ->add('data', TextType::class, ['required' => false, 'mapped' => true]) ->add('created', DateTimeType::class, ['required' => true, 'mapped' => true, 'widget' => 'single_text', 'format' => 'yyyy-MM-dd']) ->add('updated', DateTimeType::class, ['required' => true, 'mapped' => true, 'widget' => 'single_text', 'format' => 'yyyy-MM-dd']); } public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => Page::class, ]); } }
EditType
<?php namespace AppBundle\Form\Page; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; class EditType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { parent::buildForm($builder, $options); $builder->setMethod("PUT"); /** @var FormBuilderInterface $field */ foreach ($builder->all() as $field) { $field->setRequired(false); } } }
FilterType
<?php namespace AppBundle\Form\Page; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; class FilterType extends AbstractType { /** * {@inheritdoc} */ public function buildForm(FormBuilderInterface $builder, array $options) { $builder->add('title', TextType::class, ['required' => false, 'mapped' => false]) ->add('slug', TextType::class, ['required' => false, 'mapped' => false]) ->add('body', TextType::class, ['required' => false, 'mapped' => false]) ->add('data', TextType::class, ['required' => false, 'mapped' => false]) ->add('created', DateTimeType::class, ['required' => false, 'mapped' => false]) ->add('updated', DateTimeType::class, ['required' => false, 'mapped' => false]); } }
4. 创建路由
我们喜欢在单独的文件中定义端点的路由。
pages.yml
api_pages_list: path: /pages defaults: { _controller: 'OpstalentApiBundle:Action:list' } methods: [GET] options: { form: AppBundle\Form\Page\FilterType, serializerGroup: list, repository: '@repository.page', security: { secure: true, roles: [ROLE_SUPER_ADMIN] } } api_pages_get: path: '/pages/{id}' requirements: { id: \d+ } defaults: { _controller: 'OpstalentApiBundle:Action:get' } methods: [GET] options: { serializerGroup: get, repository: '@repository.page', security: { secure: true, roles: [ROLE_SUPER_ADMIN] } } api_pages_post: path: /pages defaults: { _controller: 'OpstalentApiBundle:Action:post' } methods: [POST] options: { form: AppBundle\Form\Page\AddType, serializerGroup: get, repository: '@repository.page', security: { secure: true, roles: [ROLE_SUPER_ADMIN] } } api_pages_put: path: '/pages/{id}' requirements: { id: \d+ } defaults: { _controller: 'OpstalentApiBundle:Action:put' } methods: [PUT] options: { form: AppBundle\Form\Page\EditType, serializerGroup: get, repository: '@repository.page', security: { secure: true, roles: [ROLE_SUPER_ADMIN] } } api_pages_delete: path: '/pages/{id}' requirements: { id: \d+ } defaults: { _controller: 'OpstalentApiBundle:Action:delete' } methods: [DELETE] options: { serializerGroup: get, repository: '@repository.page', security: { secure: true, roles: [ROLE_SUPER_ADMIN] } }
此文件同时由api-bundle和security-bundle使用。让我们仔细看看这一行,所有端点都使用控制器 OpstalentApiBundle:Action
,并依赖于操作,它将是 list, get, post, put, delete
。参数 methods: [GET]
是正常路由参数。unde选项中我们有自定义参数。
表单
定义应使用哪种表单类型,例如 AppBundle\Form\Page\FilterType
序列化组(可选)
定义在不同角色上应使用哪些序列化组。默认情况下,对于List操作是 list
,对于所有其他操作是 get
。如果我们想为此操作自定义serializedGroups,我们可以定义自己的表,例如
serializerGroups: all: "list" owner: "me" ROLE_SUPER_ADMIN: 'me'
所有选项都在操作无法匹配其他选项时使用,如果定义了参数 serializerGroups
,则它是必需的。选项owner用于检查登录用户是否是此数据的所有者,并且可以定义在所有操作(除list外)上。第三个值是角色,如果用户具有此角色(或更多),则获得所有匹配的序列化组。
仓库
用于定义此操作应使用哪个仓库,例如 '@repository.page'
安全性
安全性定义了我们想要如何保护我们的端点。对于所有操作,默认的 secure
选项是 true
,并且 roles
是 ROLE_SUPER_ADMIN
。还有一个第三个选项,其中我们定义此端点的事件。完整安全性示例
security: secure: true roles: [ROLE_USER] events: before.persist: 'owner'
生成器
生成器用于为我们生成上述所有文件。我们只需要实体。要运行生成器,我们需要输入
php bin/console app:generatecrude
附加选项
- entityPath - 为特定实体生成CRUD
- overwrite - 覆盖之前的配置
- actions - 定义应生成哪些端点操作。可用操作
[LIST,GET,POST,PUT,DELETE]
推荐安全包
许可证
此包受MIT许可证的约束。请参阅包中的完整许可证。