ops-talent/apibundle

基本的RESTful API包 + oauth2

安装次数: 1,055

依赖项: 0

建议者: 0

安全性: 0

星星: 1

关注者: 1

分支: 0

开放性问题: 5

类型:symfony-bundle

1.0.4 2019-07-29 09:57 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个表单 AddTypeEditTypeFilterType。我们建议将添加表单和过滤表单分开。

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,并且 rolesROLE_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]

推荐安全包

opstalent/security-bundle

许可证

此包受MIT许可证的约束。请参阅包中的完整许可证。

许可证