Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mmoreram/ControllerExtraBundle

Controller extra Bundle for Symfony2
https://github.com/mmoreram/ControllerExtraBundle

annotation controller mapping php symfony

Last synced: 3 months ago
JSON representation

Controller extra Bundle for Symfony2

Awesome Lists containing this project

README

        

# ControllerExtra for Symfony2

[![Build Status](https://travis-ci.org/mmoreram/ControllerExtraBundle.png?branch=master)](https://travis-ci.org/mmoreram/ControllerExtraBundle)

This bundle provides a collection of annotations for Symfony2 Controllers,
designed to streamline the creation of certain objects and enable smaller and
more concise actions.

Table of contents
-----
1. [Reference](#reference)
1. [Entity Provider](#entity-provider)
* [By namespace](#by-namespace)
* [By doctrine shortcut](#by-doctrine-shortcut)
* [By parameter](#by-parameter)
1. [Controller Annotations](#controller-annotations)
* [@CreatePaginator](#-createpaginator)
* [Paginator Entity](#paginator-entity)
* [Paginator Page](#paginator-page)
* [Paginator Limit](#paginator-limit)
* [Paginator OrderBy](#paginator-orderby)
* [Paginator Wheres](#paginator-wheres)
* [Paginator Left Joins](#paginator-left-joins)
* [Paginator Inner Joins](#paginator-inner-joins)
* [Paginator Not Nulls](#paginator-not-nulls)
* [Paginator Attributes](#paginator-attributes)
* [Paginator Example](#paginator-example)
* [Pagerfanta Add-on](#pagerfanta-add-on)
* [KNPPaginator Add-on](#knppaginator-add-on)
* [@LoadEntity](#-loadentity)
* [Entity Mapping](#entity-mapping)
* [Entity Mapping fallback](#entity-mapping-fallback)
* [Entity Repository](#entity-repository)
* [Entity Factory](#entity-factory)
* [@CreateForm](#-createform)
* [@Flush](#-flush)
* [@ToJsonResponse](#-tojsonresponse)
* [@Log](#-log)
* [@Get](#-get)
* [@Post](#-post)
1. [Custom annotations](#custom-annotations)
* [Annotation](#annotation)
* [Resolver](#resolver)
* [Definition](#definition)
* [Registration](#registration)

# Reference

By default, all annotations are loaded, but any individual annotation can be
completely disabled by setting to false `active` parameter.

Default values are:

``` yml
controller_extra:
resolver_priority: -8
request: current
paginator:
active: true
default_name: paginator
default_page: 1
default_limit_per_page: 10
entity:
active: true
default_name: entity
default_persist: true
default_mapping_fallback: false
default_factory_method: create
default_factory_mapping: true
form:
active: true
default_name: form
object_manager:
active: true
default_name: form
flush:
active: true
default_manager: default
json_response:
active: true
default_status: 200
default_headers: []
log:
active: true
default_level: info
default_execute: pre
```

> ResolverEventListener is subscribed to `kernel.controller` event with
> priority -8. This element can be configured and customized with
> `resolver_priority` config value. If you need to get ParamConverter entities,
> make sure that this value is lower than 0. The reason is that this listener
> must be executed always after ParamConverter one.

# Entity provider

In some annotations, you can define an entity by several ways. This chapter is
about how you can define them.

## By namespace

You can define an entity using its namespace. A simple new `new()` be performed.

``` php
/**
* Simple controller method
*
* @SomeAnnotation(
* class = "Mmoreram\CustomBundle\Entity\MyEntity",
* )
*/
public function indexAction()
{
}
```

## By doctrine shortcut

You can define an entity using Doctrine shortcut notations. With this format
you should ensure that your Entities follow Symfony Bundle standards and your
entities are placed under `Entity/` folder.

``` php
/**
* Simple controller method
*
* @SomeAnnotation(
* class = "MmoreramCustomBundle:MyEntity",
* )
*/
public function indexAction()
{
}
```

## By parameter

You can define an entity using a simple config parameter. Some projects
use parameters to define all entity namespaces (To allow overriding). If you
define the entity with a parameter, this bundle will try to instance it
with a simple `new()` accessing directly to the container ParametersBag.

``` yml
parameters:

#
# Entities
#
my.bundle.entity.myentity: Mmoreram\CustomBundle\Entity\MyEntity
```

``` php
/**
* Simple controller method
*
* @SomeAnnotation(
* class = "my.bundle.entity.myentity",
* )
*/
public function indexAction()
{
}
```

# Controller annotations

This bundle provide a reduced but useful set of annotations for your controller
actions.

## @CreatePaginator

Creates a Doctrine Paginator object, given a request and a configuration. This
annotation just injects into de controller a new
`Doctrine\ORM\Tools\Pagination\Pagination` instance ready to be iterated.

You can enable/disable this bundle by overriding `active` flag in configuration file
`config.yml`

``` yml
controller_extra:
pagination:
active: true
```

> By default, if `name` option is not set, the generated object will be placed
> in a parameter named `$paginator`. This behaviour can be configured using
> `default_name` in configuration.

This annotation can be configured with these sections

### Paginator Entity

To create a new Pagination object you need to refer to an existing Entity. You
can check all available formats you can define it just reading the
[Entity Provider](#entity-provider) section.

``` php
You can choose between Master Request or Current Request accessing to its
> attributes, by configuring the request value of the configuration.

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/{foo}
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* page = "~foo~"
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

or you can hardcode the page to use.

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* page = 1
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

### Paginator limit

You need to specify Paginator annotation the limit to fetch. By default, if none
is specified, this bundle will use the default one defined in configuration. You
can override in `config.yml`

``` yml
controller_extra:
pagination:
default_limit_per_page: 10
```

You can refer to an existing Request attribute using `~value~` format, to any
`$_GET` element by using format `?field?` or to any `$_POST` by using format
`#field#`

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/{foo}/{limit}
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* page = "~foo~",
* limit = "~limit~"
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

or you can hardcode the page to use.

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* page = 1,
* limit = 10
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

### Paginator OrderBy

You can order your Pagination just defining the fields you want to orderBy and
the desired direction. The `orderBy` section must be defined as an array of
arrays, and each array should contain these positions:

* First position: Entity alias (Principal object is set as `x`)
* Second position: Entity field
* Third position: Direction
* Fourth position: Custom direction map ***(optional)***

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* orderBy = {
* {"x", "createdAt", "ASC"},
* {"x", "updatedAt", "DESC"},
* {"x", "id", 1, {
* 0 => "ASC",
* 1 => "DESC",
* }},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

With the third and fourth value you can define a map where to match your own
direction nomenclature with DQL one. DQL nomenclature just accept ASC for
Ascendant and DESC for Descendant.

This is very useful when you need to match a url format with the DQL one. You
can refer to an existing Request attribute using `~value~` format, to any
`$_GET` element by using format `?field?` or to any `$_POST` by using format
`#field#`

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/order/{field}/{direction}
*
* For example, some matchings...
*
* /myroute/paginate/order/id/1 -> ORDER BY id DESC
* /myroute/paginate/order/enabled/0 - ORDER BY enabled ASC
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* orderBy = {
* {"x", "createdAt", "ASC"},
* {"x", "updatedAt", "DESC"},
* {"x", "~field~", ~direction~, {
* 0 => "ASC",
* 1 => "DESC",
* }},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

The order of the definitions will alter the order of the DQL query.

### Paginator Wheres

You can define some where statements in your Paginator. The `wheres` section
must be defined as an array of arrays, and each array should contain these
positions:

* First position: Entity alias (Principal object is set as `x`)
* Second position: Entity field
* Third position: Operator *=, <=, >, LIKE...*
* Fourth position: Value to compare with
* Fifth position: Is a filter. By default, false

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* wheres = {
* {"x", "enabled", "=", true},
* {"x", "age", ">", 18},
* {"x", "name", "LIKE", "Eferv%"},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

You can refer to an existing Request attribute using `~value~` format, to any
`$_GET` element by using format `?field?` or to any `$_POST` by using format
`#field#`

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/{field}
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* wheres = {
* {"x", "name", "LIKE", "~field~"},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

You can use as well this feature for optional filtering by setting the last
position to `true`. In that case, if the filter value is not found, such line
will be ignored.

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /myroute?query=name%
* This Controller matches pattern /myroute as well
*
* In both cases this will work. In the first case we will apply the where line
* in the paginator. In the second case, we wont.
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* wheres = {
* {"x", "name", "LIKE", "?query?", true},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

### Paginator Not Nulls

You can also define some fields to not null. Is same as `wheres` section, but
specific for NULL assignments. The `notNulls` section must be defined as an array
of arrays, and each array should contain these positions:

* First position: Object (Principal object is set as `x`)
* Second position: Field

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* notNulls = {
* {"x", "enabled"},
* {"x", "deleted"},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

### Paginator Left Join

You can do some left joins in this section. The `leftJoins` section must be
defined as an array of array, where each array can have these fields:

* First position: Entity alias (Principal object is set as `x`)
* Second position: Entity relation (Address)
* Third position: Relation identifier (a)
* Fourth position: If true, this relation is added in select group. Otherwise, wont
be loaded until its request ***(optional)***

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* leftJoins = {
* {"x", "User", "u", true},
* {"x", "Address", "a", true},
* {"x", "Cart", "c"},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

### Paginator Inner Join

You can do some inner joins in this section. The `innerJoins` section must be
defined as an array of array, where each array can have these fields:

* First position: Entity alias (x)
* Second position: Entity relation (Address)
* Third position: Relation identifier (a)
* Fourth position: If true, this relation is added in select group. Otherwise, wont
be loaded until its request ***(optional)***

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* innerJoins = {
* {"x", "User", "u", true},
* {"x", "Address", "a", true},
* {"x", "Cart", "c"},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

### Paginator Attributes

A nice feature of this annotation is that you can also inject into your
controller a `Mmoreram\ControllerExtraBundle\ValueObject\PaginatorAttributes`
instance with some interesting information about your pagination.

* currentPage : Current page fetched
* totalElements : Total elements given your criteria. If none criteria is
defined in your configuration, this value will show all elements of a certain entity.
* totalPages : Total pages you can fetch given a criteria.
* limitPerPage: Maximum number of elements in each page.

To inject this object you need to define the "attributes" annotation field with
the method parameter name.

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;
use Mmoreram\ControllerExtraBundle\ValueObject\PaginatorAttributes;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/
*
* @CreatePaginator(
* attributes = "paginatorAttributes",
* entityNamespace = "MmoreramCustomBundle:User",
* page = 1,
* limit = 10
* )
*/
public function indexAction(
Paginator $paginator,
PaginatorAttributes $paginatorAttributes
)
{
$currentPage = $paginatorAttributes->getCurrentPage();
$totalElements = $paginatorAttributes->getTotalElements();
$totalPages = $paginatorAttributes->getTotalPages();
$limitPerPage = $paginatorAttributes->getLimitPerPage();

}
```

### Paginator Example

This is a completed example and its DQL resolution

``` php
use Doctrine\ORM\Tools\Pagination\Pagination;
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;

/**
* Simple controller method
*
* This Controller matches pattern /paginate/nb/{limit}/{page}
*
* Where:
*
* * limit = 10
* * page = 1
*
* @CreatePaginator(
* entityNamespace = "ControllerExtraBundle:Fake",
* page = "~page~",
* limit = "~limit~",
* orderBy = {
* { "x", "createdAt", "ASC" },
* { "x", "updatedAt", "DESC" },
* { "x", "id", "0", {
* "1" = "ASC",
* "2" = "DESC",
* }}
* },
* wheres = {
* { "x", "enabled" , "=", true }
* },
* leftJoins = {
* { "x", "relation", "r" },
* { "x", "relation2", "r2" },
* { "x", "relation5", "r5", true },
* },
* innerJoins = {
* { "x", "relation3", "r3" },
* { "x", "relation4", "r4", true },
* },
* notNulls = {
* {"x", "address1"},
* {"x", "address2"},
* }
* )
*/
public function indexAction(Paginator $paginator)
{
}
```

The DQL generated by this annotation is

``` sql
SELECT x, r4, r5
FROM Mmoreram\\ControllerExtraBundle\\Tests\\FakeBundle\\Entity\\Fake x

INNER JOIN x.relation3 r3
INNER JOIN x.relation4 r4

LEFT JOIN x.relation r
LEFT JOIN x.relation2 r2
LEFT JOIN x.relation5 r5

WHERE enabled = ?where0
AND x.address1 IS NOT NULL
AND x.address2 IS NOT NULL

ORDER BY createdAt ASC, id ASC
```

### PagerFanta Add-on

This annotation can create a PagerFanta instance if you need it. You only have
to define your parameter as such, and the annotation resolver will wrap your
paginator with a Pagerfanta object instance.

``` php
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;
use Pagerfanta\Pagerfanta;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* page = 1,
* limit = 10
* )
*/
public function indexAction(Pagerfanta $paginator)
{
}
```

### KNPPaginator Add-on
This annotation can create a KNPPaginator instance if you need it. You only have
to define your parameter as such, and the annotation resolver will wrap your
paginator with a KNPPaginator object instance.
``` php
use Mmoreram\ControllerExtraBundle\Annotation\CreatePaginator;
use Knp\Component\Pager\Pagination\PaginationInterface;

/**
* Simple controller method
*
* This Controller matches pattern /myroute/paginate/
*
* @CreatePaginator(
* entityNamespace = "MmoreramCustomBundle:User",
* page = 1,
* limit = 10
* )
*/
public function indexAction(PaginationInterface $paginator)
{
}
```

## @LoadEntity

Loads an entity from your database, or creates a new one.

``` php
By default, if `name` option is not set, the generated object will be placed
> in a parameter named `$entity`. This behaviour can be configured using
> `default_name` in configuration.

You can also use setters in Entity annotation. It means that you can simply call
entity setters using Request attributes.

``` php
By default, if `mapping_fallback` option is not set, the used value will be
> the parameter `default_mapping_fallback` defined in configuration. By default
> this value is `false`

Don't confuse with the scenario where you're looking for an entity in your
database, all mapping references have been resolved, and the entity is not
found. In that case, a common "EntityNotFound" exception will be thrown by
Doctrine.

Lets see an example. Because we have enabled the mappingFallback, and because
the mapping definition does not match the assigned route, we will return a new
empty User entity.

``` php
getId() === null
}
```

### Entity Repository

By default, the Doctrine entity manager provides the right repository per each
entity (not the default one, but the right specific one). Although, you can
define a custom repository to be used in your annotation by using the repository
configuration.

``` php
/**
* Simple controller method
*
* @CreateEntity(
* namespace = "MmoreramCustomBundle:User",
* mapping = {
* "id": "~id~",
* "username": "~username~"
* }
* repository = {
* "class" = "Mmoreram\CustomBundle\Repository\AnotherRepository",
* },
* )
*/
public function indexAction(User $user)
{
}
```

By default, the method *findOneBy* will always be used, unless you define
another one.

``` php
/**
* Simple controller method
*
* @CreateEntity(
* namespace = "MmoreramCustomBundle:User",
* mapping = {
* "id": "~id~",
* "username": "~username~"
* }
* repository = {
* "class" = "Mmoreram\CustomBundle\Repository\AnotherRepository",
* "method" = "find",
* },
* )
*/
public function indexAction(User $user)
{
}
```

### Entity Factory

When the annotation considers that a new entity must be created, because no
mapping information has been provided, or because the mapping fallback has been
activated, by default a new instance will be created by using the *namespace*
value.

This configuration block has three positions

* class - factory class
* method - Method to use when retrieving the object
* static - Method is static

You can define the factory with a simple namespace

``` php
/**
* Simple controller method
*
* @CreateEntity(
* namespace = "MmoreramCustomBundle:User",
* factory = {
* "class" = "Mmoreram\CustomBundle\Factory\UserFactory",
* "method" = "create",
* "static" = true,
* },
* )
*/
public function indexAction(User $user)
{
}
```

If you want to define your Factory as a service, with the possibility of
overriding namespace, you can simply define service name. All other options have
the same behaviour.

``` yml
parameters:

#
# Factories
#
my.bundle.factory.user_factory: Mmoreram\CustomBundle\Factory\UserFactory
```

``` php
/**
* Simple controller method
*
* @CreateEntity(
* class = {
* "factory" = my.bundle.factory.user_factory,
* "method" = "create",
* "static" = true,
* },
* )
*/
public function indexAction(User $user)
{
}
```

If you do not define the `method`, default one will be used. You can
override this default value by defining new one in your `config.yml`. Same with
`static` value

``` yml
controller_extra:
entity:
default_factory_method: create
default_factory_static: true
```

## @CreateForm

Provides form injection in your controller actions. This annotation only needs
a name to be defined in, where you must define namespace where your form is
placed.

``` php
By default, if `name` option is not set, the generated object will be placed
> in a parameter named `$form`. This behaviour can be configured using
> `default_name` in configuration.

You can not just define your Type location using the namespace, in which case
a new AbstractType element will be created. but you can also define it using
service alias, in which case this bundle will return an instance using Symfony
DI.

``` php
isValid()` in specified method
argument.

``` php
If multiple @Mmoreram\Flush are defined in same action, last instance will
> overwrite previous. Anyway just one instance should be defined.

## @ToJsonResponse

JsonResponse annotation allows you to create a
`Symfony\Component\HttpFoundation\JsonResponse` object, given a simple
controller return value.

``` php
If the exception is being launched on an annotation (e.g. Entity annotation)
> remember to add the JsonResponse annotation at the beginning or at least before
> any annotation that could cause an exception.

> If multiple @Mmoreram\JsonResponse are defined in same action, last instance
> will overwrite previous. Anyway just one instance should be defined.

## @Log

Log annotation allows you to log any plain message before or after controller
action execution

``` php
field;
}
}
```

## Resolver

Once you have defined your own annotation, you have to resolve how this
annotation works in a controller. You can manage this using a Resolver. Must
extend `Mmoreram\ControllerExtraBundle\Resolver\AnnotationResolver;` abstract
class.

``` php
getField();

/**
* You can also access to existing method parameters.
*
* Available parameters are:
*
* # ParamConverter parameters ( See `resolver_priority` config value )
* # All method defined parameters, included Request object if is set.
*/
$entity = $request->attributes->get('entity');

/**
* And you can now place new elements in the controller action.
* In this example we are creating new method parameter
* called $myNewField with some value
*/
$request->attributes->set(
'myNewField',
new $field()
);

return $this;
}

}
```

This class will be defined as a service, so this method is computed just
before executing current controller. You can also subscribe to some kernel
events and do whatever you need to do ( You can check
`Mmoreram\ControllerExtraBundle\Resolver\LogAnnotationResolver` for some
examples.

## Definition

Once Resolver is done, we need to define our service as an Annotation
Resolver. We will use a custom `tag`.

``` yml
parameters:
#
# Resolvers
#
my.bundle.resolver.my_custom_annotation_resolver.class: My\Bundle\Resolver\MyCustomAnnotationResolver

services:
#
# Resolvers
#
my.bundle.resolver.my_custom_annotation_resolver:
class: %my.bundle.resolver.my_custom_annotation_resolver.class%
tags:
- { name: controller_extra.annotation }
```

## Registration

We need to register our annotation inside our application. We can just do it in
the `boot()` method of `bundle.php` file.

``` php
container->get('kernel');

AnnotationRegistry::registerFile($kernel
->locateResource("@MyBundle/Annotation/MyCustomAnnotation.php")
);
}
}
```

*Et voilà!* We can now use our custom Annotation in our project controllers.