PHPackages                             requestum/symfony-api-edition - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. requestum/symfony-api-edition

ActiveProject[API Development](/categories/api)

requestum/symfony-api-edition
=============================

Symfony edition for api development

v1.0.17(7y ago)2519[13 issues](https://github.com/requestum-team/symfony-api-edition/issues)[2 PRs](https://github.com/requestum-team/symfony-api-edition/pulls)MITPHPPHP &gt;=5.4CI failing

Since Nov 6Pushed 6y ago5 watchersCompare

[ Source](https://github.com/requestum-team/symfony-api-edition)[ Packagist](https://packagist.org/packages/requestum/symfony-api-edition)[ RSS](/packages/requestum-symfony-api-edition/feed)WikiDiscussions master Synced 3d ago

READMEChangelog (2)Dependencies (23)Versions (25)Used By (0)

Docs
----

[](#docs)

/api/doc

Authentication
--------------

[](#authentication)

Api uses OAuth2 authentication. To authenticate your request add "Authorization" header with value "Bearer access\_token"

Fixtures
--------

[](#fixtures)

**User 1**
username:
password: 123

**User 2**
username:
password: 123

**Access Tokens:** Access\_Token\_For\_Artur, Access\_Token\_For\_Kirill

Basic concepts
==============

[](#basic-concepts)

This bundle provides ready to use action classes for standard REST API operations such as create, update, delete, list and transit. It uses class per action approach, so each action type has it's own class. Also this bundle utilizes concept of having many services (i.e. class instances) per one action class.

Imagine we have 3 endpoints that provide a list of resources.

```
GET /resource1
GET /resource2
GET /resource3

```

Each of these endpoints has same business logic and can be covered by the same code. We have to query database, apply some filters, pagination and sorting. Then we have to prepare result data, serialize it and send back to the client. The only difference here is that we have different resource entity repository for each endpoint. Also these almost same endpoints can have different values for some parameters such as available filters, default page size, etc. So the idea is to have unified parametrized list action class, and instantiate it three times with different arguments. For more flexibility in parametrization we use Symfony OptionResolver component. So each action class has a set of options to configure concrete endpoint.

Under the hood
==============

[](#under-the-hood)

This bundle consist of next components:

1. Resource metadata factory
2. Serializer extension with next features

- expand of entity relations on demand
- access control per field during serialization

3. Event listeners that handle frequent use cases in api development

- json decoder that populates HttpFoundation request object's request parameter bag with passed data
- exception listener that formats errors

4. Error factory
5. Base class for voters that vote on concrete resource entity
6. Action classes

Action classes
--------------

[](#action-classes)

- [List](#list-action)
- [Fetch](#fetch-action)
- [Create](#create-action)
- [Update](#update-action)
- [Delete](#delete-action)

---

Create operation
----------------

[](#create-operation)

List action
-----------

[](#list-action)

Get list of items.
Object type: collection
HTTP method: GET

### Configuration

[](#configuration)

1 Add service. Example:

```
# config/services.yml

services:
    ...
    action.country.list:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\ListAction
        arguments:
            - MyProject\MyBundle\Entity\Сountry
        calls:
            - ['setOptions',
                [{
                    'default_per_page' : 15,
                    'pagerfanta_fetch_join_collection' : true,
                    'pagerfanta_use_output_walkers' : true,
                    'serialization_groups': ['default', 'custom-group'],
                    'filters' : ['query', 'order-by', 'name', 'language'],
                    'preset_filters' : {availableForUser: '__USER__', order-by: 'createdAt|desc'},
                }]
            ]
    ...
```

Where:
`arguments: ... ` - required arguments to the `Requestum\ApiBundle\Action\ListAction` class constructor
`- MyProject\MyBundle\Entity\Сountry` - entity class (required)
`['setOptions', ...]` - array of options

2 Add service to routing. Example:

```
# config/routing.yml
...
country.list:
   path: /country
   methods: GET
   defaults: { _controller: action.country.list:executeAction }
...
```

### Available Options

[](#available-options)

OptionTypeDefault valueDescriptiondefault\_per\_pageinteger20Results per page (Pagination)pagerfanta\_fetch\_join\_collectionbooleanfalseWhether the query joins a collection join collection (Pagination)pagerfanta\_use\_output\_walkersbooleannullWhether the query joins a collection join collection (Pagination)serialization\_groupsarray\['default'\]One can serialize properties that belong to chosen groups onlyserialization\_check\_accessbooleantrueCheck user access during serializationfiltersarray\[\]Filtering results ([More information](#filters))preset\_filtersarray\[\]Preset filters and values. String value `__USER__` can be used as alias for the current authorized user.### *Filters*

[](#filters)

**Query filter**
Available text search in some fields (`LIKE`). Supports wildcards (`*suffix`, `prefix*`, `*middle*`)
To add fields you need to edit the `createHandlers()` method in the entity repository.
Add a filter using `'filters': ['query']` option.
Example:

```
# YourBundle\Repository\CountryRepository.php

class CountryRepository extends EntityRepository implements FilterableRepositoryInterface
{
    use ApiRepositoryTrait;
    ...
    /**
     * @inheritdoc
     */
    protected function createHandlers()
    {
        return [
            new SearchHandler([
               'language',
               'cities.name', // use the dot for fields of related entities
               'president_full_name' => ['president.firstName', 'president.lastName'] //use array to concatenate fields
            ])
        ];
    }
    ...
}
```

Sample query with filter: ` GET /country?query=*nglish`

You can specify particular fields you want to search in (from list you passed to SearchHandler).

` GET /country?query[term]=*Charles*&query[fields]=president_full_name,cities.name`

**Sorting**
One may add the property name and sort order to the request (pattern: 'field|order') to sort. Example: `'order-by': 'createdAt|desc'`

**Filter by properties**
Such filtering by entity is available:

- exact matching (Example: `GET /country?status=false`);
- using comparison operators (`!=,  '[cities][id]',
            ...
        ];
    }
}
```

**Custom filter**
To create custom filters one need:
1 Add new Handler. Example:

```
# YourBundle\Filter\CustomFilteHandler

use Requestum\ApiBundle\Filter\Handler\AbstractHandler;

class CustomFilteHandler extends AbstractHandler
{
    public function handle(QueryBuilder $builder, $filter, $value)
    {
      ... // Some filter logic
    }

    protected function getFilterKey()
    {
        return 'customFilterName'; // filter name
    }
}
```

2 Add handler to item repository. Example:

```
# YourBundle\Repository\CountryRepository.php

class CountryRepository extends EntityRepository implements FilterableRepositoryInterface
{
    use ApiRepositoryTrait;
    ...
    /**
     * @inheritdoc
     */
    protected function createHandlers()
    {
        return [
            new CustomFilterHandler()
        ];
    }
    ...
}
```

3 Add custom filter to service. Example:

```
services:
    ...
    action.country.list:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\ListAction
        arguments:
            - MyProject\MyBundle\Entity\Country
        calls:
            - ['setOptions', [{'filters': ['customFilterName']}]]
    ...
```

### Additional functionality

[](#additional-functionality)

#### *Pagination*

[](#pagination)

[Pagerfanta](https://github.com/whiteoctober/Pagerfanta) is used for pagination and works with DoctrineORM query objects only.
ApiBundle pagination configured with default options `pagerfanta_fetch_join_collection = false` and `pagerfanta_use_output_walkers = null` (This setting can be changed in options).
One use pagination add `page={int}` and `per-page={int}` to the request.
Example: `GET /country?page=1&per-page=15`

#### *Count only*

[](#count-only)

To get the count of query results only one may add `count-only` to the request attributes. Add to routing configuration as an example:

```
# config/routing.yml
...
country.list:
    path: /country
    methods: GET
    defaults:
        {
            _controller: action.country.list:executeAction,
            count-only: true
        }
...
```

#### *Expand*

[](#expand)

One can use the related entity references instead of full value in the response (can be expanded on demand) by adding annotation `@Reference` to entity property, for example:

```
# YouBundle\Entity\Country.php;
use Requestum\ApiBundle\Rest\Metadata;

class Country
{
    ...
    /**
     * @ORM\OneToMany
     * @Reference
     **/
    protected $cities;
    ...
}
```

Add `expand` to the request for expand reference. For multiple references expansion according fields should be separated be commas(NB: no spaces needs here!). One use the point for expand the field in associated entity.
Example:

```
// GET /country?expand=cities&name=Australia
{
    total: 1,
    entities:
        [
            {
                id: 1,
                name: 'Australia',
                language: 'English',
                population: 25103900,
                status: true,
                createdAt: "2018-03-22T10:49:07+00:00",
                cities:
                    [
                        {
                            id: 11,
                            name: 'Sydney',
                            districts: [112, 113],
                            population: 25103900,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 12,
                            name: 'Melbourne',
                            districts: [122],
                            population: 4850740,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 13,
                            name: 'Brisbane',
                            districts: [131, 132],
                            population: 2408223,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        }
                    ]
            }
        ]
}

// GET /country?name=Australia
{
    total: 1,
    entities:
    [
        {
            id: 1,
            name: 'Australia',
            language: 'English',
            population: 25103900,
            status: true,
            createdAt: "2018-03-22T10:49:07+00:00",
            cities:
                [11, 12, 13]
        }
    ]
}
```

### Request example

[](#request-example)

```
http://mysite/country?expand=cities
```

### Response example

[](#response-example)

```
{
    total: 2,
    entities:
        [
            {
                id: 1,
                name: 'Australia',
                language: 'English',
                population: 25103900,
                status: true,
                createdAt: "2018-03-22T10:49:07+00:00",
                cities:
                    [
                        {
                            id: 11,
                            name: 'Sydney',
                            districts: [112, 113],
                            population: 25103900,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 12,
                            name: 'Melbourne',
                            districts: [122],
                            population: 4850740,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        },
                        {
                            id: 13,
                            name: 'Brisbane',
                            districts: [131, 132],
                            population: 2408223,
                            isCapital: false,
                            createdAt: "2018-03-23T10:49:07+00:00"
                        }
                    ]
            },
            {
                id: 2,
                name: 'Spain',
                language: 'Spanish',
                population: 46700000,
                status: false,
                createdAt: "2018-03-23T10:49:07+00:00",
                cities:
                    [
                        {
                            id: 21,
                            name: 'Madrid',
                            districts: [212],
                            population: 3165235,
                            isCapital: true,
                            createdAt: "2018-03-24T10:49:07+00:00"
                        },
                        {
                            id: 22,
                            name: 'Barcelona',
                            districts: [224],
                            population: 1602386,
                            isCapital: false,
                            createdAt: "2018-03-24T10:49:07+00:00"
                        },
                        {
                            id: 23,
                            name: 'Valencia',
                            districts: [231, 232],
                            population: 786424,
                            isCapital: false,
                            createdAt: "2018-03-24T10:49:07+00:00"
                        }
                    ]
            }
        ]
}
```

Fetch action
------------

[](#fetch-action)

Get single item by identifier.
Object type: item
HTTP method: GET

### Configuration

[](#configuration-1)

1 Add service. Example:

```
# config/services.yml

services:
    ...
    action.country.fetch:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\FetchAction
        arguments:
            - MyProject\MyBundle\Entity\Сountry
        calls:
            - ['setOptions',
                [{
                    'serialization_groups':['full_post', 'default'],
                    'fetch_field': 'email'
                }]
            ]
    ...
```

Where:
`arguments: ... ` - required arguments to the `Requestum\ApiBundle\Action\FetchAction` class constructor
`- MyProject\MyBundle\Entity\Сountry` - entity class (required)
`['setOptions', ...]` - array of options

2 Add service to routing. Example:

```
# config/routing.yml
...
country.fetch:
   path: /country/{id}
   methods: GET
   defaults: { _controller: action.country.fetch:executeAction }
...
```

### Available Options

[](#available-options-1)

OptionTypeDefault valueDescriptionserialization\_groupsarray\['default'\]One can serialize properties that belong to chosen groups onlyserialization\_check\_accessbooleantrueCheck user access during serializationfetch\_fieldstring/array'id'Possibility to use one (string) or more (array) property of entity as an unique identifieraccess\_attributestring'fetch'Access attribute for check user permissions ([More information](#access-attribute))#### *Access attribute*

[](#access-attribute)

Symfony Voters are used for check the user's access permissions. `AccessDecisionManager` will receive value of `access_attribute` as `$attribute` and entity as subject.
Bundle provides the base class `AbstractEntityVoter`, which checks the user in the session depending on the received parameter `$userRequired` (optional, `true` by default). It easy to use with the following settings for `access_decision_manager`:

```
# config/security.yml
...
access_decision_manager:
    strategy: unanimous
    allow_if_all_abstain: true
...
```

Also the bundle has a `OwnerVoter` class that working with \[update, delete\] attributes. It uses the Symfony PropertyAccess Component for check the current user's relationship (is the owner) to the subject entity. The relationships checked by `$propertyPath` which is passed to the constructor for `OwnerVoter` class.
One can create custom voters based on the `AbstractEntityVoter` class. Example:

1 Add new voter:

```
# YourBundle\Security\Entity\CustomVoter.php

use Requestum\ApiBundle\Security\Authorization\AbstractEntityVoter;

class CustomVoter extends AbstractEntityVoter
{
    /**
     * @param string $attribute
     * @param object $entity
     * @param UserInterface|null $user
     */
     protected function voteOnEntity($attribute, $entity, UserInterface $user = null);
    {
        // some logic
    }
}
```

2 Add new voter to services:

```
# config/services.yml

services:
...
    voter.country.owner:
        class: YourBundle\Security\Entity\CustomVoter
        arguments: [[fetch, create, update, delete], YourBundle\Entity\Country, true]
        tags:
            - { name: security.voter }
...
```

Where:
`arguments: ... ` - arguments to the custom voter class constructor
`[fetch, create, update, delete]` - array of access attributes (required)
`YourBundle\Entity\Country` - entity class (required)
`true` - required user flag (optional, `true` by default)

3 Add `'access_attribute'` to service config for set attributes to check user permissions (as needed).
`'access_attribute' : 'fetch'` by default.

### Additional functionality

[](#additional-functionality-1)

#### *Expand*

[](#expand-1)

One can use the related entity references instead of full value in the response. See [Expand in ListAction](#expand)

### Request example

[](#request-example-1)

```
http://mysite/country/1?expand=cities
```

### Response example

[](#response-example-1)

```
{
    id: 1,
    name: 'Australia',
    language: 'English',
    population: 25103900,
    status: true,
    createdAt: "2018-03-22T10:49:07+00:00",
    cities:
        [
            {
                id: 11,
                name: 'Sydney',
                districts: [112, 113],
                population: 25103900,
                isCapital: false,
                createdAt: "2018-03-23T10:49:07+00:00"
            },
            {
                id: 12,
                name: 'Melbourne',
                districts: [122],
                population: 4850740,
                isCapital: false,
                createdAt: "2018-03-23T10:49:07+00:00"
            },
            {
                id: 13,
                name: 'Brisbane',
                districts: [131, 132],
                population: 2408223,
                isCapital: false,
                createdAt: "2018-03-23T10:49:07+00:00"
            }
        ]
}
```

Abstract Form Action Class
--------------------------

[](#abstract-form-action-class)

This is an abstract class that is the parent for the [Create](#create-action) and [Update](#update-action) Actions. Can be used to inherit and to create another custom actions.

### Available Options

[](#available-options-2)

OptionTypeDescriptionDefault Valueshttp\_methodstringHTTP methodPOSTsuccess\_status\_codeintegerStatus that is returned after execution200return\_entitybooleanResult entity in responsetrueform\_optionsarrayoptions that will be used in building form\[\]before\_save\_eventsarrayBefore submit events (events that throws before the flush)\[\]after\_save\_eventsarrayAfter submit events (events that throws after the flush)\[\]Create Action
-------------

[](#create-action)

Action to create a new object. This is a subclass that inherits from [AbstractFormAction](#abstract-form-action-class) class.

There are two required parameters: Entity class and FormType Class. Example:

```
# src/AppBundle/Resources/config/services.yml

services:
    #...

    action.user.create:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\CreateAction
        arguments:
            - AppBundle\Entity\User
            - AppBundle\Form\User\UserType

```

### Available Options

[](#available-options-3)

OptionTypeDescriptionDefault Valueshttp\_methodstringHTTP methodPOSTsuccess\_status\_codeintegerStatus that is returned after execution201return\_entitybooleanResult entity in responsetrueform\_optionsarrayoptions that will be used in building form\[\]before\_save\_eventsarrayBefore submit events (events that throws before the flush)\[\]after\_save\_eventsarrayAfter submit events (events that throws after the flush)\[\]access\_attributestringAccess Attributecreate### Event listeners

[](#event-listeners)

By default Create and Update actions throws such events: `'action.before_save'`, `'action.after_save'`. You can dispatch this events, or throw another events using such options as: `before_save_events` and `after_save_events`.

You can create listeners that will respond to event occuring before and after submit the request. You need to configure it in `services.yml` file:

```
    before_save.user.event:
        class: Requestum\ApiBundle\EventListener\UserBeforeSaveListener
        arguments: ["@security.token_storage"]
        tags:
            - { name: kernel.event_listener, event: action.before_save_user, method: onBeforeSaveUser }

    after_save.user.event:
        class: Requestum\ApiBundle\EventListener\UserAfterSaveListener
        arguments: ["@security.token_storage"]
        tags:
            - { name: kernel.event_listener, event: action.after_save_user, method: onAfterSaveUser }

```

Then you need to specify this listeners in create action configuration:

```
    action.user.create:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\CreateAction
        arguments:
            - AppBundle\Entity\User
            - AppBundle\Form\User\UserType
        calls:
            - ['setOptions', [{'before_save_events': ['action.before_save_user'], 'after_save_events': ['action.after_save_user']}]]

```

Update Action
-------------

[](#update-action)

Action to update an existing object. This is a subclass that inherits from [AbstractFormAction](#abstract-form-action-class) class.

There are two required parameters: Entity class and FormType Class. Example:

```
# src/AppBundle/Resources/config/services.yml

services:
    #...

    action.user.update:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\UpdateAction
        arguments:
            - AppBundle\Entity\User
            - AppBundle\Form\User\UserType

```

### Available Options

[](#available-options-4)

OptionTypeDescriptionDefault Valueshttp\_methodstringHTTP methodPATCHsuccess\_status\_codeintegerStatus that is returned after execution200return\_entitybooleanResult entity in responsetrueform\_optionsarrayoptions that will be used in building form\[\]before\_save\_eventsarrayBefore submit events (events that throws before the flush)\[\]after\_save\_eventsarrayAfter submit events (events that throws after the flush)\[\]access\_attributestringAccess AttributeupdateUpdate action has the same available features and options as a create action. (see "[Create Action](#create-action)")

Delete Action
-------------

[](#delete-action)

Action to delete an existing object

There is one required parameter: Entity class. Example:

```
# src/AppBundle/Resources/config/services.yml

services:
    #...

    action.user.delete:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\DeleteAction
        arguments:
            - AppBundle\Entity\User

```

### Available Options

[](#available-options-5)

OptionTypeDescriptionDefault Valuesfetch\_fieldstringThe field that is the entity identifier (id by default)idbefore\_delete\_eventsarrayBefore delete events\[\]access\_attributestringAccess Attributedelete### Event listeners

[](#event-listeners-1)

By default Delete action throws a such event: `'action.before_delete'`. You can dispatch this event, or throw another events using such an option: `before_delete_events`.

You can create listeners that will respond to event occuring before delete the entity. You need to configure it in `services.yml` file:

```
    before_delete.user.event:
        class: Requestum\ApiBundle\EventListener\UserBeforeDeleteListener
        tags:
            - { name: kernel.event_listener, event: action.before_delete_user, method: onBeforeDeleteUser }

```

Then you need to specify this listeners in delete action configuration:

```
    action.user.delete:
        parent: core.action.abstract
        class: Requestum\ApiBundle\Action\DeleteAction
        arguments:
            - AppBundle\Entity\User
        calls:
            - ['setOptions', [{'before_delete_events': ['action.before_delete_user'] }]]

```

###  Health Score

27

—

LowBetter than 49% of packages

Maintenance0

Infrequent updates — may be unmaintained

Popularity14

Limited adoption so far

Community18

Small or concentrated contributor base

Maturity68

Established project with proven stability

 Bus Factor2

2 contributors hold 50%+ of commits

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~27 days

Recently: every ~8 days

Total

18

Last Release

2641d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/1530d28e923ae84b9e03c724f7cb97529cd06238c7466bfea6bc4c4e7bc0532f?d=identicon)[admins@requestum.com](/maintainers/admins@requestum.com)

---

Top Contributors

[![Ivanova27](https://avatars.githubusercontent.com/u/26598050?v=4)](https://github.com/Ivanova27 "Ivanova27 (34 commits)")[![stavichenko](https://avatars.githubusercontent.com/u/5190500?v=4)](https://github.com/stavichenko "stavichenko (34 commits)")[![Vasia-Gav](https://avatars.githubusercontent.com/u/5128278?v=4)](https://github.com/Vasia-Gav "Vasia-Gav (6 commits)")[![VladZernov](https://avatars.githubusercontent.com/u/15523128?v=4)](https://github.com/VladZernov "VladZernov (5 commits)")[![maxikSP](https://avatars.githubusercontent.com/u/2980628?v=4)](https://github.com/maxikSP "maxikSP (1 commits)")

### Embed Badge

![Health badge](/badges/requestum-symfony-api-edition/health.svg)

```
[![Health](https://phpackages.com/badges/requestum-symfony-api-edition/health.svg)](https://phpackages.com/packages/requestum-symfony-api-edition)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[ec-cube/ec-cube

EC-CUBE EC open platform.

78527.0k1](/packages/ec-cube-ec-cube)[forumify/forumify-platform

121.8k11](/packages/forumify-forumify-platform)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
