📜 ⬆️ ⬇️

REST API on Symfony, FOSRestBundle + GlavwebDatagridBundle

Hello! In the last article I talked about our experience in the REST API with the build on the FOSRestBundle + JMSSerializer . Today I will share our approach to developing a REST API on FOSRestBundle + GlavwebDatagridBundle.

New tasks.


With the growing number of REST projects, the requirements for our api increased. Often, our api users are third-party developers who have certain needs for their solutions. So, for example, not everyone liked to pass the configuration of nesting objects in requests. Filtration capabilities out of the box also lacked.

But the most weighty reason for revising the approach to the development of api were performance problems. The reasons for these problems were either not optimally written SQL, or additional costs for processing the result with doctrine and serializer.

If with SQL it is clear which way to optimize, then with the loss of performance in the doctrine and serializer is more difficult. Here is how it looks after the query to the database worked:
')
    PDO ->     ( , ) ->         jmsserializer ->    json  . 

It was decided to shorten this path to:

     PDO ->       ( , ) ->  ,     . 

Since we refused to hydrate into objects, we had to abandon the JMSSerializer too. JMSSerializer did a lot of useful things for our api (I described this in the previous article ). In addition, he performed another important work - reload nested entities, if they were not defined in the join-ah.

In order to close the resulting “hole” in the functionality resulting from the abandonment of the jmsserializer, GlavwebDatagridBundle was developed.

In a few words about GlavwebDatagridBundle


Briefly determine the purpose of the GlavwebDatagridBundle, you can: get data, formatted properly, on the basis of filters, limits and offset. Unclear? I know. And now about everything in order.

At the heart of the GlavwebDatagridBundle are the following components:

DatagridBuilder


DatagridBuilder forms QueryBuilder using DataSchema, filters and additional query parameters:

 $datagridBuilder = $this->get('glavweb_datagrid.doctrine_datagrid_builder'); $datagridBuilder ->setEntityClassName(Entity::class) ->setFirstResult(0) ->setMaxResults(100) ->setOrderings(['id'=>'DESC']) ->setOperators(['field1' => '=']) ->setDataSchema('entity.schema.yml', 'entity/list.yml') ; 

Next, it returns a Datagrid object:

 $datagrid = $datagridBuilder->build($paramFetcher->all()); 

Filter


Filters allow you to specify additional conditions in the query.

 // Define filters $datagridBuilder ->addFilter('field1') ->addFilter('field2') ; 

Datagrid


Datagrid gets QueryBuilder from DatagridBuilder and allows you to return data as in a list:
 $datagrid->getList(); 

and in the form of a single line:

 $datagrid->getItem(); 

Dataschema


DataSchema defines a dataset in the yaml format:

 schema: class: AppBundle\Entity\Article db_driver: orm properties: id: # integer type: # ArticleType name: # string body: # text publish: # boolean publishAt: # datetime 

Defines fields and links:

 schema: class: AppBundle\Entity\Movie db_driver: orm properties: ... tags: # AppBundle\Entity\Tag join: left properties: id: # integer 

Additional conditions:

 schema: class: AppBundle\Entity\Article db_driver: orm conditions: ["{{alias}}.publish = true"] properties: .... 

It is also possible to define additional conditions for links:

 schema: class: AppBundle\Entity\Movie db_driver: orm properties: ... articles: # AppBundle\Entity\Article conditions: ["{{alias}}.publish = true"] join: none properties: .... 

DataSchema also implements the function to load nested entities, if they were not configured as join:

 schema: class: AppBundle\Entity\Movie db_driver: orm properties: ... articles: # AppBundle\Entity\Article join: none properties: id: # integer 

DataSchema is used to build a query in a DatagridBuilder and to convert data to a Datagrid.

Scope


Scope allows you to narrow the data set defined in DataSchema.

For example, using scope, you can define a small amount of data for a list of entries (article \ list.yml):

 scope: id: name: 

and a complete data set for viewing a single entry (article \ view.yml):

 scope: id: type: name: body: publish: publishAt: movies: # AppBundle\Entity\Movie id: name: 


From words to demo.


I offer you a fictional api cinema, created specifically for demonstration. Entities and fields of entities are selected in such a way as to maximize the possibilities of api.

Demo project posted on githaba .

In order to deploy a project locally, you need to do 3 simple steps:

1. Create a project through the composer.

 composer create-project glavweb/rest-demo-app 

2. Run the migrations.

 php bin/console d:m:m -n 

3. Execute fixtures.

 php bin/console h:d:f:l -n 


Entities


The following entities are available:

Between them we have the following connections:
  1. one-to-many (film sessions, comments);
  2. many-to-one (group of films);
  3. many-to-many (tags);
  4. one-to-one (movie info).

Additional features:
  1. Unpublished comments are available only to authors.
  2. Administrators and moderators are available all comments.
  3. A comment created by a user in the group of administrators or moderators is automatically published.

Users and User Groups


Group of users:
  1. Administrators. They have full access to all records in the administration system.
  2. Moderators. They have limited access in the administration system (they can only edit and delete comments).
  3. Users Do not have access to the administration system. Through api, applications can add new comments, edit and delete only their own comments.

Preset user set:
  1. Administrator. Group: administrators. Login: admin, password: qwerty
  2. Moderator. Group: moderators. Login: login: moderator, password: qwerty
  3. User 1. Group: users. Login: login: user-1, password: qwerty
  4. User 2. Group: users. Login: login: user-2, password: qwerty

Scenarios


Scenario 1.

User is not authorized. All movies and comments confirmed by moderators are available to him. The user can not create comments, he does not have access to the administration system.

Scenario 2.

User is authorized. It belongs to the group "User". He is also available as comments confirmed by moderators and their own comments. The user can create comments and attach images to comments, can edit and delete their comments. Administration system is not available.

Scenario 3.

User is authorized. Belongs to the group "Moderator". All movies and comments are available to him. The moderator can create comments and attach images to comments, can edit and delete any comments. Limited administration system options are available - access to comments.

Scenario 4.

User is authorized. It belongs to the group "Administrator". All movies and comments are available to him. Can create comments and attach images to comments, can edit and delete any comments. Full system administration features are available.

Administration system


The administration system is available to admins and moderators.

URL: / admin

Api Request Examples


Api documentation URL: / api / doc

Special parameters


_scope

With the help of this parameter you can determine the skopu other than default. For example, we will get an abbreviated list of films consisting only of id and name.

 GET /api/movies?_scope=list_short 

_oprs

The parameter "_oprs" allows you to define the operator for the values ​​passed to the filter. Those. if it is not possible to pass the operator to the first character in the parameter to the filter, this can be done using the "_oprs" parameter. For example, this is useful for arrays when you need to pass "NOT IN":

 GET /api/articles?_oprs[type]=<>&type=photo_report 

_sort

With this parameter you can determine the sorting order. For example, we get all articles sorted by name (from last to first letter) and ID (from smaller to larger):

 GET /api/articles?_sort[name]=DESC&_sort[id]=ASC 

_offset

Using the parameter "_offset" you can determine the position for the list. For example, in order to get all the entries starting from the 11th, give "_offset = 10":

 GET /api/articles?_offset=10 

_limit

This parameter defines the limit for the list. For example, we get the first 10 articles:

 GET /api/articles?_limit=10 

Filter examples


String filters

For string filters by default, the search is performed by the occurrence of a substring.

 GET /api/articles?name=Dolorem 

If the opposite is needed, i.e. find all entries that do not contain the given substring, then you must pass "!" before value:

 GET /api/articles?name=!Dolorem 

If you need a full comparison, you must put the "=" sign in front of the value:

 GET /api/articles?name==Dolorem+eaque+libero+maxime. 

Not equal, is defined as follows (the "<>" characters in front of the value):

 GET /api/articles?name=<>Dolorem+eaque+libero+maxime. 

Enum types

Enum types are searched for by full comparison (=):

In order to find articles with the type of news, you must pass news:

 GET /api/articles?type=news 

Such a query will return an empty result:

 GET /api/articles?type=new 

Arrays

In order to filter by the array of values ​​(IN), it is necessary to transfer the array as follows [“name1”, “name2”, “name3 '...]. For example, in order to get articles with the type photo_report and news:

 /api/articles?type=["photo_report","news"] 

Get articles all but new and photo_report:

 /api/articles?_oprs[type]=<>&type=["photo_report",+"news"] 

Dates

More / less operators are available for both numeric filters and date filters. For example, get articles later 2016-06-07:

 /api/articles?publishAt=>2016-06-07 

Get articles earlier 2016-06-07:

 /api/articles?publishAt=<2016-06-07 

The range of dates can be obtained as follows:

 GET /api/articles?publishAt=[">2016-03-06","<2016-03-13"] 


Conclusion


For those who want to get acquainted with how it is implemented in the code - a link to the githab .

Source: https://habr.com/ru/post/303366/


All Articles