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;
- Datagrid;
- Filter;
- DataSchema + Scope.
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.
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:
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:
- Movie. Films.
- MovieDetail. Details of the movie film.
- MovieSession. Sessions of films.
- MovieGroup. Movie groups.
- MovieComment. Comments to the films.
- Image. Images are used in movie comments.
- Article. Articles.
- Tag. Tags
Between them we have the following connections:
- one-to-many (film sessions, comments);
- many-to-one (group of films);
- many-to-many (tags);
- one-to-one (movie info).
Additional features:
- Unpublished comments are available only to authors.
- Administrators and moderators are available all comments.
- A comment created by a user in the group of administrators or moderators is automatically published.
Users and User Groups
Group of users:
- Administrators. They have full access to all records in the administration system.
- Moderators. They have limited access in the administration system (they can only edit and delete comments).
- 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:
- Administrator. Group: administrators. Login: admin, password: qwerty
- Moderator. Group: moderators. Login: login: moderator, password: qwerty
- User 1. Group: users. Login: login: user-1, password: qwerty
- 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 .