braune-digital / api-base-bundle
API包
Requires
- friendsofsymfony/rest-bundle: ^2.0
- nelmio/cors-bundle: ^1.4.0
- symfony/security: ~3.0
- white-october/pagerfanta-bundle: ^1.0.0
Suggests
- jms/serializer-bundle: Used for better serialization control
- nelmio/api-doc-bundle: Used for nice and simple Api-Documentation
README
此Symfony-Bundle使用FOS Rest并提供基本的API功能
特性
- BaseApiController:API控制器的基石
- ApiKey认证:使用API令牌验证用户
- 分页
- 查询过滤:过滤列表(即将推出)
- 模块访问:将API分割成模块并限制对特定用户角色的访问
- 响应中的自定义配置:向特定响应添加自定义配置
需求
- FOSRestBundle
- WhiteOctoberPagerFantaBundle
- FOSUserBundle(目前)
- JMSSerializerBundle(可选)
安装
使用composer下载
composer require braune-digital/api-base-bundle "1.*"
并在你的AppKernel中启用Bundle。
你也可以不注册Bundle就使用BaseApiController。
public function registerBundles() { $bundles = array( ... new JMS\SerializerBundle\JMSSerializerBundle(), new FOS\RestBundle\FOSRestBundle(), new BrauneDigital\ApiBaseBundle\BrauneDigitalApiBaseBundle(), new Nelmio\CorsBundle\NelmioCorsBundle(), ... );
配置
默认配置
braune_digital_api_base: modules: ~ #Used for Module-Access timeout: 0 # Timeout for Api-Tokens (use 0 for no timeout) configuration: # Your configuration to be send
FOSRest配置
fos_rest:
disable_csrf_role: ROLE_API
param_fetcher_listener: true
body_listener:
array_normalizer: fos_rest.normalizer.camel_keys
format_listener: true
view:
view_response_listener: force
exception_wrapper_handler: 'BrauneDigital\ApiBaseBundle\View\ExceptionWrapperHandler'
routing_loader:
default_format: json
body_converter:
enabled: true
validate: true
exception:
codes:
'Doctrine\ORM\EntityNotFoundException': 403
messages:
'Doctrine\ORM\EntityNotFoundException': false
NelmioCors配置
以支持来自客户端的OPTIONS调用。
nelmio_cors:
defaults:
allow_credentials: false
allow_origin: []
allow_headers: []
allow_methods: []
expose_headers: []
max_age: 0
hosts: []
origin_regex: false
paths:
'^/api/':
allow_credentials: true
allow_origin: ['*']
allow_headers: ['*']
allow_methods: ['POST', 'PUT', 'GET', 'DELETE', 'OPTIONS']
max_age: 0
Security.yml配置
providers:
braune_digital_api_base:
id: braune_digital_api_base.security.apikey_user_provider
firewalls:
api_doc: #Open API Documentation
pattern: ^/api/doc
anonymous: true
security: false
api_login:
pattern: ^/api/v1/login$
anonymous: true
api_password_reset:
pattern: ^/api/v1/password-((request$)|(reset$))
anonymous: true
api: #Secured API-Area
pattern: ^/api
stateless: true #we are using tokens
simple_preauth:
authenticator: braune_digital_api_base.security.apikey_authenticator #use apikeys for authentication
provider: braune_digital_api_base #use apikeys for authentication
使用
BaseApiController
BaseApiController提供底层逻辑以快速轻松地创建API端点:只需扩展BrauneDigital\ApiBaseBundle\Controller\BaseApiController并添加你的函数
<?php namespace BrauneDigital\DemoBundle\Controller\V1; use BrauneDigital\ApiBaseBundle\Controller\BaseApiController; use BrauneDigital\DemoBundle\Form\Type\ProjectType; use BrauneDigital\Pitcher\BaseBundle\Entity\Company; use BrauneDigital\Pitcher\BaseBundle\Entity\Project; use Doctrine\Common\Collections\ArrayCollection; use Nelmio\ApiDocBundle\Annotation\ApiDoc; use FOS\RestBundle\Controller\Annotations as Rest; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Symfony\Component\HttpFoundation\Request; /** * @author Patrick Rathje <pr@braune-digital.com> * @copyright 2016 Braune Digital GmbH */ class ProjectController extends BaseApiController { protected function getRepository() { return $this->getDoctrine()->getRepository('BrauneDigitalDemoBundle:Project'); } /** * * @ApiDoc( * resource=false, * section="Project", * description="Get a project by id", * requirements= { * {"name": "id", "description":"Project-ID", "dataType": "integer"}, * {"name": "_format", "description":"Response-Format", "requirement": "json|xml|html", "dataType": "string"}, * {"name": "version", "description":"API-Version", "requirement": "json|xml|html", "dataType": "integer"} * } *) * * @param Request $request * @param $id * @return \FOS\RestBundle\View\View * * @Rest\Get("/projects/{id}", name="project_read", defaults={"_format": "json"}) */ public function readAction(Request $request, $id) { return parent::readAction($request, $id); } /** * * @ApiDoc( * resource=false, * section="Project", * description="Get projects", * requirements= { * {"name": "_format", "description":"Response-Format", "requirement": "json|xml|html", "dataType": "string"}, * {"name": "version", "description":"API-Version", "requirement": "json|xml|html", "dataType": "integer"} * } *) * * @param Request $request * @param $id * @return \FOS\RestBundle\View\View * * @Rest\Get("/projects", name="project_list", defaults={"_format": "json"}) */ public function listAction(Request $request) { return parent::listAction($request); } /** * * @ApiDoc( * resource=false, * section="Project", * description="Create a project", * requirements= { * {"name": "_format", "description":"Response-Format", "requirement": "json|xml|html", "dataType": "string"}, * {"name": "version", "description":"API-Version", "requirement": "json|xml|html", "dataType": "integer"} * }, * input="BrauneDigital\DemoBundle\Form\Type\ProjectType" *) * * @param Request $request * @param $id * @return \FOS\RestBundle\View\View * * @Rest\Post("/projects", name="project_create", defaults={"_format": "json"}) */ public function createAction(Request $request, $entity = null, $refresh = false, $formOptions = null) { $entity = new Project(); return parent::createAction($request, $entity); } /** * * @ApiDoc( * resource=false, * section="Project", * description="Update a project", * requirements= { * {"name": "id", "description":"Project-ID", "dataType": "integer"}, * {"name": "_format", "description":"Response-Format", "requirement": "json|xml|html", "dataType": "string"}, * {"name": "version", "description":"API-Version", "requirement": "json|xml|html", "dataType": "integer"} * }, * input="BrauneDigital\DemoBundle\Form\Type\ProjectType" *) * * @param Request $request * @param $id * @return \FOS\RestBundle\View\View * * @Rest\Post("/projects/{id}", name="project_update", defaults={"_format": "json"}) */ public function updateAction(Request $request, $id, $refresh = false, $formOptions = null) { //add validation groups return parent::updateAction($request, $id, false, array('validation_groups' => array('ProjectUpdate'))); } /** * * @ApiDoc( * resource=false, * section="Project", * description="Delete a project", * requirements= { * {"name": "id", "description":"Project-ID", "dataType": "integer"}, * {"name": "_format", "description":"Response-Format", "requirement": "json|xml|html", "dataType": "string"}, * {"name": "version", "description":"API-Version", "requirement": "json|xml|html", "dataType": "integer"} * } *) * * @param Request $request * @param $id * @return \FOS\RestBundle\View\View * * @Rest\Delete("/projects/{id}", name="project_delete", defaults={"_format": "json"}) */ public function deleteAction(Request $request, $id) { return parent::deleteAction($request, $id); } /** * Override the getForm Method for create and update functionalities, you may want to return different forms accordings to the mode **/ protected function getForm($entity, $mode = '', $options = array()) { return $this->createForm(new ProjectType(), $entity, $options); } }
你必须指定一个仓库,如果你想要创建或更新实体,你可能需要重写getForm
函数。
安全系统
为了限制对单个资源的访问,你需要使用symfony投票者。查看BrauneDigital\ApiBaseBundle\Security\Authorization\Voter\BaseCrudVoter,它指定了用于对应路由的属性。
过滤列表操作
要过滤列表操作,可以重写createListQueryBuilder($alias = 'e')
方法。可以在返回之前自定义querybuilder。
序列化组(需要JMSSerializerBundle)
序列化组由JMS Serializer用于更好地控制序列化过程。
在你的控制器中
你可以轻松使用$this->addSerializationGroup($group)
添加序列化组,或者通过调用$this->serializationGroups($groups)
设置它们。
使用API请求头
客户端还可以通过在请求中设置serializationGroups
头来自定义序列化组。该头可以是简单的字符串,逗号分隔的字符串或字符串数组。
ApiKey认证
为了使用API令牌,你必须向你的User-Class添加令牌
protected $token; /** * @return mixed */ public function getToken() { return $this->token; } /** * @param mixed $token */ public function setToken($token) { $this->token = $token; }
并添加你的DB映射(例如,DoctrineORM)
fields: token: type: string nullable: true
模块访问
此Bundle提供了一个模块访问注解,可以用于限制特定路由的访问权限。与Symfony投票系统相比,这基于API端点而不是资源。导入注解
use BrauneDigital\ApiBaseBundle\Annotation\ModuleAccess;
添加你的模块
@ModuleAccess({"products", "sales"})
或者如果你只使用一个模块
@ModuleAccess("products")
如果用户有权访问其中一个模块,则将授予访问权限。在配置中定义模块
braune_digital_api_base: modules: products: roles: ['ROLE_ADMIN', 'ROLE_CLIENT'] sales: roles: ['ROLE_SALESMAN']
示例
/** * @ApiDoc( * resource=false, * section="Your Section", * description="A nice description", * requirements= { * {"name": "_format", "description":"Response-Format", "requirement": "json|xml|html", "dataType": "string"} * } *) * @Rest\Get("/products") * @ModuleAccess({"products", "sales"}) * @param Request $request * @return mixed */ public function listAction(Request $request) { return parent::listAction($request); }
变量配置
你可以在请求中设置_braune_digital_api_base_config属性以将你的自定义配置(braune_digital_api_base.configuration)添加到响应中
//send configuration $request->attributes->set('_braune_digital_api_base_config', true);
该配置将在响应中的configuration键下可用。
建议
API文档
我们建议使用NelmioApiDocBundle来生成干净且易于使用的API文档。
JMSSerializerBundle
待办事项
- 在初始化服务之前检查FOSUserBundle