Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
Creating and Editing objects
============================
.. note::
This document is a stub representing a new work in progress. If you're reading
this you can help contribute, **no matter what your experience level with Sonata
is**. Check out the `issues on Github`_ for more information about how to get involved.
This document will cover the Create and Edit actions. It will cover configuration
of the fields and forms available in these views and any other relevant settings.
Basic configuration
-------------------
SonataAdmin Options that may affect the create or edit view:
.. code-block:: yaml
sonata_admin:
options:
html5_validate: true # enable or disable html5 form validation
confirm_exit: true # enable or disable a confirmation before navigating away
use_select2: true # enable or disable usage of the Select2 jQuery library
use_icheck: true # enable or disable usage of the iCheck library
use_bootlint: false # enable or disable usage of Bootlint
use_stickyforms: true # enable or disable the floating buttons
form_type: standard # can also be 'horizontal'
templates:
edit: SonataAdminBundle:CRUD:edit.html.twig
tab_menu_template: SonataAdminBundle:Core:tab_menu_template.html.twig
For more information about optional libraries:
- Select2: https://github.com/select2/select2
- iCheck: http://icheck.fronteed.com/
- Bootlint: https://github.com/twbs/bootlint#in-the-browser
.. note::
**TODO**:
* options available when adding fields, inc custom templates
Routes
~~~~~~
You can disable creating or editing entities by removing the corresponding routes in your Admin.
For more detailed information about routes, see :doc:`routing`.
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
class PersonAdmin extends AbstractAdmin
{
// ...
protected function configureRoutes(RouteCollection $collection): void
{
/* Removing the edit route will disable editing entities. It will also
* use the 'show' view as default link on the identifier columns in the list view.
*/
$collection->remove('edit');
/* Removing the create route will disable creating new entities. It will also
* remove the 'Add new' button in the list view.
*/
$collection->remove('create');
}
// ...
}
Adding form fields
------------------
Within the configureFormFields method you can define which fields should
be shown when editing or creating entities.
Each field has to be added to a specific form group. And form groups can
optionally be added to a tab. See `FormGroup options`_ for additional
information about configuring form groups.
Using the FormMapper add method, you can add form fields. The add method
has 4 parameters:
- ``name``: The name of your entity.
- ``type``: The type of field to show; by defaults this is ``null`` to let
Sonata decide which type to use. See :doc:`Field Types <field_types>`
for more information on available types.
- ``options``: The form options to be used for the field. These may differ
per type. See :doc:`Field Types <field_types>` for more information on
available options.
- ``fieldDescriptionOptions``: The field description options. Options here
are passed through to the field template. See :ref:`Form Types, FieldDescription
options <form_types_fielddescription_options>` for more information.
.. note::
The property entered in ``name`` should be available in your Entity
through getters/setters or public access.
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
class PersonAdmin extends AbstractAdmin
{
// ...
protected function configureFormFields(FormMapper $formMapper): void
{
$formMapper
->tab('General') // The tab call is optional
->with('Addresses')
->add('title') // Add a field and let Sonata decide which type to use
->add('streetname', TextType::class) // Add a textfield
->add('housenumber', NumberType::class) // Add a number field
->add('housenumberAddition', TextType::class, ['required' => false]) // Add a non-required text field
->end() // End form group
->end() // End tab
;
}
// ...
}
FormGroup options
~~~~~~~~~~~~~~~~~
When adding a form group to your edit/create form, you may specify some
options for the group itself.
- ``collapsed``: unused at the moment
- ``class``: The class for your form group in the admin; by default, the
value is set to ``col-md-12``.
- ``fields``: The fields in your form group (you should NOT override this
unless you know what you're doing).
- ``box_class``: The class for your form group box in the admin; by default,
the value is set to ``box box-primary``.
- ``description``: A text shown at the top of the form group.
- ``translation_domain``: The translation domain for the form group title
(the Admin translation domain is used by default).
To specify options, do as follows:
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
class PersonAdmin extends AbstractAdmin
{
// ...
public function configureFormFields(FormMapper $formMapper): void
{
$formMapper
->tab('General') // the tab call is optional
->with('Addresses', [
'class' => 'col-md-8',
'box_class' => 'box box-solid box-danger',
'description' => 'Lorem ipsum',
// ...
])
->add('title')
// ...
->end()
->end()
;
}
// ...
}
Here is an example of what you can do with customizing the box_class on
a group:
.. figure:: ../images/box_class.png
:align: center
:alt: Box Class
:width: 500
Embedding other Admins
----------------------
.. note::
**TODO**:
* how to embed one Admin in another (1:1, 1:M, M:M)
* how to access the right object(s) from the embedded Admin's code
Customizing just one of the actions
-----------------------------------
.. note::
**TODO**:
* how to create settings/fields that appear on just one of the create/edit views
* and any controller changes needed to manage them
.. _`issues on GitHub`: https://github.com/sonata-project/SonataAdminBundle/issues/1519

View File

@@ -0,0 +1,23 @@
Deleting objects
================
.. note::
This document is a stub representing a new work in progress. If you're reading
this you can help contribute, **no matter what your experience level with Sonata
is**. Check out the `issues on GitHub`_ for more information about how to get involved.
This document will cover the Delete action and any related configuration options.
Basic configuration
-------------------
.. note::
**TODO**:
* global (yml) options that affect the delete action
* a note about Routes and how disabling them disables the related action
* any Admin configuration options that affect delete
* a note about lifecycle events triggered by delete?
.. _`issues on Github`: https://github.com/sonata-project/SonataAdminBundle/issues/1519

View File

@@ -0,0 +1,100 @@
The Export action
=================
This document will cover the Export action and related configuration options.
Basic configuration
-------------------
If you have registered the ``SonataExporterBundle`` bundle in the kernel of your application,
you can benefit from a lot of flexibility:
* You can configure default exporters globally.
* You can add custom exporters, also globally.
* You can configure every default writer.
See `the exporter bundle documentation`_ for more information.
Translation
~~~~~~~~~~~
All field names are translated by default.
An internal mechanism checks if a field matching the translator strategy label exists in the current translation file
and will use the field name as a fallback.
Picking which fields to export
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default, all fields are exported. More accurately, it depends on the
persistence backend you are using, but for instance, the doctrine ORM backend
exports all fields (associations are not exported). If you want to change this
behavior for a specific admin, you can override the ``getExportFields()`` method:
.. code-block:: php
<?php
public function getExportFields()
{
return array('givenName', 'familyName', 'contact.phone');
}
.. note::
Note that you can use `contact.phone` to access the `phone` property of `Contact` entity
You can also tweak the list by creating an admin extension that implements the
``configureExportFields()`` method.
.. code-block:: php
public function configureExportFields(AdminInterface $admin, array $fields)
{
unset($fields['updatedAt']);
return $fields;
}
Overriding the export formats for a specific admin
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Changing the export formats can be done by defining a ``getExportFormats()``
method in your admin class.
.. code-block:: php
<?php
public function getExportFormats()
{
return array('pdf', 'html');
}
Customizing the query used to fetch the results
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to customize the query used to fetch the results for a specific admin,
you can override the ``getDataSourceIterator()`` method:
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
class PersonAdmin extends AbstractAdmin
{
public function getDataSourceIterator()
{
$iterator = parent::getDataSourceIterator();
$iterator->setDateTimeFormat('d/m/Y'); //change this to suit your needs
return $iterator;
}
}
.. note::
**TODO**:
* customising the templates used to render the output
* publish the exporter documentation on the project's website and update the link
.. _`the exporter bundle documentation`: https://github.com/sonata-project/exporter/blob/1.x/docs/reference/symfony.rst

View File

@@ -0,0 +1,645 @@
The List View
=============
.. note::
This document is a stub representing a new work in progress. If you're reading
this you can help contribute, **no matter what your experience level with Sonata
is**. Check out the `issues on GitHub`_ for more information about how to get involved.
This document will cover the List view which you use to browse the objects in your
system. It will cover configuration of the list itself and the filters you can use
to control what's visible.
Basic configuration
-------------------
SonataAdmin Options that may affect the list view:
.. code-block:: yaml
sonata_admin:
templates:
list: SonataAdminBundle:CRUD:list.html.twig
action: SonataAdminBundle:CRUD:action.html.twig
select: SonataAdminBundle:CRUD:list__select.html.twig
list_block: SonataAdminBundle:Block:block_admin_list.html.twig
short_object_description: SonataAdminBundle:Helper:short-object-description.html.twig
batch: SonataAdminBundle:CRUD:list__batch.html.twig
inner_list_row: SonataAdminBundle:CRUD:list_inner_row.html.twig
base_list_field: SonataAdminBundle:CRUD:base_list_field.html.twig
pager_links: SonataAdminBundle:Pager:links.html.twig
pager_results: SonataAdminBundle:Pager:results.html.twig
.. note::
**TODO**:
* a note about Routes and how disabling them disables the related action
* adding custom columns
Customizing the fields displayed on the list page
-------------------------------------------------
You can customize the columns displayed on the list through the ``configureListFields`` method.
Here is an example:
.. code-block:: php
<?php
// ...
public function configureListFields(ListMapper $listMapper)
{
$listMapper
// addIdentifier allows to specify that this column
// will provide a link to the entity
// (edit or show route, depends on your access rights)
->addIdentifier('name')
// you may specify the field type directly as the
// second argument instead of in the options
->add('isVariation', 'boolean')
// if null, the type will be guessed
->add('enabled', null, array(
'editable' => true
))
// editable association field
->add('status', 'choice', array(
'editable' => true,
'class' => 'Vendor\ExampleBundle\Entity\ExampleStatus',
'choices' => array(
1 => 'Active',
2 => 'Inactive',
3 => 'Draft',
),
))
// we can add options to the field depending on the type
->add('price', 'currency', array(
'currency' => $this->currencyDetector->getCurrency()->getLabel()
))
// Here we specify which property is used to render the label of each entity in the list
->add('productCategories', null, array(
'associated_property' => 'name')
)
// you may also use dotted-notation to access
// specific properties of a relation to the entity
->add('image.name')
// You may also specify the actions you want to be displayed in the list
->add('_action', null, array(
'actions' => array(
'show' => array(),
'edit' => array(),
'delete' => array(),
)
))
;
}
Options
^^^^^^^
.. note::
* ``(m)`` stands for mandatory
* ``(o)`` stands for optional
- ``type`` (m): defines the field type - mandatory for the field description itself but will try to detect the type automatically if not specified
- ``template`` (o): the template used to render the field
- ``label`` (o): the name used for the column's title
- ``link_parameters`` (o): add link parameter to the related Admin class when the ``Admin::generateUrl`` is called
- ``code`` (o): the method name to retrieve the related value (for example,
if you have an `array` type field, you would like to show info prettier
than `[0] => 'Value'`; useful when simple getter is not enough).
Notice: works with string-like types (string, text, html)
- ``associated_property`` (o): property path to retrieve the "string" representation of the collection element, or a closure with the element as argument and return a string.
- ``identifier`` (o): if set to true a link appears on the value to edit the element
Available types and associated options
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. note::
``(m)`` means that option is mandatory
+-----------+----------------+-----------------------------------------------------------------------+
| Type | Options | Description |
+===========+================+=======================================================================+
| actions | actions | List of available actions |
+-----------+----------------+-----------------------------------------------------------------------+
| batch | | Renders a checkbox |
+-----------+----------------+-----------------------------------------------------------------------+
| select | | Renders a select box |
+-----------+----------------+-----------------------------------------------------------------------+
| array | | Displays an array |
+-----------+----------------+-----------------------------------------------------------------------+
| boolean | ajax_hidden | Yes/No; ajax_hidden allows to hide list field during an AJAX context. |
+ +----------------+-----------------------------------------------------------------------+
| | editable | Yes/No; editable allows to edit directly from the list if authorized. |
+ +----------------+-----------------------------------------------------------------------+
| | inverse | Yes/No; reverses the background color (green for false, red for true) |
+-----------+----------------+-----------------------------------------------------------------------+
| choice | choices | Possible choices |
+ +----------------+-----------------------------------------------------------------------+
| | multiple | Is it a multiple choice option? Defaults to false. |
+ +----------------+-----------------------------------------------------------------------+
| | delimiter | Separator of values if multiple. |
+ +----------------+-----------------------------------------------------------------------+
| | catalogue | Translation catalogue. |
+ +----------------+-----------------------------------------------------------------------+
| | class | Class path for editable association field. |
+-----------+----------------+-----------------------------------------------------------------------+
| currency | currency (m) | A currency string (EUR or USD for instance). |
+-----------+----------------+-----------------------------------------------------------------------+
| date | format | A format understandable by Twig's ``date`` function. |
+-----------+----------------+-----------------------------------------------------------------------+
| datetime | format | A format understandable by Twig's ``date`` function. |
+-----------+----------------+-----------------------------------------------------------------------+
| email | as_string | Renders the email as string, without any link. |
+ +----------------+-----------------------------------------------------------------------+
| | subject | Add subject parameter to email link. |
+ +----------------+-----------------------------------------------------------------------+
| | body | Add body parameter to email link. |
+-----------+----------------+-----------------------------------------------------------------------+
| percent | | Renders value as a percentage. |
+-----------+----------------+-----------------------------------------------------------------------+
| string | | Renders a simple string. |
+-----------+----------------+-----------------------------------------------------------------------+
| text | | See 'string' |
+-----------+----------------+-----------------------------------------------------------------------+
| html | | Renders string as html |
+-----------+----------------+-----------------------------------------------------------------------+
| time | | Renders a datetime's time with format ``H:i:s``. |
+-----------+----------------+-----------------------------------------------------------------------+
| trans | catalogue | Translates the value with catalogue ``catalogue`` if defined. |
+-----------+----------------+-----------------------------------------------------------------------+
| url | url | Adds a link with url ``url`` to the displayed value |
+ +----------------+-----------------------------------------------------------------------+
| | route | Give a route to generate the url |
+ + + +
| | name | Route name |
+ + + +
| | parameters | Route parameters |
+ +----------------+-----------------------------------------------------------------------+
| | hide_protocol | Hide http:// or https:// (default: false) |
+-----------+----------------+-----------------------------------------------------------------------+
If you have the SonataDoctrineORMAdminBundle installed, you have access to more field types, see `SonataDoctrineORMAdminBundle Documentation <https://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/list_field_definition.html>`_.
.. note::
It is better to prefer non negative notions when possible for boolean
values so use the ``inverse`` option if you really cannot find a good enough
antonym for the name you have.
Customizing the query used to generate the list
-----------------------------------------------
You can customize the list query thanks to the ``createQuery`` method.
.. code-block:: php
<?php
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query->andWhere(
$query->expr()->eq($query->getRootAliases()[0] . '.my_field', ':my_param')
);
$query->setParameter('my_param', 'my_value');
return $query;
}
Customizing the sort order
--------------------------
Configure the default ordering in the list view
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Configuring the default ordering column can simply be achieved by overriding
the ``datagridValues`` array property. All three keys ``_page``, ``_sort_order`` and
``_sort_by`` can be omitted.
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
use Sonata\AdminBundle\Admin\AbstractAdmin;
class PostAdmin extends AbstractAdmin
{
// ...
protected $datagridValues = array(
// display the first page (default = 1)
'_page' => 1,
// reverse order (default = 'ASC')
'_sort_order' => 'DESC',
// name of the ordered field (default = the model's id field, if any)
'_sort_by' => 'updatedAt',
);
// ...
}
.. note::
The ``_sort_by`` key can be of the form ``mySubModel.mySubSubModel.myField``.
.. note::
**TODO**: how to sort by multiple fields (this might be a separate recipe?)
Filters
-------
You can add filters to let user control which data will be displayed.
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
use Sonata\AdminBundle\Datagrid\DatagridMapper;
class ClientAdmin extends AbstractAdmin
{
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('phone')
->add('email')
;
}
// ...
}
All filters are hidden by default for space-saving. User has to check which filter he wants to use.
To make the filter always visible (even when it is inactive), set the parameter
``show_filter`` to ``true``.
.. code-block:: php
<?php
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('phone')
->add('email', null, array(
'show_filter' => true
))
// ...
;
}
By default the template generates an ``operator`` for a filter which defaults to ``sonata_type_equal``.
Though this ``operator_type`` is automatically detected it can be changed or even be hidden:
.. code-block:: php
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('foo', null, array(
'operator_type' => 'sonata_type_boolean'
))
->add('bar', null, array(
'operator_type' => 'hidden'
))
// ...
;
}
If you don't need the advanced filters, or all your ``operator_type`` are hidden, you can disable them by setting
``advanced_filter`` to ``false``. You need to disable all advanced filters to make the button disappear.
.. code-block:: php
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('bar', null, array(
'operator_type' => 'hidden',
'advanced_filter' => false
))
// ...
;
}
Default filters
^^^^^^^^^^^^^^^
Default filters can be added to the datagrid values by using the ``configureDefaultFilterValues`` method.
A filter has a ``value`` and an optional ``type``. If no ``type`` is given the default type ``is equal`` is used.
.. code-block:: php
public function configureDefaultFilterValues(array &$filterValues)
{
$filterValues['foo'] = array(
'type' => ChoiceFilter::TYPE_CONTAINS,
'value' => 'bar',
);
}
Available types are represented through classes which can be found here:
https://github.com/sonata-project/SonataCoreBundle/tree/master/Form/Type
Types like ``equal`` and ``boolean`` use constants to assign a choice of ``type`` to an ``integer`` for its ``value``:
.. code-block:: php
<?php
// SonataCoreBundle/Form/Type/EqualType.php
namespace Sonata\CoreBundle\Form\Type;
class EqualType extends AbstractType
{
const TYPE_IS_EQUAL = 1;
const TYPE_IS_NOT_EQUAL = 2;
}
The integers are then passed in the URL of the list action e.g.:
**/admin/user/user/list?filter[enabled][type]=1&filter[enabled][value]=1**
This is an example using these constants for an ``boolean`` type:
.. code-block:: php
use Sonata\UserBundle\Admin\Model\UserAdmin as SonataUserAdmin;
use Sonata\CoreBundle\Form\Type\EqualType;
use Sonata\CoreBundle\Form\Type\BooleanType;
class UserAdmin extends SonataUserAdmin
{
protected $datagridValues = array(
'enabled' => array(
'type' => EqualType::TYPE_IS_EQUAL, // => 1
'value' => BooleanType::TYPE_YES // => 1
)
);
}
Please note that setting a ``false`` value on a the ``boolean`` type will not work since the type expects an integer of ``2`` as ``value`` as defined in the class constants:
.. code-block:: php
<?php
// SonataCoreBundle/Form/Type/BooleanType.php
namespace Sonata\CoreBundle\Form\Type;
class BooleanType extends AbstractType
{
const TYPE_YES = 1;
const TYPE_NO = 2;
}
Default filters can also be added to the datagrid values by overriding the ``getFilterParameters`` method.
.. code-block:: php
use Sonata\CoreBundle\Form\Type\EqualType;
use Sonata\CoreBundle\Form\Type\BooleanType;
class UserAdmin extends SonataUserAdmin
{
public function getFilterParameters()
{
$this->datagridValues = array_merge(array(
'enabled' => array (
'type' => EqualType::TYPE_IS_EQUAL,
'value' => BooleanType::TYPE_YES
)
), $this->datagridValues);
return parent::getFilterParameters();
}
}
This approach is useful when you need to create dynamic filters.
.. code-block:: php
class PostAdmin extends SonataUserAdmin
{
public function getFilterParameters()
{
// Assuming security context injected
if (!$this->securityContext->isGranted('ROLE_ADMIN')) {
$user = $this->securityContext->getToken()->getUser();
$this->datagridValues = array_merge(array(
'author' => array (
'type' => EqualType::TYPE_IS_EQUAL,
'value' => $user->getId()
)
), $this->datagridValues);
}
return parent::getFilterParameters();
}
}
Please note that this is not a secure approach to hide posts from others. It's just an example for setting filters on demand.
Callback filter
^^^^^^^^^^^^^^^
If you have the **SonataDoctrineORMAdminBundle** installed you can use the ``doctrine_orm_callback`` filter type e.g. for creating a full text filter:
.. code-block:: php
use Sonata\UserBundle\Admin\Model\UserAdmin as SonataUserAdmin;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
class UserAdmin extends SonataUserAdmin
{
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('full_text', CallbackFilter::class, array(
'callback' => array($this, 'getFullTextFilter'),
'field_type' => 'text'
))
// ...
;
}
public function getFullTextFilter($queryBuilder, $alias, $field, $value)
{
if (!$value['value']) {
return;
}
// Use `andWhere` instead of `where` to prevent overriding existing `where` conditions
$queryBuilder->andWhere($queryBuilder->expr()->orX(
$queryBuilder->expr()->like($alias.'.username', $queryBuilder->expr()->literal('%' . $value['value'] . '%')),
$queryBuilder->expr()->like($alias.'.firstName', $queryBuilder->expr()->literal('%' . $value['value'] . '%')),
$queryBuilder->expr()->like($alias.'.lastName', $queryBuilder->expr()->literal('%' . $value['value'] . '%'))
));
return true;
}
}
You can also get the filter type which can be helpful to change the operator type of your condition(s):
.. code-block:: php
use Sonata\CoreBundle\Form\Type\EqualType;
class UserAdmin extends SonataUserAdmin
{
public function getFullTextFilter($queryBuilder, $alias, $field, $value)
{
if (!$value['value']) {
return;
}
$operator = $value['type'] == EqualType::TYPE_IS_EQUAL ? '=' : '!=';
$queryBuilder
->andWhere($alias.'.username '.$operator.' :username')
->setParameter('username', $value['value'])
;
return true;
}
}
.. note::
**TODO**:
* basic filter configuration and options
* targeting submodel fields using dot-separated notation
* advanced filter options (global_search)
Visual configuration
--------------------
You have the possibility to configure your List View to customize the render without overriding to whole template.
You can :
- `header_style`: Customize the style of header (width, color, background, align...)
- `header_class`: Customize the class of the header
- `collapse`: Allow to collapse long text fields with a "read more" link
- `row_align`: Customize the alignment of the rendered inner cells
- `label_icon`: Add an icon before label
.. code-block:: php
<?php
public function configureListFields(ListMapper $list)
{
$list
->add('id', null, array(
'header_style' => 'width: 5%; text-align: center',
'row_align' => 'center'
))
->add('name', 'text', array(
'header_style' => 'width: 35%'
)
->add('description', 'text', array(
'header_style' => 'width: 35%',
'collapse' => true
)
->add('upvotes', null, array(
'label_icon' => 'fa fa-thumbs-o-up'
)
->add('actions', null, array(
'header_class' => 'customActions',
'row_align' => 'right'
)
// ...
;
}
If you want to customise the `collapse` option, you can also give an array to override the default parameters.
.. code-block:: php
// ...
->add('description', 'text', array(
'header_style' => 'width: 35%',
'collapse' => array(
'height' => 40, // height in px
'read_more' => 'I want to see the full description', // content of the "read more" link
'read_less' => 'This text is too long, reduce the size' // content of the "read less" link
)
)
// ...
If you want to show only the `label_icon`:
.. code-block:: php
// ...
->add('upvotes', null, array(
'label' => false,
'label_icon' => 'fa fa-thumbs-o-up'
)
// ...
.. _`issues on GitHub`: https://github.com/sonata-project/SonataAdminBundle/issues/1519
Mosaic view button
------------------
You have the possibility to show/hide mosaic view button.
.. code-block:: yaml
sonata_admin:
# for hide mosaic view button on all screen using `false`
show_mosaic_button: true
You can show/hide mosaic view button using admin service configuration. You need to add option ``show_mosaic_button``
in your admin services:
.. code-block:: yaml
sonata_admin.admin.post:
class: Sonata\AdminBundle\Admin\PostAdmin
arguments: [~, Sonata\AdminBundle\Entity\Post, ~]
tags:
- { name: sonata.admin, manager_type: orm, group: admin, label: Post, show_mosaic_button: true }
sonata_admin.admin.news:
class: Sonata\AdminBundle\Admin\NewsAdmin
arguments: [~, Sonata\AdminBundle\Entity\News, ~]
tags:
- { name: sonata.admin, manager_type: orm, group: admin, label: News, show_mosaic_button: false }
Checkbox range selection
------------------------
.. tip::
You can check / uncheck a range of checkboxes by clicking a first one,
then a second one with shift + click.

View File

@@ -0,0 +1,180 @@
The Show action
===============
.. note::
This document is a stub representing a new work in progress. If you're reading
this you can help contribute, **no matter what your experience level with Sonata
is**. Check out the `issues on GitHub`_ for more information about how to get involved.
This document will cover the Show action and related configuration options.
Basic configuration
-------------------
.. note::
**TODO**:
* a note about Routes and how disabling them disables the related action
* a note about lifecycle events triggered by delete?
* options available when adding general fields, inc custom templates
* targeting submodel fields using dot-separated notation
* (Note, if this is very similar to the form documentation it can be combined)
Group options
~~~~~~~~~~~~~
When adding a group to your show page, you may specify some options for the group itself.
- ``collapsed``: unused at the moment
- ``class``: the class for your group in the admin; by default, the value is set to ``col-md-12``.
- ``fields``: the fields in your group (you should NOT override this unless you know what you're doing).
- ``box_class``: the class for your group box in the admin; by default, the value is set to ``box box-primary``.
- ``description``: to complete
- ``translation_domain``: to complete
To specify options, do as follow:
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Show\ShowMapper;
class PersonAdmin extends AbstractAdmin
{
public function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->tab('General') // the tab call is optional
->with('Addresses', array(
'class' => 'col-md-8',
'box_class' => 'box box-solid box-danger',
'description' => 'Lorem ipsum',
))
->add('title')
// ...
->end()
->end()
;
}
}
When extending an existing Admin, you may want to remove some fields, groups or tabs.
Here is an example of how to achieve this :
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
use Sonata\AdminBundle\Show\ShowMapper;
class PersonAdmin extends ParentAdmin
{
public function configureShowFields(ShowMapper $showMapper)
{
parent::configureShowFields($showMapper);
// remove just one field
$showMapper->remove('field_to_remove');
// remove a group from the "default" tab
$showMapper->removeGroup('GroupToRemove1');
// remove a group from a specific tab
$showMapper->removeGroup('GroupToRemove2', 'Tab2');
// remove a group from a specific tab and also remove the tab if it ends up being empty
$showMapper->removeGroup('GroupToRemove3', 'Tab3', true);
}
}
Customising the query used to show the object from within your Admin class
--------------------------------------------------------------------------
Setting up a showAction is pretty much the same as a form, which we did in the initial setup.
It is actually a bit easier, because we are only concerned with displaying information.
Smile, the hard part is already done.
The following is a working example of a ShowAction
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
use Sonata\AdminBundle\Show\ShowMapper;
class ClientAdmin extends AbstractAdmin
{
protected function configureShowFields(ShowMapper $showMapper)
{
// here we set the fields of the ShowMapper variable,
// $showMapper (but this can be called anything)
$showMapper
// The default option is to just display the
// value as text (for boolean this will be 1 or 0)
->add('name')
->add('phone')
->add('email')
// The boolean option is actually very cool
// true shows a check mark and the 'yes' label
// false shows a check mark and the 'no' label
->add('dateCafe', 'boolean')
->add('datePub', 'boolean')
->add('dateClub', 'boolean')
;
}
}
.. tip::
To customize the displayed label of a show field you can use the ``label`` option:
.. code-block:: php
$showMapper->add('name', null, array('label' => 'UserName'));
Setting this option to ``false`` will make the label empty.
Setting up a custom show template (very useful)
===============================================
The first thing you need to do is define it in app/config/config/yml:
.. configuration-block::
.. code-block:: yaml
sonata_admin:
title: Acme
title_logo: img/logo_small.png
templates:
show: AppBundle:Admin:Display_Client.html.twig
Once you have defined this, Sonata Admin looks for it in the following location:
``src/AppBundle/Resources/views/Admin/Display_Client.html.twig``
Now that you have told Sonata Admin where to find the template, it is time to put one in there.
The recommended way to start is to copy the default template, and paste it into its new home.
This ensures that you can update Sonata Admin and keep all of your hard work.
The original template can be found in the following location:
``vendor/sonata-project/admin-bundle/Resources/views/CRUD/base_show.html.twig``
Now that you have a copy of the default template, check to make sure it works.
That's it, now go code.
.. _`issues on GitHub`: https://github.com/sonata-project/SonataAdminBundle/issues/1519

View File

@@ -0,0 +1,455 @@
Advanced configuration
======================
Service Configuration
---------------------
When you create a new Admin service you can configure its dependencies, the services which are injected by default are:
========================= =============================================
Dependencies Service Id
========================= =============================================
model_manager sonata.admin.manager.%manager-type%
form_contractor sonata.admin.builder.%manager-type%_form
show_builder sonata.admin.builder.%manager-type%_show
list_builder sonata.admin.builder.%manager-type%_list
datagrid_builder sonata.admin.builder.%manager-type%_datagrid
translator translator
configuration_pool sonata.admin.pool
router router
validator validator
security_handler sonata.admin.security.handler
menu_factory knp_menu.factory
route_builder sonata.admin.route.path_info | sonata.admin.route.path_info_slashes
label_translator_strategy sonata.admin.label.strategy.form_component
========================= =============================================
.. note::
%manager-type% is to be replaced by the manager type (orm, doctrine_mongodb...),
and the default route_builder depends on it.
You have 2 ways of defining the dependencies inside ``services.xml``:
* With a tag attribute, less verbose:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.project" class="AppBundle\Admin\ProjectAdmin">
<tag
name="sonata.admin"
manager_type="orm"
group="Project"
label="Project"
label_translator_strategy="sonata.admin.label.strategy.native"
route_builder="sonata.admin.route.path_info"
/>
<argument />
<argument>AppBundle\Entity\Project</argument>
<argument />
</service>
.. configuration-block::
.. code-block:: yaml
app.admin.project:
class: AppBundle\Admin\ProjectAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Project", label: "Project", label_translator_strategy: "sonata.admin.label.strategy.native", route_builder: "sonata.admin.route.path_info" }
arguments:
- ~
- AppBundle\Entity\Project
- ~
public: true
* With a method call, more verbose
.. configuration-block::
.. code-block:: xml
<service id="app.admin.project" class="AppBundle\Admin\ProjectAdmin">
<tag
name="sonata.admin"
manager_type="orm"
group="Project"
label="Project"
/>
<argument />
<argument>AppBundle\Entity\Project</argument>
<argument />
<call method="setLabelTranslatorStrategy">
<argument type="service" id="sonata.admin.label.strategy.native" />
</call>
<call method="setRouteBuilder">
<argument type="service" id="sonata.admin.route.path_info" />
</call>
</service>
.. configuration-block::
.. code-block:: yaml
app.admin.project:
class: AppBundle\Admin\ProjectAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Project", label: "Project" }
arguments:
- ~
- AppBundle\Entity\Project
- ~
calls:
- [ setLabelTranslatorStrategy, [ "@sonata.admin.label.strategy.native" ]]
- [ setRouteBuilder, [ "@sonata.admin.route.path_info" ]]
public: true
If you want to modify the service that is going to be injected, add the following code to your
application's config file:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
admins:
sonata_admin:
sonata.order.admin.order: # id of the admin service this setting is for
model_manager: # dependency name, from the table above
sonata.order.admin.order.manager # customised service id
Creating a custom RouteBuilder
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To create your own RouteBuilder create the PHP class and register it as a service:
* php Route Generator
.. code-block:: php
<?php
namespace AppBundle\Route;
use Sonata\AdminBundle\Builder\RouteBuilderInterface;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\AdminBundle\Route\PathInfoBuilder;
use Sonata\AdminBundle\Route\RouteCollection;
class EntityRouterBuilder extends PathInfoBuilder implements RouteBuilderInterface
{
/**
* @param AdminInterface $admin
* @param RouteCollection $collection
*/
public function build(AdminInterface $admin, RouteCollection $collection)
{
parent::build($admin, $collection);
$collection->add('yourSubAction');
// The create button will disappear, delete functionality will be disabled as well
// No more changes needed!
$collection->remove('create');
$collection->remove('delete');
}
}
* xml service registration
.. configuration-block::
.. code-block:: xml
<service id="app.admin.entity_route_builder" class="AppBundle\Route\EntityRouterBuilder">
<argument type="service" id="sonata.admin.audit.manager" />
</service>
* YAML service registration
.. configuration-block::
.. code-block:: yaml
services:
app.admin.entity_route_builder:
class: AppBundle\Route\EntityRouterBuilder
arguments:
- "@sonata.admin.audit.manager"
Inherited classes
-----------------
You can manage inherited classes by injecting subclasses using the service configuration.
Lets consider a base class named `Person` and its subclasses `Student` and `Teacher`:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.person" class="AppBundle\Admin\PersonAdmin">
<tag name="sonata.admin" manager_type="orm" group="admin" label="Person" />
<argument/>
<argument>AppBundle\Entity\Person</argument>
<argument></argument>
<call method="setSubClasses">
<argument type="collection">
<argument key="student">AppBundle\Entity\Student</argument>
<argument key="teacher">AppBundle\Entity\Teacher</argument>
</argument>
</call>
</service>
You will just need to change the way forms are configured in order to take into account these new subclasses:
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
protected function configureFormFields(FormMapper $formMapper)
{
$subject = $this->getSubject();
$formMapper
->add('name')
;
if ($subject instanceof Teacher) {
$formMapper->add('course', 'text');
}
elseif ($subject instanceof Student) {
$formMapper->add('year', 'integer');
}
}
Tab Menu
--------
ACL
^^^
Though the route linked by a menu may be protected the Tab Menu will not automatically check the ACl for you.
The link will still appear unless you manually check it using the `hasAccess` method:
.. code-block:: php
<?php
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
// Link will always appear even if it is protected by ACL
$menu->addChild($this->trans('Show'), array('uri' => $admin->generateUrl('show', array('id' => $id))));
// Link will only appear if access to ACL protected URL is granted
if ($this->hasAccess('edit')) {
$menu->addChild($this->trans('Edit'), array('uri' => $admin->generateUrl('edit', array('id' => $id))));
}
}
Dropdowns
^^^^^^^^^
You can use dropdowns inside the Tab Menu by default. This can be achieved by using
the `'dropdown' => true` attribute:
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
// other tab menu stuff ...
$menu->addChild('comments', array('attributes' => array('dropdown' => true)));
$menu['comments']->addChild('list', array('uri' => $admin->generateUrl('listComment', array('id' => $id))));
$menu['comments']->addChild('create', array('uri' => $admin->generateUrl('addComment', array('id' => $id))));
}
If you want to use the Tab Menu in a different way, you can replace the Menu Template:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
templates:
tab_menu_template: AppBundle:Admin:own_tab_menu_template.html.twig
Translations
^^^^^^^^^^^^
The translation parameters and domain can be customised by using the
``translation_domain`` and ``translation_parameters`` keys of the extra array
of data associated with the item, respectively.
.. code-block:: php
<?php
$menuItem->setExtras(array(
'translation_parameters' => array('myparam' => 'myvalue'),
'translation_domain' => 'My domain',
));
You can also set the translation domain on the menu root, and children will
inherit it :
.. code-block:: php
<?php
$menu->setExtra('translation_domain', 'My domain');
Filter parameters
^^^^^^^^^^^^^^^^^
You can add or override filter parameters to the Tab Menu:
.. code-block:: php
<?php
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Admin\AdminInterface;
use Sonata\CoreBundle\Form\Type\EqualType;
class DeliveryAdmin extends AbstractAdmin
{
protected function configureTabMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
if (!$childAdmin && !in_array($action, array('edit', 'show', 'list'))) {
return;
}
if ($action == 'list') {
// Get current filter parameters
$filterParameters = $this->getFilterParameters();
// Add or override filter parameters
$filterParameters['status'] = array(
'type' => EqualType::TYPE_IS_EQUAL, // => 1
'value' => Delivery::STATUS_OPEN,
);
// Add filters to uri of tab
$menu->addChild('List open deliveries', array('uri' => $this->generateUrl('list', array(
'filter' => $filterParameters,
))));
return;
}
}
}
The `Delivery` class is based on the `sonata_type_translatable_choice` example inside the Core's documentation:
http://sonata-project.org/bundles/core/master/doc/reference/form_types.html#sonata-type-translatable-choice
Actions Menu
------------
You can add custom items to the actions menu for a specific action by overriding the following method:
.. code-block:: php
public function configureActionButtons(AdminInterface $admin, $list, $action, $object)
{
if (in_array($action, array('show', 'edit', 'acl')) && $object) {
$list['custom'] = array(
'template' => 'AppBundle:Button:custom_button.html.twig',
);
}
// Remove history action
unset($list['history']);
return $list;
}
.. figure:: ../images/custom_action_buttons.png
:align: center
:alt: Custom action buttons
Disable content stretching
--------------------------
You can disable ``html``, ``body`` and ``sidebar`` elements stretching. These containers are forced
to be full height by default. If you use custom layout or just don't need such behavior,
add ``no-stretch`` class to the ``<html>`` tag.
For example:
.. code-block:: html+jinja
{# src/AppBundle/Resources/views/standard_layout.html.twig #}
{% block html_attributes %}class="no-js no-stretch"{% endblock %}
Custom Action Access Management
-------------------------------
You can customize the access system inside the CRUDController by adding some entries inside the `$accessMapping` array in the linked Admin.
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class CustomAdmin extends AbstractAdmin
{
protected $accessMapping = array(
'myCustomFoo' => 'EDIT',
'myCustomBar' => array('EDIT', 'LIST'),
);
}
<?php
// src/AppBundle/Controller/CustomCRUDController.php
class CustomCRUDController extends CRUDController
{
public function myCustomFooAction()
{
$this->admin->checkAccess('myCustomFoo');
// If you can't access to EDIT role for the linked admin, an AccessDeniedException will be thrown
// ...
}
public function myCustomBarAction($object)
{
$this->admin->checkAccess('myCustomBar', $object);
// If you can't access to EDIT AND LIST roles for the linked admin, an AccessDeniedException will be thrown
// ...
}
// ...
}
You can also fully customize how you want to handle your access management by simply overriding ``checkAccess`` function
.. code-block:: php
<?php
// src/AppBundle/Admin/CustomAdmin.php
class CustomAdmin extends AbstractAdmin
{
public function checkAccess($action, $object = null)
{
$this->customAccessLogic();
}
// ...
}

View File

@@ -0,0 +1,77 @@
Annotations
===========
All annotations require jms/di-extra-bundle, it can easily be installed by composer:
.. code-block:: bash
composer require jms/di-extra-bundle
if you want to know more: http://jmsyst.com/bundles/JMSDiExtraBundle
The annotations get registered with JMSDiExtraBundle automatically if it is installed.
If you need to disable this for some reason, you can do this via the configuration:
.. configuration-block::
.. code-block:: yaml
sonata_admin:
options:
enable_jms_di_extra_autoregistration: false
.. note::
Starting with version 4.0, SonataAdminBundle will no longer register
annotations with JMSDiExtraBundle automatically. Please add the following to
your config.yml to register the annotations yourself:
.. code-block:: yaml
jms_di_extra:
annotation_patterns:
- JMS\DiExtraBundle\Annotation
- Sonata\AdminBundle\Annotation
Define Admins
^^^^^^^^^^^^^
All you have to do is include ``Sonata\AdminBundle\Annotation`` and define the values you need.
.. code-block:: php
<?php
namespace AcmeBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Annotation as Sonata;
/**
* @Sonata\Admin(
* class="AcmeBundle\Entity\MyEntity",
* id="service id (generated per default)",
* managerType="doctrine_mongodb (orm per default)",
* baseControllerName="SonataAdminBundle:CRUD",
* group="myGroup",
* label="myLabel",
* showInDashboard=true,
* translationDomain="OMG",
* pagerType="",
* persistFilters="",
* icon="<i class='fa fa-folder'></i>",
* keepOpen=false,
* onTop=false
* )
*/
class MyAdmin extends AbstractAdmin
{
}
.. note::
If you need to define custom controllers you can also use jms/di-extra-bundle by using
the DI\Service annotation.

View File

@@ -0,0 +1,317 @@
Architecture
============
The architecture of the ``SonataAdminBundle`` is primarily inspired by the Django Admin
Project, which is truly a great project. More information can be found at the
`Django Project Website`_.
If you followed the instructions on the :doc:`getting_started` page, you should by
now have an ``Admin`` class and an ``Admin`` service. In this chapter, we'll discuss more in
depth how it works.
The Admin Class
---------------
The ``Admin`` class maps a specific model to the rich CRUD interface provided by
``SonataAdminBundle``. In other words, using your ``Admin`` classes, you can configure
what is shown by ``SonataAdminBundle`` in each CRUD action for the associated model.
By now you've seen 3 of those actions in the ``getting started`` page: list,
filter and form (for creation/editing). However, a fully configured ``Admin`` class
can define more actions:
============= =========================================================================
Actions Description
============= =========================================================================
list The fields displayed in the list table
filter The fields available for filtering the list
form The fields used to create/edit the entity
show The fields used to show the entity
batch actions Actions that can be performed on a group of entities (e.g. bulk delete)
============= =========================================================================
The ``Sonata\AdminBundle\Admin\AbstractAdmin`` class is provided as an easy way to
map your models, by extending it. However, any implementation of the
``Sonata\AdminBundle\Admin\AdminInterface`` can be used to define an ``Admin``
service. For each ``Admin`` service, the following required dependencies are
automatically injected by the bundle:
========================= =========================================================================
Class Description
========================= =========================================================================
ConfigurationPool configuration pool where all Admin class instances are stored
ModelManager service which handles specific code relating to your persistence layer (e.g. Doctrine ORM)
FormContractor builds the forms for the edit/create views using the Symfony ``FormBuilder``
ShowBuilder builds the show fields
ListBuilder builds the list fields
DatagridBuilder builds the filter fields
Request the received http request
RouteBuilder allows you to add routes for new actions and remove routes for default actions
RouterGenerator generates the different URLs
SecurityHandler handles permissions for model instances and actions
Validator handles model validation
Translator generates translations
LabelTranslatorStrategy a strategy to use when generating labels
MenuFactory generates the side menu, depending on the current action
========================= =========================================================================
.. note::
Each of these dependencies is used for a specific task, briefly described above.
If you wish to learn more about how they are used, check the respective documentation
chapter. In most cases, you won't need to worry about their underlying implementation.
All of these dependencies have default values that you can override when declaring any of
your ``Admin`` services. This is done using a ``call`` to the matching ``setter`` :
.. configuration-block::
.. code-block:: xml
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument />
<call method="setLabelTranslatorStrategy">
<argument type="service" id="sonata.admin.label.strategy.underscore" />
</call>
</service>
.. code-block:: yaml
services:
app.admin.post:
class: AppBundle\Admin\PostAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Post" }
arguments:
- ~
- AppBundle\Entity\Post
- ~
calls:
- [ setLabelTranslatorStrategy, ["@sonata.admin.label.strategy.underscore"]]
public: true
Here, we declare the same ``Admin`` service as in the :doc:`getting_started` chapter, but using a
different label translator strategy, replacing the default one. Notice that
``sonata.admin.label.strategy.underscore`` is a service provided by ``SonataAdminBundle``,
but you could just as easily use a service of your own.
CRUDController
--------------
The ``CRUDController`` contains the actions you have available to manipulate
your model instances, like create, list, edit or delete. It uses the ``Admin``
class to determine its behavior, like which fields to display in the edit form,
or how to build the list view. Inside the ``CRUDController``, you can access the
``Admin`` class instance via the ``$admin`` variable.
.. note::
`CRUD`_ is an acronym for "Create, Read, Update and Delete"
The ``CRUDController`` is no different from any other Symfony controller, meaning
that you have all the usual options available to you, like getting services from
the Dependency Injection Container (DIC).
This is particularly useful if you decide to extend the ``CRUDController`` to
add new actions or change the behavior of existing ones. You can specify which controller
to use when declaring the ``Admin`` service by passing it as the 3rd argument. For example
to set the controller to ``AppBundle:PostAdmin``:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument>AppBundle:PostAdmin</argument>
<call method="setTranslationDomain">
<argument>AppBundle</argument>
</call>
</service>
.. code-block:: yaml
services:
app.admin.post:
class: AppBundle\Admin\PostAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Post" }
arguments:
- ~
- AppBundle\Entity\Post
- AppBundle:PostAdmin
calls:
- [ setTranslationDomain, [AppBundle]]
public: true
When extending ``CRUDController``, remember that the ``Admin`` class already has
a set of automatically injected dependencies that are useful when implementing several
scenarios. Refer to the existing ``CRUDController`` actions for examples of how to get
the best out of them.
In your overloaded CRUDController you can overload also these methods to limit
the number of duplicated code from SonataAdmin:
* ``preCreate``: called from ``createAction``
* ``preEdit``: called from ``editAction``
* ``preDelete``: called from ``deleteAction``
* ``preShow``: called from ``showAction``
* ``preList``: called from ``listAction``
These methods are called after checking the access rights and after retrieving the object
from database. You can use them if you need to redirect user to some other page under certain conditions.
Fields Definition
-----------------
Your ``Admin`` class defines which of your model's fields will be available in each
action defined in your ``CRUDController``. So, for each action, a list of field mappings
is generated. These lists are implemented using the ``FieldDescriptionCollection`` class
which stores instances of ``FieldDescriptionInterface``. Picking up on our previous
``PostAdmin`` class example:
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
class PostAdmin extends AbstractAdmin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title', 'text', array(
'label' => 'Post Title'
))
->add('author', 'entity', array(
'class' => 'AppBundle\Entity\User'
))
// if no type is specified, SonataAdminBundle tries to guess it
->add('body')
// ...
;
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
->add('author')
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
->add('slug')
->add('author')
;
}
// Fields to be shown on show action
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('id')
->add('title')
->add('slug')
->add('author')
;
}
}
Internally, the provided ``Admin`` class will use these three functions to create three
``FieldDescriptionCollection`` instances:
* ``$formFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
for title, author and body
* ``$filterFieldDescriptions``, containing two ``FieldDescriptionInterface`` instances
for title and author
* ``$listFieldDescriptions``, containing three ``FieldDescriptionInterface`` instances
for title, slug and author
* ``$showFieldDescriptions``, containing four ``FieldDescriptionInterface`` instances
for id, title, slug and author
The actual ``FieldDescription`` implementation is provided by the storage abstraction
bundle that you choose during the installation process, based on the
``BaseFieldDescription`` abstract class provided by ``SonataAdminBundle``.
Each ``FieldDescription`` contains various details about a field mapping. Some of
them are independent of the action in which they are used, like ``name`` or ``type``,
while others are used only in specific actions. More information can be found in the
``BaseFieldDescription`` class file.
In most scenarios, you will not actually need to handle the ``FieldDescription`` yourself.
However, it is important that you know it exists and how it is used, as it sits at the
core of ``SonataAdminBundle``.
Templates
---------
Like most actions, ``CRUDController`` actions use view files to render their output.
``SonataAdminBundle`` provides ready to use views as well as ways to easily customize them.
The current implementation uses ``Twig`` as the template engine. All templates
are located in the ``Resources/views`` directory of the bundle.
There are two base templates, one of these is ultimately used in every action:
* ``SonataAdminBundle::standard_layout.html.twig``
* ``SonataAdminBundle::ajax_layout.html.twig``
Like the names say, one if for standard calls, the other one for AJAX.
The subfolders include Twig files for specific sections of ``SonataAdminBundle``:
Block:
``SonataBlockBundle`` block views. By default there is only one, which
displays all the mapped classes on the dashboard
Button:
Buttons such as ``Add new`` or ``Delete`` that you can see across several
CRUD actions
CRUD:
Base views for every CRUD action, plus several field views for each field type
Core:
Dashboard view, together with deprecated and stub twig files.
Form:
Views related to form rendering
Helper:
A view providing a short object description, as part of a specific form field
type provided by ``SonataAdminBundle``
Pager:
Pagination related view files
These will be discussed in greater detail in the specific :doc:`templates` section, where
you will also find instructions on how to configure ``SonataAdminBundle`` to use your templates
instead of the default ones.
Managing ``Admin`` Service
--------------------------
Your ``Admin`` service definitions are parsed when Symfony is loaded, and handled by
the ``Pool`` class. This class, available as the ``sonata.admin.pool`` service from the
DIC, handles the ``Admin`` classes, lazy-loading them on demand (to reduce overhead)
and matching each of them to a group. It is also responsible for handling the top level
template files, administration panel title and logo.
.. _`Django Project Website`: http://www.djangoproject.com/
.. _`CRUD`: http://en.wikipedia.org/wiki/CRUD

View File

@@ -0,0 +1,250 @@
Batch actions
=============
Batch actions are actions triggered on a set of selected objects. By default,
Admins have a ``delete`` action which allows you to remove several entries at once.
Defining new actions
--------------------
To create a new custom batch action which appears in the list view follow these steps:
Override ``configureBatchActions()`` in your ``Admin`` class to define the new batch actions
by adding them to the ``$actions`` array. Each key represent a batch action and could contain these settings:
- **label**: The name to use when offering this option to users, should be passed through the translator
(default: the label is generated via the labelTranslatorStrategy)
- **translation_domain**: The domain which will be used to translate the key.
(default: the translation domain of the admin)
- **ask_confirmation**: defaults to true and means that the user will be asked
for confirmation before the batch action is processed
For example, lets define a new ``merge`` action which takes a number of source items and
merges them onto a single target item. It should only be available when two conditions are met:
- the EDIT and DELETE routes exist for this Admin (have not been disabled)
- the logged in administrator has EDIT and DELETE permissions
.. code-block:: php
<?php
// in your Admin class
public function configureBatchActions($actions)
{
if (
$this->hasRoute('edit') && $this->hasAccess('edit') &&
$this->hasRoute('delete') && $this->hasAccess('delete')
) {
$actions['merge'] = array(
'ask_confirmation' => true
);
}
return $actions;
}
Define the core action logic
----------------------------
The method ``batchAction<MyAction>`` will be executed to process your batch in your ``CRUDController`` class. The selected
objects are passed to this method through a query argument which can be used to retrieve them.
If for some reason it makes sense to perform your batch action without the default selection
method (for example you defined another way, at template level, to select model at a lower
granularity), the passed query is ``null``.
.. note::
You can check how to declare your own ``CRUDController`` class in the Architecture section.
.. code-block:: php
<?php
// src/AppBundle/Controller/CRUDController.php
namespace AppBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class CRUDController extends BaseController
{
/**
* @param ProxyQueryInterface $selectedModelQuery
* @param Request $request
*
* @return RedirectResponse
*/
public function batchActionMerge(ProxyQueryInterface $selectedModelQuery, Request $request = null)
{
$this->admin->checkAccess('edit');
$this->admin->checkAccess('delete');
$modelManager = $this->admin->getModelManager();
$target = $modelManager->find($this->admin->getClass(), $request->get('targetId'));
if ($target === null){
$this->addFlash('sonata_flash_info', 'flash_batch_merge_no_target');
return new RedirectResponse(
$this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
);
}
$selectedModels = $selectedModelQuery->execute();
// do the merge work here
try {
foreach ($selectedModels as $selectedModel) {
$modelManager->delete($selectedModel);
}
$modelManager->update($selectedModel);
} catch (\Exception $e) {
$this->addFlash('sonata_flash_error', 'flash_batch_merge_error');
return new RedirectResponse(
$this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
);
}
$this->addFlash('sonata_flash_success', 'flash_batch_merge_success');
return new RedirectResponse(
$this->admin->generateUrl('list', array('filter' => $this->admin->getFilterParameters()))
);
}
// ...
}
(Optional) Overriding the batch selection template
--------------------------------------------------
A merge action requires two kinds of selection: a set of source objects to merge from
and a target object to merge into. By default, batch_actions only let you select one set
of objects to manipulate. We can override this behavior by changing our list template
(``list__batch.html.twig``) and adding a radio button to choose the target object.
.. code-block:: html+jinja
{# src/AppBundle/Resources/views/CRUD/list__batch.html.twig #}
{# see SonataAdminBundle:CRUD:list__batch.html.twig for the current default template #}
{% extends admin.getTemplate('base_list_field') %}
{% block field %}
<input type="checkbox" name="idx[]" value="{{ admin.id(object) }}" />
{# the new radio button #}
<input type="radio" name="targetId" value="{{ admin.id(object) }}" />
{% endblock %}
And add this:
.. code-block:: php
<?php
// src/AppBundle/AppBundle.php
public function getParent()
{
return 'SonataAdminBundle';
}
See the `Symfony bundle overriding mechanism`_
for further explanation of overriding bundle templates.
(Optional) Overriding the default relevancy check function
----------------------------------------------------------
By default, batch actions are not executed if no object was selected, and the user is notified of
this lack of selection. If your custom batch action needs more complex logic to determine if
an action can be performed or not, just define a ``batchAction<MyAction>IsRelevant`` method
(e.g. ``batchActionMergeIsRelevant``) in your ``CRUDController`` class. This check is performed
before the user is asked for confirmation, to make sure there is actually something to confirm.
This method may return three different values:
- ``true``: The batch action is relevant and can be applied.
- ``false``: Same as above, with the default "action aborted, no model selected" notification message.
- ``string``: The batch action is not relevant given the current request parameters
(for example the ``target`` is missing for a ``merge`` action).
The returned string is a message displayed to the user.
.. code-block:: php
<?php
// src/AppBundle/Controller/CRUDController.php
namespace AppBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController as BaseController;
use Symfony\Component\HttpFoundation\Request;
class CRUDController extends BaseController
{
public function batchActionMergeIsRelevant(array $selectedIds, $allEntitiesSelected, Request $request = null)
{
// here you have access to all POST parameters, if you use some custom ones
// POST parameters are kept even after the confirmation page.
$parameterBag = $request->request;
// check that a target has been chosen
if (!$parameterBag->has('targetId')) {
return 'flash_batch_merge_no_target';
}
$targetId = $parameterBag->get('targetId');
// if all entities are selected, a merge can be done
if ($allEntitiesSelected) {
return true;
}
// filter out the target from the selected models
$selectedIds = array_filter($selectedIds,
function($selectedId) use($targetId){
return $selectedId !== $targetId;
}
);
// if at least one but not the target model is selected, a merge can be done.
return count($selectedIds) > 0;
}
// ...
}
(Optional) Executing a pre batch hook
-------------------------------------
In your admin class you can create a ``preBatchAction`` method to execute something before doing the batch action.
The main purpose of this method is to alter the query or the list of selected ids.
.. code-block:: php
<?php
// in your Admin class
public function preBatchAction($actionName, ProxyQueryInterface $query, array & $idx, $allElements)
{
// altering the query or the idx array
$foo = $query->getParameter('foo')->getValue();
// Doing something with the foo object
// ...
$query->setParameter('foo', $bar);
}
.. _Symfony bundle overriding mechanism: http://symfony.com/doc/current/cookbook/bundles/inheritance.html

View File

@@ -0,0 +1,28 @@
The breadcrumbs builder
=======================
The ``sonata.admin.breadcrumbs_builder`` service is used in the layout of every
page to compute the underlying data for two breadcrumbs:
* one as text, appearing in the ``title`` tag of the document's ``head`` tag;
* the other as html, visible as an horizontal bar at the top of the page.
Getting the breadcrumbs for a given action of a given admin is done like this:
.. code-block:: php
<?php
$this->get('sonata.admin.breadcrumbs_builder')->getBreadcrumbs($admin, $action);
Configuration
-------------
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
breadcrumbs:
# use this to change the default route used to generate the link to the parent object inside a breadcrumb, when in a child admin
child_admin_route: edit

View File

@@ -0,0 +1,129 @@
Create child admins
-------------------
Let us say you have a ``PlaylistAdmin`` and a ``VideoAdmin``. You can optionally declare the ``VideoAdmin``
to be a child of the ``PlaylistAdmin``. This will create new routes like, for example, ``/playlist/{id}/video/list``,
where the videos will automatically be filtered by post.
To do this, you first need to call the ``addChild`` method in your ``PlaylistAdmin`` service configuration:
.. configuration-block::
.. code-block:: xml
<!-- app/config/config.xml -->
<service id="sonata.admin.playlist" class="AppBundle\Admin\PlaylistAdmin">
<!-- ... -->
<call method="addChild">
<argument type="service" id="sonata.admin.video" />
</call>
</service>
Then, you have to set the VideoAdmin ``parentAssociationMapping`` attribute to ``playlist`` :
.. code-block:: php
<?php
namespace AppBundle\Admin;
// ...
class VideoAdmin extends AbstractAdmin
{
protected $parentAssociationMapping = 'playlist';
// OR
public function getParentAssociationMapping()
{
return 'playlist';
}
}
To display the ``VideoAdmin`` extend the menu in your ``PlaylistAdmin`` class:
.. code-block:: php
<?php
namespace AppBundle\Admin;
use Knp\Menu\ItemInterface as MenuItemInterface;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Admin\AdminInterface;
class PlaylistAdmin extends AbstractAdmin
{
// ...
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
if (!$childAdmin && !in_array($action, array('edit', 'show'))) {
return;
}
$admin = $this->isChild() ? $this->getParent() : $this;
$id = $admin->getRequest()->get('id');
$menu->addChild('View Playlist', array('uri' => $admin->generateUrl('show', array('id' => $id))));
if ($this->isGranted('EDIT')) {
$menu->addChild('Edit Playlist', array('uri' => $admin->generateUrl('edit', array('id' => $id))));
}
if ($this->isGranted('LIST')) {
$menu->addChild('Manage Videos', array(
'uri' => $admin->generateUrl('sonata.admin.video.list', array('id' => $id))
));
}
}
}
It also possible to set a dot-separated value, like ``post.author``, if your parent and child admins are not directly related.
Be wary that being a child admin is optional, which means that regular routes
will be created regardless of whether you actually need them or not. To get rid
of them, you may override the ``configureRoutes`` method::
<?php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Route\RouteCollection;
class VideoAdmin extends AbstractAdmin
{
protected $parentAssociationMapping = 'playlist';
protected function configureRoutes(RouteCollection $collection)
{
if ($this->isChild()) {
// This is the route configuration as a child
$collection->clearExcept(['show', 'edit']);
return;
}
// This is the route configuration as a parent
$collection->clear();
}
}
You can nest admins as deep as you wish.
Let's say you want to add comments to videos.
You can then add your ``CommentAdmin`` admin service as a child of
the ``VideoAdmin`` admin service.
Finally, the admin interface will look like this:
.. figure:: ../images/child_admin.png
:align: center
:alt: Child admin interface
:width: 700px

View File

@@ -0,0 +1,46 @@
Inline Validation
=================
The inline validation code is now part of the SonataCoreBundle. You can refer to the related documentation for more information.
The above examples show how the integration has been done with the SonataAdminBundle. For completeness, it's worth remembering that
the ``Admin`` class itself contains an empty ``validate`` method. This is automatically called, so you can override it in your own admin class:
.. code-block:: php
// add this to your existing use statements
use Sonata\CoreBundle\Validator\ErrorElement;
class MyAdmin extends AbstractAdmin
{
// add this method
public function validate(ErrorElement $errorElement, $object)
{
$errorElement
->with('name')
->assertLength(array('max' => 32))
->end()
;
}
Troubleshooting
---------------
Make sure your validator method is being called. If in doubt, try throwing an exception:
.. code-block:: php
public function validate(ErrorElement $errorElement, $object)
{
throw new \Exception(__METHOD__);
}
There should not be any validation_groups defined for the form. If you have code like the example below in
your ``Admin`` class, remove the 'validation_groups' entry, the whole $formOptions property or set validation_groups
to an empty array:
.. code-block:: php
protected $formOptions = array(
'validation_groups' => array()
);

View File

@@ -0,0 +1,222 @@
Configuration
=============
.. note::
This page will be removed soon, as it's content is being improved and moved to
other pages of the documentation. Please refer to each section's documentation for up-to-date
information on SonataAdminBundle configuration options.
Configuration
-------------
Configuration options
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
security:
# the default value
handler: sonata.admin.security.handler.role
# use this service if you want ACL
handler: sonata.admin.security.handler.acl
Full Configuration Options
--------------------------
.. configuration-block::
.. code-block:: yaml
# Default configuration for extension with alias: "sonata_admin"
sonata_admin:
security:
handler: sonata.admin.security.handler.noop
information:
# Prototype
id: []
admin_permissions:
# Defaults:
- CREATE
- LIST
- DELETE
- UNDELETE
- EXPORT
- OPERATOR
- MASTER
object_permissions:
# Defaults:
- VIEW
- EDIT
- DELETE
- UNDELETE
- OPERATOR
- MASTER
- OWNER
acl_user_manager: null
title: 'Sonata Admin'
title_logo: bundles/sonataadmin/logo_title.png
options:
html5_validate: true
# Auto order groups and admins by label or id
sort_admins: false
confirm_exit: true
use_select2: true
use_icheck: true
use_bootlint: false
use_stickyforms: true
pager_links: null
form_type: standard
dropdown_number_groups_per_colums: 2
title_mode: ~ # One of "single_text"; "single_image"; "both"
# Enable locking when editing an object, if the corresponding object manager supports it.
lock_protection: false
# Enable automatic registration of annotations with JMSDiExtraBundle
enable_jms_di_extra_autoregistration: true
dashboard:
groups:
# Prototype
id:
label: ~
label_catalogue: ~
icon: '<i class="fa fa-folder"></i>'
provider: ~
items:
admin: ~
label: ~
route: ~
route_params: []
item_adds: []
roles: []
blocks:
type: ~
roles: []
settings:
# Prototype
id: ~
position: right
class: col-md-4
admin_services:
model_manager: null
form_contractor: null
show_builder: null
list_builder: null
datagrid_builder: null
translator: null
configuration_pool: null
route_generator: null
validator: null
security_handler: null
label: null
menu_factory: null
route_builder: null
label_translator_strategy: null
pager_type: null
templates:
form: []
filter: []
view:
# Prototype
id: ~
templates:
user_block: 'SonataAdminBundle:Core:user_block.html.twig'
add_block: 'SonataAdminBundle:Core:add_block.html.twig'
layout: 'SonataAdminBundle::standard_layout.html.twig'
ajax: 'SonataAdminBundle::ajax_layout.html.twig'
dashboard: 'SonataAdminBundle:Core:dashboard.html.twig'
search: 'SonataAdminBundle:Core:search.html.twig'
list: 'SonataAdminBundle:CRUD:list.html.twig'
filter: 'SonataAdminBundle:Form:filter_admin_fields.html.twig'
show: 'SonataAdminBundle:CRUD:show.html.twig'
show_compare: 'SonataAdminBundle:CRUD:show_compare.html.twig'
edit: 'SonataAdminBundle:CRUD:edit.html.twig'
preview: 'SonataAdminBundle:CRUD:preview.html.twig'
history: 'SonataAdminBundle:CRUD:history.html.twig'
acl: 'SonataAdminBundle:CRUD:acl.html.twig'
history_revision_timestamp: 'SonataAdminBundle:CRUD:history_revision_timestamp.html.twig'
action: 'SonataAdminBundle:CRUD:action.html.twig'
select: 'SonataAdminBundle:CRUD:list__select.html.twig'
list_block: 'SonataAdminBundle:Block:block_admin_list.html.twig'
search_result_block: 'SonataAdminBundle:Block:block_search_result.html.twig'
short_object_description: 'SonataAdminBundle:Helper:short-object-description.html.twig'
delete: 'SonataAdminBundle:CRUD:delete.html.twig'
batch: 'SonataAdminBundle:CRUD:list__batch.html.twig'
batch_confirmation: 'SonataAdminBundle:CRUD:batch_confirmation.html.twig'
inner_list_row: 'SonataAdminBundle:CRUD:list_inner_row.html.twig'
outer_list_rows_mosaic: 'SonataAdminBundle:CRUD:list_outer_rows_mosaic.html.twig'
outer_list_rows_list: 'SonataAdminBundle:CRUD:list_outer_rows_list.html.twig'
outer_list_rows_tree: 'SonataAdminBundle:CRUD:list_outer_rows_tree.html.twig'
base_list_field: 'SonataAdminBundle:CRUD:base_list_field.html.twig'
pager_links: 'SonataAdminBundle:Pager:links.html.twig'
pager_results: 'SonataAdminBundle:Pager:results.html.twig'
tab_menu_template: 'SonataAdminBundle:Core:tab_menu_template.html.twig'
knp_menu_template: 'SonataAdminBundle:Menu:sonata_menu.html.twig'
assets:
stylesheets:
# Defaults:
- bundles/sonatacore/vendor/bootstrap/dist/css/bootstrap.min.css
- bundles/sonatacore/vendor/components-font-awesome/css/font-awesome.min.css
- bundles/sonatacore/vendor/ionicons/css/ionicons.min.css
- bundles/sonataadmin/vendor/admin-lte/dist/css/AdminLTE.min.css
- bundles/sonataadmin/vendor/admin-lte/dist/css/skins/skin-black.min.css
- bundles/sonataadmin/vendor/iCheck/skins/square/blue.css
- bundles/sonatacore/vendor/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css
- bundles/sonataadmin/vendor/jqueryui/themes/base/jquery-ui.css
- bundles/sonatacore/vendor/select2/select2.css
- bundles/sonatacore/vendor/select2-bootstrap-css/select2-bootstrap.min.css
- bundles/sonataadmin/vendor/x-editable/dist/bootstrap3-editable/css/bootstrap-editable.css
- bundles/sonataadmin/css/styles.css
- bundles/sonataadmin/css/layout.css
- bundles/sonataadmin/css/tree.css
- bundles/sonataadmin/css/colors.css
javascripts:
# Defaults:
- bundles/sonatacore/vendor/jquery/dist/jquery.min.js
- bundles/sonataadmin/vendor/jquery.scrollTo/jquery.scrollTo.min.js
- bundles/sonatacore/vendor/moment/min/moment.min.js
- bundles/sonataadmin/vendor/jqueryui/ui/minified/jquery-ui.min.js
- bundles/sonataadmin/vendor/jqueryui/ui/minified/i18n/jquery-ui-i18n.min.js
- bundles/sonatacore/vendor/bootstrap/dist/js/bootstrap.min.js
- bundles/sonatacore/vendor/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js
- bundles/sonataadmin/vendor/jquery-form/jquery.form.js
- bundles/sonataadmin/jquery/jquery.confirmExit.js
- bundles/sonataadmin/vendor/x-editable/dist/bootstrap3-editable/js/bootstrap-editable.min.js
- bundles/sonatacore/vendor/select2/select2.min.js
- bundles/sonataadmin/vendor/admin-lte/dist/js/app.min.js
- bundles/sonataadmin/vendor/iCheck/icheck.min.js
- bundles/sonataadmin/vendor/slimScroll/jquery.slimscroll.min.js
- bundles/sonataadmin/vendor/waypoints/lib/jquery.waypoints.min.js
- bundles/sonataadmin/vendor/waypoints/lib/shortcuts/sticky.min.js
- bundles/sonataadmin/Admin.js
- bundles/sonataadmin/treeview.js
extensions:
# Prototype
id:
admins: []
excludes: []
implements: []
extends: []
instanceof: []
uses: []
persist_filters: false
show_mosaic_button: true
global_search:
show_empty_boxes: show

View File

@@ -0,0 +1,120 @@
Console/Command-Line Commands
=============================
SonataAdminBundle provides the following console commands:
* ``cache:create-cache-class``
* ``sonata:admin:generate``
* ``sonata:admin:list``
* ``sonata:admin:explain``
* ``sonata:admin:setup-acl``
* ``sonata:admin:generate-object-acl``
cache:create-cache-class
------------------------
The ``cache:create-cache-class`` command generates the cache class
(``app/cache/...env.../classes.php``) from the classes.map file.
Usage example:
.. code-block:: bash
$ php app/console cache:create-cache-class
sonata:admin:generate
---------------------
The ``sonata:admin:generate`` command generates a new Admin class based on the given model
class, registers it as a service and potentially creates a new controller.
As an argument you need to specify the fully qualified model class.
All passed arguments and options are used as default values in interactive mode.
You can disable the interactive mode with ``--no-interaction`` option.
The command require the SensioGeneratorBundle_ to work. If you don't already have it, you can install it with :
.. code-block:: bash
$ composer require --dev sensio/generator-bundle
=============== ===============================================================================================================================
Options Description
=============== ===============================================================================================================================
**bundle** the bundle name (the default value is determined by the given model class, e.g. "AppBundle" or "YourNSFooBundle")
**admin** the admin class basename (by default this adds "Admin" to the model class name, e.g. "BarAdmin")
**controller** the controller class basename (by default this adds "AdminController" to the model class name, e.g. "BarAdminController")
**manager** the model manager type (by default this is the first registered model manager type, e.g. "orm")
**services** the services YAML file (the default value is "services.yml" or "admin.yml" if it already exist)
**id** the admin service ID (the default value is combination of the bundle name and admin class basename like "your_ns_foo.admin.bar")
=============== ===============================================================================================================================
Usage example:
.. code-block:: bash
$ php app/console sonata:admin:generate AppBundle/Entity/Foo
sonata:admin:list
-----------------
To see which admin services are available use the ``sonata:admin:list`` command.
It prints all the admin service ids available in your application. This command
gets the ids from the ``sonata.admin.pool`` service where all the available admin
services are registered.
Usage example:
.. code-block:: bash
$ php app/console sonata:admin:list
.. figure:: ../images/console_admin_list.png
:align: center
:alt: List command
:width: 700px
List command
sonata:admin:explain
--------------------
The ``sonata:admin:explain`` command prints details about the admin of a model.
As an argument you need to specify the admin service id of the Admin to explain.
Usage example:
.. code-block:: bash
$ php app/console sonata:admin:explain sonata.news.admin.post
.. figure:: ../images/console_admin_explain.png
:align: center
:alt: Explain command
:width: 700px
Explain command
sonata:admin:setup-acl
----------------------
The ``sonata:admin:setup-acl`` command updates ACL definitions for all Admin
classes available in ``sonata.admin.pool``. For instance, every time you create a
new ``Admin`` class, you can create its ACL by using the ``sonata:admin:setup-acl``
command. The ACL database will be automatically updated with the latest masks
and roles.
Usage example:
.. code-block:: bash
$ php app/console sonata:admin:setup-acl
sonata:admin:generate-object-acl
--------------------------------
The ``sonata:admin:generate-object-acl`` is an interactive command which helps
you to generate ACL entities for the objects handled by your Admins. See the help
of the command for more information.
.. _SensioGeneratorBundle: http://symfony.com/doc/current/bundles/SensioGeneratorBundle/index.html

View File

@@ -0,0 +1,419 @@
Dashboard
=========
The Dashboard is the main landing page. By default it lists your mapped models,
as defined by your ``Admin`` services. This is useful to help you start using
``SonataAdminBundle`` right away, but there is much more that you can do to take
advantage of the Dashboard.
The Dashboard is, by default, available at ``/admin/dashboard``, which is handled by
the ``SonataAdminBundle:Core:dashboard`` controller action. The default view file for
this action is ``SonataAdminBundle:Core:dashboard.html.twig``, but you can change
this in your ``config.yml``:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
templates:
dashboard: SonataAdminBundle:Core:dashboard.html.twig
.. note::
This view, like most of the ``SonataAdminBundle`` views, extends a global
template file, which also contains significant parts to the page. More information
about this is available in the :doc:`templates` chapter.
Blocks
------
The Dashboard is actually built using ``Blocks`` from ``SonataBlockBundle``. You
can learn more about this bundle and how to build your own Blocks on the
`SonataBlock documentation page`_.
The ``Admin`` list block
------------------------
The ``Admin`` list is a ``Block`` that fetches information from the ``Admin`` service's
``Pool`` and prints it in the nicely formatted list you have on your default Dashboard.
The ``Admin`` list is defined by the ``sonata.admin.block.admin_list`` service, which is
implemented by the ``Block\AdminListBlockService`` class. It is then rendered using the
``SonataAdminBundle:Block:block_admin_list.html.twig`` template file.
Feel free to take a look at these files. You'll find the code rather short and easy to
understand, and it will be a great help when implementing your own blocks.
Configuring the ``Admin`` list
------------------------------
As you probably noticed by now, the ``Admin`` list groups ``Admin`` mappings together.
There are several ways in which you can configure these groups.
By default the admins are ordered the way you defined them. With the setting ``sort_admins``
groups and admins will be ordered by their respective label with a fallback to the admin id.
Using the ``Admin`` service declaration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The first, and most commonly used, method is to set a group when defining your ``Admin``
services:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm"
group="Content"
label="Post" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument />
</service>
.. code-block:: yaml
services:
app.admin.post:
class: AppBundle\Admin\PostAdmin
tags:
- name: sonata.admin
manager_type: orm
group: "Content"
label: "Post"
arguments:
- ~
- AppBundle\Entity\Post
- ~
public: true
In these examples, notice the ``group`` tag, stating that this particular ``Admin``
service belongs to the ``Content`` group.
.. configuration-block::
.. code-block:: xml
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm"
group="app.admin.group.content"
label="app.admin.model.post" label_catalogue="AppBundle" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument />
</service>
.. code-block:: yaml
services:
app.admin.post:
class: AppBundle\Admin\PostAdmin
tags:
- name: sonata.admin
manager_type: orm
group: "app.admin.group.content"
label: "app.admin.model.post"
label_catalogue: "AppBundle"
arguments:
- ~
- AppBundle\Entity\Post
- ~
In this example, the labels are translated by ``AppBundle``, using the given
``label_catalogue``. So, you can use the above examples to support multiple languages
in your project.
.. note::
You can use parameters (e.g. ``%app_admin.group_post%``) for the group names
in either scenario.
Using the ``config.yml``
^^^^^^^^^^^^^^^^^^^^^^^^
You can also configure the ``Admin`` list in your ``config.yml`` file. This
configuration method overrides any settings defined in the Admin service
declarations.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
dashboard:
groups:
app.admin.group.content:
label: app.admin.group.content
label_catalogue: AppBundle
items:
- app.admin.post
app.admin.group.blog:
items: ~
item_adds:
- sonata.admin.page
roles: [ ROLE_ONE, ROLE_TWO ]
app.admin.group.misc: ~
.. note::
This is an academic, full configuration, example. In real cases, you will usually
not need to use all the displayed options. To use a default value for any setting
either leave out that key or use the ``~`` value for that option.
This configuration specifies that the ``app.admin.group.content`` group uses the
``app.admin.group.content`` label, which is translated using the ``AppBundle``
translation catalogue (the same label and translation configuration that we declared
previously, in the service definition example).
It also states that the ``app.admin.group.content`` group contains just the
``app.admin.post`` ``Admin`` mapping, meaning that any other ``Admin`` services
declared as belonging to this group will not be displayed here.
Secondly, we declare a ``app.admin.group.blog`` group as having all its default items
(i.e. the ones specified in the ``Admin`` service declarations), plus an *additional*
``sonata.admin.page`` mapping, that was not initially part of this group.
We also use the ``roles`` option here, which means that only users with the ``ROLE_ONE``
or ``ROLE_TWO`` privileges will be able to see this group, as opposed to the default setting
which allows everyone to see a given group. Users with ``ROLE_SUPER_ADMIN`` are always
able to see groups that would otherwise be hidden by this configuration option.
The third group, ``app.admin.group.misc``, is set up as a group which uses all its
default values, as declared in the service declarations.
Adding more Blocks
------------------
Like we said before, the Dashboard comes with a default ``Admin`` list block, but
you can create and add more blocks to it.
.. figure:: ../images/dashboard.png
:align: center
:alt: Dashboard
:width: 500
In this screenshot, in addition to the default ``Admin`` list block on the left, we added
a text block and RSS feed block on the right. The configuration for this scenario would be:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
dashboard:
blocks:
-
position: left
type: sonata.admin.block.admin_list
-
position: right
type: sonata.block.service.text
settings:
content: >
<h2>Welcome to the Sonata Admin</h2>
<p>This is a <code>sonata.block.service.text</code> from the Block
Bundle, you can create and add new block in these area by configuring
the <code>sonata_admin</code> section.</p> <br /> For instance, here
a RSS feed parser (<code>sonata.block.service.rss</code>):
-
position: right
type: sonata.block.service.rss
roles: [POST_READER]
settings:
title: Sonata Project's Feeds
url: https://sonata-project.org/blog/archive.rss
.. note::
Blocks may accept/require additional settings to be passed in order to
work properly. Refer to the associated documentation/implementation to
get more information on each block's options and requirements.
You can also configure the ``roles`` section to configure users that can
view the block.
Display two ``Admin`` list blocks with different dashboard groups
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The same block can have multiple instances, and be displayed multiple times
across the Dashboard using different configuration settings for each instance.
A particular example is the ``Admin`` list block, which can be configured to
suit this scenario.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
dashboard:
blocks:
# display two dashboard blocks
-
position: left
type: sonata.admin.block.admin_list
settings:
groups: [sonata_page1, sonata_page2]
-
position: right
type: sonata.admin.block.admin_list
settings:
groups: [sonata_page3]
groups:
sonata_page1:
items:
- sonata.page.admin.myitem1
sonata_page2:
items:
- sonata.page.admin.myitem2
- sonata.page.admin.myitem3
sonata_page3:
items:
- sonata.page.admin.myitem4
In this example, you would have two ``admin_list`` blocks on your dashboard, each
of them containing just the respectively configured groups.
.. _`SonataBlock documentation page`: https://sonata-project.org/bundles/block/master/doc/index.html
Statistic Block
~~~~~~~~~~~~~~~
A statistic block can be used to display a simple counter with a color, an font awesome icon and a text. A
counter is related to the filters from one admin
.. configuration-block::
.. code-block:: yaml
sonata_admin:
dashboard:
blocks:
-
class: col-lg-3 col-xs-6 # twitter bootstrap responsive code
position: top # zone in the dashboard
type: sonata.admin.block.stats # block id
settings:
code: sonata.page.admin.page # admin code - service id
icon: fa-magic # font awesome icon
text: Edited Pages
color: bg-yellow # colors: bg-green, bg-red and bg-aqua
filters: # filter values
edited: { value: 1 }
Dashboard Layout
~~~~~~~~~~~~~~~~
Supported positions right now are the following:
* top
* left
* center
* right
* bottom
The layout is as follows:
.. code-block:: bash
TOP TOP TOP
LEFT CENTER RIGHT
LEFT CENTER RIGHT
LEFT CENTER RIGHT
BOTTOM BOTTOM BOTTOM
On ``top`` and ``bottom`` positions, you can also specify an optional ``class`` option to set the width of the block.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
dashboard:
blocks:
# display dashboard block in the top zone with a col-md-6 css class
-
position: top
class: col-md-6
type: sonata.admin.block.admin_list
Configuring what actions are available for each item on the dashboard
---------------------------------------------------------------------
By default. A "list" and a "create" option are available for each item on the
dashboard. If you created a custom action and want to display it along the
other two on the dashboard, you can do so by overriding the
``getDashboardActions()`` method of your admin class:
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
// ...
public function getDashboardActions()
{
$actions = parent::getDashboardActions();
$actions['import'] = array(
'label' => 'Import',
'url' => $this->generateUrl('import'),
'icon' => 'import',
'translation_domain' => 'SonataAdminBundle', // optional
'template' => 'SonataAdminBundle:CRUD:dashboard__action.html.twig', // optional
);
return $actions;
}
}
You can also hide an action from the dashboard by unsetting it:
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
// ...
public function getDashboardActions()
{
$actions = parent::getDashboardActions();
unset($actions['list']);
return $actions;
}
}
If you do this, you need to be aware that the action is only hidden. it will
still be available by directly calling its URL, unless you prevent that using
proper security measures (e.g. ACL or role based).

View File

@@ -0,0 +1,49 @@
Events
======
An event mechanism is available to add an extra entry point to extend an Admin instance.
ConfigureEvent
~~~~~~~~~~~~~~
This event is generated when a form, list, show, datagrid is configured. The event names are:
- ``sonata.admin.event.configure.form``
- ``sonata.admin.event.configure.list``
- ``sonata.admin.event.configure.datagrid``
- ``sonata.admin.event.configure.show``
PersistenceEvent
~~~~~~~~~~~~~~~~
This event is generated when a persistency layer update, save or delete an object. The event names are:
- ``sonata.admin.event.persistence.pre_update``
- ``sonata.admin.event.persistence.post_update``
- ``sonata.admin.event.persistence.pre_persist``
- ``sonata.admin.event.persistence.post_persist``
- ``sonata.admin.event.persistence.pre_remove``
- ``sonata.admin.event.persistence.post_remove``
ConfigureQueryEvent
~~~~~~~~~~~~~~~~~~~
This event is generated when a list query is defined. The event name is: ``sonata.admin.event.configure.query``
BlockEvent
~~~~~~~~~~
Block events help you customize your templates. Available events are :
- ``sonata.admin.dashboard.top``
- ``sonata.admin.dashboard.bottom``
- ``sonata.admin.list.table.top``
- ``sonata.admin.list.table.bottom``
- ``sonata.admin.edit.form.top``
- ``sonata.admin.edit.form.bottom``
- ``sonata.admin.show.top``
- ``sonata.admin.show.bottom``
If you want more information about block events, you should check the
`"Event" section of block bundle documentation <https://sonata-project.org/bundles/block/master/doc/reference/events.html>`_.

View File

@@ -0,0 +1,128 @@
Extensions
==========
Admin extensions allow you to add or change features of one or more Admin instances. To create an extension your class
must implement the interface ``Sonata\AdminBundle\Admin\AdminExtensionInterface`` and be registered as a service. The
interface defines a number of functions which you can use to customize the edit form, list view, form validation,
alter newly created objects and other admin features.
.. code-block:: php
use Sonata\AdminBundle\Admin\AbstractAdminExtension;
use Sonata\AdminBundle\Form\FormMapper;
class PublishStatusAdminExtension extends AbstractAdminExtension
{
public function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('status', 'choice', array(
'choices' => array(
'draft' => 'Draft',
'published' => 'Published',
),
))
;
}
}
Configuration
~~~~~~~~~~~~~
There are two ways to configure your extensions and connect them to an admin.
You can include this information in the service definition of your extension.
Add the tag *sonata.admin.extension* and use the *target* attribute to point to
the admin you want to modify. Please note you can specify as many tags you want.
Set the *global* attribute to *true* and the extension will be added to all admins.
The *priority* attribute is *0* by default and can be a positive or negative integer.
The higher the priority, the earlier it's executed.
.. configuration-block::
.. code-block:: yaml
services:
app.publish.extension:
class: AppBundle\Admin\Extension\PublishStatusAdminExtension
tags:
- { name: sonata.admin.extension, target: app.admin.article }
- { name: sonata.admin.extension, target: app.admin.blog }
app.order.extension:
class: AppBundle\Admin\Extension\OrderAdminExtension
tags:
- { name: sonata.admin.extension, global: true }
app.important.extension:
class: AppBundle\Admin\Extension\ImportantAdminExtension
tags:
- { name: sonata.admin.extension, priority: 5 }
The second option is to add it to your config.yml file.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
extensions:
app.publish.extension:
admins:
- app.admin.article
Using the ``config.yml`` file has some advantages, it allows you to keep your configuration centralized and it provides some
extra options you can use to wire your extensions in a more dynamic way. This means you can change the behaviour of all
admins that manage a class of a specific type.
admins:
specify one or more admin service ids to which the Extension should be added
excludes:
specify one or more admin service ids to which the Extension should not be added (this will prevent it matching
any of the other settings)
extends:
specify one or more classes. If the managed class of an admin extends one of the specified classes the extension
will be added to that admin.
implements:
specify one or more interfaces. If the managed class of an admin implements one of the specified interfaces the
extension will be added to that admin.
instanceof:
specify one or more classes. If the managed class of an admin extends one of the specified classes or is an instance
of that class the extension will be added to that admin.
uses:
Requires PHP >= 5.4.0. Specify one or more traits. If the managed class of an admin uses one of the specified traits the extension will be
added to that admin.
priority:
Can be a positive or negative integer. The higher the priority, the earlier its executed.
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
extensions:
app.publish.extension:
admins:
- app.admin.article
implements:
- AppBundle\Publish\PublishStatusInterface
excludes:
- app.admin.blog
- app.admin.news
extends:
- AppBundle\Document\Blog
instanceof:
- AppBundle\Document\Page
uses:
- AppBundle\Trait\Timestampable

View File

@@ -0,0 +1,254 @@
Field Types
===========
List and Show Actions
---------------------
There are many field types that can be used in the list and show action :
============ =============================================
Fieldtype Description
============ =============================================
array display value from an array
boolean display a green or red picture dependant on the boolean value
date display a formatted date. Accepts an optional ``format`` parameter
datetime display a formatted date and time. Accepts an optional ``format`` and ``timezone`` parameter
text display a text
textarea display a textarea
trans translate the value with a provided ``catalogue`` option
string display a text
number display a number
currency display a number with a provided ``currency`` option
percent display a percentage
choice uses the given value as index for the ``choices`` array and displays (and optionally translates) the matching value
url display a link
html display (and optionally truncate or strip tags from) raw html
============ =============================================
Theses types accept an ``editable`` parameter to edit the value from within the list action.
This is currently limited to scalar types (text, integer, url...) and choice types with association field.
.. note::
If the ``SonataIntlBundle`` is installed in the project some template types
will be changed to use localized information.
Option for currency type must be an official ISO code, example : EUR for "euros".
List of ISO codes : `http://en.wikipedia.org/wiki/List_of_circulating_currencies <http://en.wikipedia.org/wiki/List_of_circulating_currencies>`_
In ``date`` and ``datetime`` field types, ``format`` pattern must match twig's
``date`` filter specification, available at: `http://twig.sensiolabs.org/doc/filters/date.html <http://twig.sensiolabs.org/doc/filters/date.html>`_
In ``datetime`` field types, ``timezone`` syntax must match twig's
``date`` filter specification, available at: `http://twig.sensiolabs.org/doc/filters/date.html <http://twig.sensiolabs.org/doc/filters/date.html>`_
and php timezone list: `https://php.net/manual/en/timezones.php <https://php.net/manual/en/timezones.php>`_
You can use in lists what `view-timezone <http://symfony.com/doc/current/reference/forms/types/datetime.html#view-timezone>`_ allows on forms,
a way to render the date in the user timezone.
.. code-block:: php
public function configureListFields(ListMapper $listMapper)
{
$listMapper
// store date in UTC but display is in the user timezone
->add('date', null, array(
'format' => 'Y-m-d H:i',
'timezone' => 'America/New_York'
))
;
}
More types might be provided based on the persistency layer defined. Please refer to their
related documentations.
Choice
^^^^^^
.. code-block:: php
public function configureListFields(ListMapper $listMapper)
{
// For the value `prog`, the displayed text is `In progress`. The `AppBundle` catalogue will be used to translate `In progress` message.
$listMapper
->add('status', 'choice', array(
'choices' => array(
'prep' => 'Prepared',
'prog' => 'In progress',
'done' => 'Done'
),
'catalogue' => 'AppBundle'
))
;
}
The ``choice`` field type also supports multiple values that can be separated by a ``delimiter``.
.. code-block:: php
public function configureListFields(ListMapper $listMapper)
{
// For the value `array('r', 'b')`, the displayed text ist `red | blue`.
$listMapper
->add('colors', 'choice', array(
'multiple' => true,
'delimiter' => ' | ',
'choices' => array(
'r' => 'red',
'g' => 'green',
'b' => 'blue'
)
))
;
}
.. note::
The default delimiter is a comma ``,``.
URL
^^^
Display URL link to external website or controller action.
You can use the following parameters:
====================================== ==================================================================
Parameter Description
====================================== ==================================================================
**hide_protocol** remove protocol part from the link text
**url** URL address (e.g. ``http://example.com``)
**attributes** array of html tag attributes (e.g. ``array('target' => '_blank')``)
**route.name** route name (e.g. ``acme_blog_homepage``)
**route.parameters** array of route parameters (e.g. ``array('type' => 'example', 'display' => 'full')``)
**route.absolute** boolean value, create absolute or relative url address based on ``route.name`` and ``route.parameters`` (default ``false``)
**route.identifier_parameter_name** parameter added to ``route.parameters``, its value is an object identifier (e.g. 'id') to create dynamic links based on rendered objects.
====================================== ==================================================================
.. code-block:: php
public function configureListFields(ListMapper $listMapper)
{
$listMapper
// Output for value `http://example.com`:
// `<a href="http://example.com">http://example.com</a>`
->add('targetUrl', 'url')
// Output for value `http://example.com`:
// `<a href="http://example.com" target="_blank">example.com</a>`
->add('targetUrl', 'url', array(
'attributes' => array('target' => '_blank')
))
// Output for value `http://example.com`:
// `<a href="http://example.com">example.com</a>`
->add('targetUrl', 'url', array(
'hide_protocol' => true
))
// Output for value `Homepage of example.com` :
// `<a href="http://example.com">Homepage of example.com</a>`
->add('title', 'url', array(
'url' => 'http://example.com'
))
// Output for value `Acme Blog Homepage`:
// `<a href="http://blog.example.com">Acme Blog Homepage</a>`
->add('title', 'url', array(
'route' => array(
'name' => 'acme_blog_homepage',
'absolute' => true
)
))
// Output for value `Sonata is great!` (related object has identifier `123`):
// `<a href="http://blog.example.com/xml/123">Sonata is great!</a>`
->add('title', 'url', array(
'route' => array(
'name' => 'acme_blog_article',
'absolute' => true,
'parameters' => array('format' => 'xml'),
'identifier_parameter_name' => 'id'
)
))
;
}
.. note::
Do not use ``url`` type with ``addIdentifier()`` method, because it will create invalid nested URLs.
HTML
^^^^
Display (and optionally truncate or strip tags from) raw html.
You can use the following parameters:
======================== ==================================================================
Parameter Description
======================== ==================================================================
**strip** Strip HTML and PHP tags from a string
**truncate** Truncate a string to ``length`` characters beginning from start. Implies strip. Beware of HTML entities. Make sure to configure your HTML editor to disable entities if you want to use truncate. For instance, use `config.entities <http://docs.ckeditor.com/#!/api/CKEDITOR.config-cfg-entities>`_ for ckeditor
**truncate.length** The length to truncate the string to (default ``30``)
**truncate.preserve** Preserve whole words (default ``false``)
**truncate.separator** Separator to be appended to the trimmed string (default ``...``)
======================== ==================================================================
.. code-block:: php
public function configureListFields(ListMapper $listMapper)
{
$listMapper
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `<p><strong>Creating a Template for the Field</strong> and form</p>` (no escaping is done)
->add('content', 'html')
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `Creating a Template for the Fi...`
->add('content', 'html', array(
'strip' => true
))
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `Creating a Template for...`
->add('content', 'html', array(
'truncate' => true
))
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `Creating a...`
->add('content', 'html', array(
'truncate' => array(
'length' => 10
)
))
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `Creating a Template for the Field...`
->add('content', 'html', array(
'truncate' => array(
'preserve' => true
)
))
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `Creating a Template for the Fi, etc.`
->add('content', 'html', array(
'truncate' => array(
'separator' => ', etc.'
)
))
// Output for value `<p><strong>Creating a Template for the Field</strong> and form</p>`:
// `Creating a Template for***`
->add('content', 'html', array(
'truncate' => array(
'length' => 20,
'preserve' => true,
'separator' => '***'
)
))
;
}

View File

@@ -0,0 +1,201 @@
Form Help Messages and Descriptions
===================================
Help Messages
-------------
Help messages are short notes that are rendered together with form fields. They are generally used to show additional information so the user can complete the form element faster and more accurately. The text is not escaped, so HTML can be used.
Example
^^^^^^^
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('title', null, array(
'help' => 'Set the title of a web page'
))
->add('keywords', null, array(
'help' => 'Set the keywords of a web page'
))
->end()
;
}
}
Alternative Ways To Define Help Messages
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
All at once
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('title')
->add('keywords')
->setHelps(array(
'title' => 'Set the title of a web page',
'keywords' => 'Set the keywords of a web page',
))
->end()
;
}
}
or step by step.
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('title')
->add('keywords')
->setHelp('title', 'Set the title of a web page')
->setHelp('keywords', 'Set the keywords of a web page')
->end()
;
}
}
This can be very useful if you want to apply general help messages via an ``AdminExtension``.
This Extension for example adds a note field to some entities which use a custom trait.
.. code-block:: php
<?php
namespace AppBundle\Admin\Extension;
use Sonata\AdminBundle\Admin\AbstractAdminExtension;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Show\ShowMapper;
class NoteAdminExtension extends AbstractAdminExtension
{
// add this field to the datagrid every time its available
/**
* @param DatagridMapper $datagridMapper
*/
public function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('note')
;
}
// here we don't add the field, because we would like to define
// the place manually in the admin. But if the filed is available,
// we want to add the following help message to the field.
/**
* @param FormMapper $formMapper
*/
public function configureFormFields(FormMapper $formMapper)
{
$formMapper
->addHelp('note', 'Use this field for an internal note.')
;
}
// if the field exists, add it in a special tab on the show view.
/**
* @param ShowMapper $showMapper
*/
public function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->with('Internal')
->add('note')
->end()
;
}
}
Help messages in a sub-field
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('enabled')
->add('settings', 'sonata_type_immutable_array', array(
'keys' => array(
array('content', 'textarea', array(
'sonata_help' => 'Set the content'
)),
array('public', 'checkbox', array()),
))
;
}
}
Advanced usage
^^^^^^^^^^^^^^
Since help messages can contain HTML they can be used for more advanced solutions.
See the cookbook entry :doc:`Showing image previews <../cookbook/recipe_image_previews>` for a detailed example of how to
use help messages to display an image tag.
Form Group Descriptions
-----------------------
A form group description is a block of text rendered below the group title. These can be used to describe a section of a form. The text is not escaped, so HTML can be used.
Example
^^^^^^^
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General', array(
'description' => 'This section contains general settings for the web page'
))
->add('title', null, array(
'help' => 'Set the title of a web page'
))
->add('keywords', null, array(
'help' => 'Set the keywords of a web page'
))
->end()
;
}
}

View File

@@ -0,0 +1,751 @@
Form Types
==========
Admin related form types
------------------------
When defining fields in your Admin classes you can use any of the standard
`Symfony field types`_ and configure them as you would normally. In addition
there are some special Sonata field types which allow you to work with
relationships between one entity class and another.
.. _field-types-model:
sonata_type_model
^^^^^^^^^^^^^^^^^
Setting a field type of ``sonata_type_model`` will use an instance of
``ModelType`` to render that field. This Type allows you to choose an existing
entity from the linked model class. In effect it shows a list of options from
which you can choose a value (or values).
For example, we have an entity class called ``Page`` which has a field called
``image1`` which maps a relationship to another entity class called ``Image``.
All we need to do now is add a reference for this field in our ``PageAdmin`` class:
.. code-block:: php
<?php
// src/AppBundle/Admin/PageAdmin.php
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$imageFieldOptions = array(); // see available options below
$formMapper
->add('image1', 'sonata_type_model', $imageFieldOptions)
;
}
}
Note that the third parameter to ``FormMapper::add()`` is optional so
there is no need to pass in an empty array, it is shown here just to demonstrate
where the options go when you want to use them.
Since the ``image1`` field refers to a related entity we do not need to specify
any options. Sonata will calculate that the linked admin class is of type ``Image`` and,
by default, use the ``ImageAdmin`` class to retrieve a list of all existing Images
to display as choices in the selector.
.. tip::
You need to create ``ImageAdmin`` class in this case to use ``sonata_type_model`` type.
:ref:`You can also use <form_types_fielddescription_options>` use the ``admin_code`` parameter.
The available options are:
property
defaults to null. You can set this to a `Symfony PropertyPath`_ compatible
string to designate which field to use for the choice values.
query
defaults to null. You can set this to a QueryBuilder instance in order to
define a custom query for retrieving the available options.
template
defaults to 'choice' (not currently used?)
multiple
defaults to false - see the `Symfony choice Field Type docs`_ for more info
expanded
defaults to false - see the `Symfony choice Field Type docs`_ for more info
choices
defaults to null - see the `Symfony choice Field Type docs`_ for more info
preferred_choices
defaults to array() - see the `Symfony choice Field Type docs`_ for more info
choice_list
**(deprecated in favor of choice_loader since Symfony 2.7)**
defaults to a ``ModelChoiceList`` built from the other options
choice_loader
defaults to a ``ModelChoiceLoader`` built from the other options
model_manager
defaults to null, but is actually calculated from the linked Admin class.
You usually should not need to set this manually.
class
The entity class managed by this field. Defaults to null, but is actually
calculated from the linked Admin class. You usually should not need to set
this manually.
btn_add, btn_list, btn_delete and btn_catalogue:
The labels on the ``add``, ``list`` and ``delete`` buttons can be customized
with these parameters. Setting any of them to ``false`` will hide the
corresponding button. You can also specify a custom translation catalogue
for these labels, which defaults to ``SonataAdminBundle``.
.. note::
An admin class for the linked model class needs to be defined to render this form type.
.. note::
If you need to use a sortable ``sonata_type_model`` check the :doc:`../cookbook/recipe_sortable_sonata_type_model` page.
.. note::
When using ``sonata_type_model`` with ``btn_add``, a jQuery event will be
triggered when a child form is added to the DOM
(``sonata-admin-setup-list-modal`` by default and
``sonata-admin-append-form-element`` when using ``edit:inline``).
sonata_type_model_hidden
^^^^^^^^^^^^^^^^^^^^^^^^
Setting a field type of ``sonata_type_model_hidden`` will use an instance of
``ModelHiddenType`` to render hidden field. The value of hidden field is
identifier of related entity.
.. code-block:: php
<?php
// src/AppBundle/Admin/PageAdmin.php
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
// generates hidden form field with id of related Category entity
$formMapper
->add('categoryId', 'sonata_type_model_hidden')
;
}
}
The available options are:
model_manager
defaults to null, but is actually calculated from the linked Admin class.
You usually should not need to set this manually.
class
The entity class managed by this field. Defaults to null, but is actually
calculated from the linked Admin class. You usually should not need to set
this manually.
sonata_type_model_autocomplete
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Setting a field type of ``sonata_type_model_autocomplete`` will use an instance of
``ModelAutocompleteType`` to render that field. This Type allows you to choose an existing
entity from the linked model class. In effect it shows a list of options from
which you can choose a value. The list of options is loaded dynamically
with ajax after typing 3 chars (autocomplete). It is best for entities with many
items.
This field type works by default if the related entity has an admin instance and
in the related entity datagrid is a string filter on the ``property`` field.
For example, we have an entity class called ``Article`` (in the ``ArticleAdmin``)
which has a field called ``category`` which maps a relationship to another entity
class called ``Category``. All we need to do now is add a reference for this field
in our ``ArticleAdmin`` class and make sure, that in the CategoryAdmin exists
datagrid filter for the property ``title``.
.. code-block:: php
<?php
// src/AppBundle/Admin/ArticleAdmin.php
class ArticleAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
// the dropdown autocomplete list will show only Category
// entities that contain specified text in "title" attribute
$formMapper
->add('category', 'sonata_type_model_autocomplete', array(
'property' => 'title'
))
;
}
}
.. code-block:: php
<?php
// src/AppBundle/Admin/CategoryAdmin.php
class CategoryAdmin extends AbstractAdmin
{
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
// this text filter will be used to retrieve autocomplete fields
$datagridMapper
->add('title')
;
}
}
The available options are:
property
defaults to null. You have to set this to designate which field (or a list of fields) to use for the choice values.
This value can be string or array of strings.
class
The entity class managed by this field. Defaults to null, but is actually
calculated from the linked Admin class. You usually should not need to set
this manually.
model_manager
defaults to null, but is actually calculated from the linked Admin class.
You usually should not need to set this manually.
callback
defaults to null. Callable function that can be used to modify the query which is used to retrieve autocomplete items.
The callback should receive three parameters - the Admin instance, the property (or properties) defined as searchable and the
search value entered by the user.
From the ``$admin`` parameter it is possible to get the ``Datagrid`` and the ``Request``:
.. code-block:: php
$formMapper
->add('category', 'sonata_type_model_autocomplete', array(
'property' => 'title',
'callback' => function ($admin, $property, $value) {
$datagrid = $admin->getDatagrid();
$queryBuilder = $datagrid->getQuery();
$queryBuilder
->andWhere($queryBuilder->getRootAlias() . '.foo=:barValue')
->setParameter('barValue', $admin->getRequest()->get('bar'))
;
$datagrid->setValue($property, null, $value);
},
))
;
to_string_callback
defaults to null. Callable function that can be used to change the default toString behaviour of entity.
.. code-block:: php
$formMapper
->add('category', 'sonata_type_model_autocomplete', array(
'property' => 'title',
'to_string_callback' => function($entity, $property) {
return $entity->getTitle();
},
))
;
multiple
defaults to false. Set to true, if your field is in a many-to-many relation.
placeholder
defaults to "". Placeholder is shown when no item is selected.
minimum_input_length
defaults to 3. Minimum number of chars that should be typed to load ajax data.
items_per_page
defaults to 10. Number of items per one ajax request.
quiet_millis
defaults to 100. Number of milliseconds to wait for the user to stop typing before issuing the ajax request.
cache
defaults to false. Set to true, if the requested pages should be cached by the browser.
url
defaults to "". Target external remote URL for ajax requests.
You usually should not need to set this manually.
route
The route ``name`` with ``parameters`` that is used as target URL for ajax
requests.
width
defaults to "". Controls the width style attribute of the Select2 container div.
dropdown_auto_width
defaults to false. Set to true to enable the `dropdownAutoWidth` Select2 option,
which allows the drop downs to be wider than the parent input, sized according to their content.
container_css_class
defaults to "". Css class that will be added to select2's container tag.
dropdown_css_class
defaults to "". CSS class of dropdown list.
dropdown_item_css_class
defaults to "". CSS class of dropdown item.
req_param_name_search
defaults to "q". Ajax request parameter name which contains the searched text.
req_param_name_page_number
defaults to "_page". Ajax request parameter name which contains the page number.
req_param_name_items_per_page
defaults to "_per_page". Ajax request parameter name which contains the limit of
items per page.
template
defaults to ``SonataAdminBundle:Form/Type:sonata_type_model_autocomplete.html.twig``.
Use this option if you want to override the default template of this form type.
.. code-block:: php
<?php
// src/AppBundle/Admin/ArticleAdmin.php
class ArticleAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('category', 'sonata_type_model_autocomplete', array(
'property' => 'title',
'template' => 'AppBundle:Form/Type:sonata_type_model_autocomplete.html.twig',
))
;
}
}
.. code-block:: jinja
{# src/AppBundle/Resources/views/Form/Type/sonata_type_model_autocomplete.html.twig #}
{% extends 'SonataAdminBundle:Form/Type:sonata_type_model_autocomplete.html.twig' %}
{# change the default selection format #}
{% block sonata_type_model_autocomplete_selection_format %}'<b>'+item.label+'</b>'{% endblock %}
target_admin_access_action
defaults to ``list``.
By default, the user needs the ``LIST`` role (mapped to ``list`` access action)
to get the autocomplete items from the target admin's datagrid.
If you can't give some users this role because they will then have access to the target
admin's datagrid, you have to grant them another role.
In the example below we changed the ``target_admin_access_action`` from ``list`` to ``autocomplete``,
which is mapped in the target admin to ``AUTOCOMPLETE`` role. Please make sure that all valid users
have the ``AUTOCOMPLETE`` role.
.. code-block:: php
<?php
// src/AppBundle/Admin/ArticleAdmin.php
class ArticleAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
// the dropdown autocomplete list will show only Category
// entities that contain specified text in "title" attribute
$formMapper
->add('category', 'sonata_type_model_autocomplete', array(
'property' => 'title',
'target_admin_access_action' => 'autocomplete'
))
;
}
}
.. code-block:: php
<?php
// src/AppBundle/Admin/CategoryAdmin.php
class CategoryAdmin extends AbstractAdmin
{
protected $accessMapping = array(
'autocomplete' => 'AUTOCOMPLETE',
);
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
// this text filter will be used to retrieve autocomplete fields
// only the users with role AUTOCOMPLETE will be able to get the items
$datagridMapper
->add('title')
;
}
}
sonata_choice_field_mask
^^^^^^^^^^^^^^^^^^^^^^^^
Setting a field type of ``sonata_choice_field_mask`` will use an instance of
``ChoiceFieldMaskType`` to render choice field.
According the choice made only associated fields are displayed. The others fields are hidden.
.. code-block:: php
<?php
// src/AppBundle/Admin/AppMenuAdmin.php
class AppMenuAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('linkType', 'sonata_type_choice_field_mask', array(
'choices' => array(
'uri' => 'uri',
'route' => 'route',
),
'map' => array(
'route' => array('route', 'parameters'),
'uri' => array('uri'),
),
'placeholder' => 'Choose an option',
'required' => false
))
->add('route', 'text')
->add('uri', 'text')
->add('parameters')
;
}
}
map
Associative array. Describes the fields that are displayed for each choice.
sonata_type_admin
^^^^^^^^^^^^^^^^^
Setting a field type of ``sonata_type_admin`` will embed another Admin class
and use the embedded Admin's configuration when editing this field.
``sonata_type_admin`` fields should only be used when editing a field which
represents a relationship between two model classes.
This Type allows you to embed a complete form for the related element, which
you can configure to allow the creation, editing and (optionally) deletion of
related objects.
For example, lets use a similar example to the one for ``sonata_type_model`` above.
This time, when editing a ``Page`` using ``PageAdmin`` we want to enable the inline
creation (and editing) of new Images instead of just selecting an existing Image
from a list.
First we need to create an ``ImageAdmin`` class and register it as an Admin class
for managing ``Image`` objects. In our admin.yml we have an entry for ``ImageAdmin``
that looks like this:
.. configuration-block::
.. code-block:: yaml
# src/AppBundle/Resources/config/admin.yml
services:
app.admin.image:
class: AppBundle\Admin\ImageAdmin
tags:
- { name: sonata.admin, manager_type: orm, label: "Image" }
arguments:
- ~
- AppBundle\Entity\Image
- 'SonataAdminBundle:CRUD'
calls:
- [ setTranslationDomain, [AppBundle]]
public: true
.. note::
Refer to `Getting started documentation`_ to see how to define your admin.yml file.
To embed ``ImageAdmin`` within ``PageAdmin`` we just need to change the reference
for the ``image1`` field to ``sonata_type_admin`` in our ``PageAdmin`` class:
.. code-block:: php
<?php
// src/AppBundle/Admin/PageAdmin.php
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('image1', 'sonata_type_admin')
;
}
}
We do not need to define any options since Sonata calculates that the linked class
is of type ``Image`` and the service definition (in admin.yml) defines that ``Image``
objects are managed by the ``ImageAdmin`` class.
The available options (which can be passed as a third parameter to ``FormMapper::add()``) are:
delete
defaults to true and indicates that a 'delete' checkbox should be shown allowing
the user to delete the linked object.
btn_add, btn_list, btn_delete and btn_catalogue:
The labels on the ``add``, ``list`` and ``delete`` buttons can be customized
with these parameters. Setting any of them to ``false`` will hide the
corresponding button. You can also specify a custom translation catalogue
for these labels, which defaults to ``SonataAdminBundle``.
sonata_type_collection
^^^^^^^^^^^^^^^^^^^^^^
The ``CollectionType`` is meant to handle creation and editing of model
collections. Rows can be added and deleted, and your model abstraction layer may
allow you to edit fields inline. You can use ``type_options`` to pass values
to the underlying forms.
.. code-block:: php
<?php
// src/AppBundle/Admin/ProductAdmin.php
class ProductAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('sales', 'sonata_type_collection', array(
'type_options' => array(
// Prevents the "Delete" option from being displayed
'delete' => false,
'delete_options' => array(
// You may otherwise choose to put the field but hide it
'type' => 'hidden',
// In that case, you need to fill in the options as well
'type_options' => array(
'mapped' => false,
'required' => false,
)
)
)
), array(
'edit' => 'inline',
'inline' => 'table',
'sortable' => 'position',
))
// ...
;
}
// ...
}
The available options (which can be passed as a third parameter to ``FormMapper::add()``) are:
btn_add and btn_catalogue:
The label on the ``add`` button can be customized
with this parameters. Setting it to ``false`` will hide the
corresponding button. You can also specify a custom translation catalogue
for this label, which defaults to ``SonataAdminBundle``.
**TIP**: A jQuery event is fired after a row has been added (``sonata-admin-append-form-element``).
You can listen to this event to trigger custom JavaScript (eg: add a calendar widget to a newly added date field)
**TIP**: Setting the 'required' option to true does not cause a requirement of 'at least one' child entity.
Setting the 'required' option to false causes all nested form fields to become not required as well.
.. tip::
You can check / uncheck a range of checkboxes by clicking a first one,
then a second one with shift + click.
sonata_type_native_collection (previously collection)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This bundle handle the native Symfony ``collection`` form type by adding:
* an ``add`` button if you set the ``allow_add`` option to ``true``.
* a ``delete`` button if you set the ``allow_delete`` option to ``true``.
.. tip::
A jQuery event is fired after a row has been added (``sonata-admin-append-form-element``).
You can listen to this event to trigger custom JavaScript (eg: add a calendar widget to a newly added date field)
.. tip::
A jQuery event is fired after a row has been added (``sonata-collection-item-added``)
or before deleted (``sonata-collection-item-deleted``).
A jQuery event is fired after a row has been deleted successfully (``sonata-collection-item-deleted-successful``)
You can listen to these events to trigger custom JavaScript.
.. _form_types_fielddescription_options:
FieldDescription options
^^^^^^^^^^^^^^^^^^^^^^^^
The fourth parameter to FormMapper::add() allows you to pass in ``FieldDescription``
options as an array. The most useful of these is ``admin_code``, which allows you to
specify which Admin to use for managing this relationship. It is most useful for inline
editing in conjunction with the ``sonata_type_admin`` form type.
The value used should be the admin *service* name, not the class name. If you do
not specify an ``admin_code`` in this way, the default admin class for the field's
model type will be used.
For example, to specify the use of the Admin class which is registered as
``sonata.admin.imageSpecial`` for managing the ``image1`` field from our ``PageAdmin``
example above:
.. code-block:: php
<?php
// src/AppBundle/Admin/PageAdmin.php
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('image1', 'sonata_type_admin', array(), array(
'admin_code' => 'sonata.admin.imageSpecial'
))
// ...
;
}
// ...
}
Other specific field configuration options are detailed in the related
abstraction layer documentation.
Adding a FormBuilderInterface
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can add Symfony ``FormBuilderInterface`` instances to the ``FormMapper``. This allows you to
re-use a model form type. When adding a field using a ``FormBuilderInterface``, the type is guessed.
Given you have a ``PostType`` like this:
.. code-block:: php
<?php
// src/AppBundle/Form/PostType.php
class PostType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('author', EntityType::class, [
'class' => User::class
])
->add('title', TextType::class)
->add('body', TextareaType::class)
;
}
}
you can reuse it like this:
.. code-block:: php
<?php
// src/AppBundle/Admin/Post.php
class Post extend AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$builder = $formMapper->getFormBuilder()->getFormFactory()->createBuilder(PostType::class);
$formMapper
->with('Post')
->add($builder->get('title'))
->add($builder->get('body'))
->end()
->with('Author')
->add($builder->get('author'))
->end()
;
}
}
Types options
-------------
General
^^^^^^^
- ``label``: You can set the ``label`` option to ``false`` if you don't want to show it.
.. code-block:: php
<?php
// src/AppBundle/Admin/PageAdmin.php
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('status', null, array(
'label' => false
))
// ...
;
}
// ...
}
ChoiceType
^^^^^^^^^^
- ``sortable``: This option can be added for multiple choice widget to activate select2 sortable.
.. code-block:: php
<?php
// src/AppBundle/Admin/PageAdmin.php
class PageAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('multiChoices', 'choice', array(
'multiple' => true,
'sortable' => true,
))
// ...
;
}
// ...
}
.. _`Symfony field types`: http://symfony.com/doc/current/book/forms.html#built-in-field-types
.. _`Symfony choice Field Type docs`: http://symfony.com/doc/current/reference/forms/types/choice.html
.. _`Symfony PropertyPath`: http://api.symfony.com/2.0/Symfony/Component/Form/Util/PropertyPath.html
.. _`Getting started documentation`: https://sonata-project.org/bundles/admin/master/doc/reference/getting_started.html#importing-it-in-the-main-config-yml

View File

@@ -0,0 +1,279 @@
Getting started with SonataAdminBundle
======================================
If you followed the installation instructions, SonataAdminBundle should be installed
but inaccessible. You first need to configure it for your models before you can
start using it. Here is a quick checklist of what is needed to quickly setup
SonataAdminBundle and create your first admin interface for the models of your application:
* Step 1: Create an Admin class
* Step 2: Create an Admin service
* Step 3: Configuration
Create an Admin class
---------------------
SonataAdminBundle helps you manage your data using a graphic interface that
will let you create, update or search your model's instances. Those actions need to
be configured, which is done using an Admin class.
An Admin class represents the mapping of your model to each administration action.
In it, you decide which fields to show on a listing, which to use as filters or what
to show in a creation or edition form.
The easiest way to create an Admin class for your model is to extend
the ``Sonata\AdminBundle\Admin\AbstractAdmin`` class.
Suppose your ``AppBundle`` has a ``Post`` entity.
This is how a basic Admin class for it could look like:
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
namespace AppBundle\Admin;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Show\ShowMapper;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
class PostAdmin extends AbstractAdmin
{
// Fields to be shown on create/edit forms
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('title', 'text', array(
'label' => 'Post Title'
))
->add('author', 'entity', array(
'class' => 'AppBundle\Entity\User'
))
// if no type is specified, SonataAdminBundle tries to guess it
->add('body')
// ...
;
}
// Fields to be shown on filter forms
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
->add('author')
;
}
// Fields to be shown on lists
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
->add('slug')
->add('author')
;
}
// Fields to be shown on show action
protected function configureShowFields(ShowMapper $showMapper)
{
$showMapper
->add('title')
->add('slug')
->add('author')
;
}
}
Implementing these four functions is the first step to creating an Admin class.
Other options are available, that will let you further customize the way your model
is shown and handled. Those will be covered in more advanced chapters of this manual.
Create an Admin service
-----------------------
Now that you have created your Admin class, you need to create a service for it. This
service needs to have the ``sonata.admin`` tag, which is your way of letting
SonataAdminBundle know that this particular service represents an Admin class:
Create either a new ``admin.xml`` or ``admin.yml`` file inside the ``src/AppBundle/Resources/config/`` folder:
.. configuration-block::
.. code-block:: xml
<!-- src/AppBundle/Resources/config/admin.xml -->
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument />
<call method="setTranslationDomain">
<argument>AppBundle</argument>
</call>
</service>
.. code-block:: yaml
# src/AppBundle/Resources/config/admin.yml
services:
app.admin.post:
class: AppBundle\Admin\PostAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Post" }
arguments:
- ~
- AppBundle\Entity\Post
- ~
calls:
- [ setTranslationDomain, [AppBundle]]
public: true
The example above assumes that you're using ``SonataDoctrineORMAdminBundle``.
If you're using ``SonataDoctrineMongoDBAdminBundle``, ``SonataPropelAdminBundle`` or ``SonataDoctrinePhpcrAdminBundle`` instead, set ``manager_type`` option to ``doctrine_mongodb``, ``propel`` or ``doctrine_phpcr`` respectively.
The basic configuration of an Admin service is quite simple. It creates a service
instance based on the class you specified before, and accepts three arguments:
1. The Admin service's code (defaults to the service's name)
2. The model which this Admin class maps (required)
3. The controller that will handle the administration actions (defaults to ``SonataAdminBundle:CRUDController()``)
Usually you just need to specify the second argument, as the first and third's default
values will work for most scenarios.
The ``setTranslationDomain`` call lets you choose which translation domain to use when
translating labels on the admin pages. If you don't call ``setTranslationDomain``, SonataAdmin uses ``messages`` as translation domain.
More info on the `Symfony translations page`_.
Now that you have a configuration file with your admin service, you just need to tell
Symfony to load it. There are two ways to do so:
Have your bundle load it
^^^^^^^^^^^^^^^^^^^^^^^^
Inside your bundle's extension file, using the ``load()`` method as described in the `Symfony cookbook`_.
For ``admin.xml`` use:
.. code-block:: php
<?php
// src/AppBundle/DependencyInjection/AppExtension.php
namespace AppBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\FileLocator;
class AppExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container) {
// ...
$loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
// ...
$loader->load('admin.xml');
}
}
and for ``admin.yml``:
.. code-block:: php
<?php
// src/AppBundle/DependencyInjection/AppExtension.php
namespace AppBundle\DependencyInjection;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader;
use Symfony\Component\Config\FileLocator;
class AppExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
// ...
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
// ...
$loader->load('admin.yml');
}
}
Importing it in the main config.yml
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We recommend the to load the file in the Extension, but this way is possible, too.
You can include your new configuration file in the main ``config.yml`` (make sure that you
use the correct file extension):
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
imports:
# for xml
- { resource: "@AppBundle/Resources/config/admin.xml" }
# for yaml
- { resource: "@AppBundle/Resources/config/admin.yml" }
Configuration
-------------
At this point you have basic administration actions for your model. If you visit ``http://yoursite.local/admin/dashboard`` again, you should now see a panel with
your mapped model. You can start creating, listing, editing and deleting instances.
You probably want to put your own project's name and logo on the top bar.
Put your logo file here ``src/AppBundle/Resources/public/images/fancy_acme_logo.png``
Install your assets:
.. code-block:: bash
$ php app/console assets:install
Now you can change your project's main config.yml file:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
title: Acme
title_logo: bundles/app/images/fancy_acme_logo.png
Next steps - Security
---------------------
As you probably noticed, you were able to access your dashboard and data by just
typing in the URL. By default, the SonataAdminBundle does not come with any user
management for ultimate flexibility. However, it is most likely that your application
requires such a feature. The Sonata Project includes a ``SonataUserBundle`` which
integrates the very popular ``FOSUserBundle``. Please refer to the :doc:`security` section of
this documentation for more information.
Congratulations! You are ready to start using SonataAdminBundle. You can now map
additional models or explore advanced functionalities. The following sections will
each address a specific section or functionality of the bundle, giving deeper
details on what can be configured and achieved with SonataAdminBundle.
.. _`Symfony cookbook`: http://symfony.com/doc/master/cookbook/bundles/extension.html#using-the-load-method
.. _`Symfony translations page`: http://symfony.com/doc/current/book/translation.html#using-message-domains

View File

@@ -0,0 +1,171 @@
Installation
============
SonataAdminBundle can be installed at any moment during a project's lifecycle,
whether it's a clean Symfony installation or an existing project.
Downloading the code
--------------------
Use composer to manage your dependencies and download SonataAdminBundle:
.. code-block:: bash
$ php composer.phar require sonata-project/admin-bundle
You'll be asked to type in a version constraint. 'dev-master' will get you the latest
version, compatible with the latest Symfony version. Check `packagist <https://packagist.org/packages/sonata-project/admin-bundle>`_
for older versions:
.. code-block:: bash
Please provide a version constraint for the sonata-project/admin-bundle requirement: dev-master
Selecting and downloading a storage bundle
------------------------------------------
SonataAdminBundle is storage agnostic, meaning it can work with several storage
mechanisms. Depending on which you are using on your project, you'll need to install
one of the following bundles. In the respective links you'll find simple installation
instructions for each of them:
- `SonataDoctrineORMAdminBundle <https://sonata-project.org/bundles/doctrine-orm-admin/master/doc/reference/installation.html>`_
- `SonataDoctrineMongoDBAdminBundle <https://github.com/sonata-project/SonataDoctrineMongoDBAdminBundle/blob/master/Resources/doc/reference/installation.rst>`_
- `SonataPropelAdminBundle <https://sonata-project.org/bundles/propel-admin/master/doc/reference/installation.html>`_
- `SonataDoctrinePhpcrAdminBundle <https://github.com/sonata-project/SonataDoctrinePhpcrAdminBundle/blob/master/Resources/doc/reference/installation.rst>`_
.. note::
Don't know which to choose? Most new users prefer SonataDoctrineORMAdmin, to interact with traditional relational databases (MySQL, PostgreSQL, etc)
Enabling SonataAdminBundle and its dependencies
-----------------------------------------------
SonataAdminBundle relies on other bundles to implement some features.
Besides the storage layer mentioned on step 2, there are other bundles needed
for SonataAdminBundle to work:
- `SonataBlockBundle <https://sonata-project.org/bundles/block/master/doc/reference/installation.html>`_
- `KnpMenuBundle <https://github.com/KnpLabs/KnpMenuBundle/blob/master/Resources/doc/index.md#installation>`_ (Version 2.*)
These bundles are automatically downloaded by composer as a dependency of SonataAdminBundle.
However, you have to enable them in your ``AppKernel.php``, and configure them manually. Don't
forget to enable SonataAdminBundle too:
.. code-block:: php
<?php
// app/AppKernel.php
public function registerBundles()
{
return array(
// ...
// The admin requires some twig functions defined in the security
// bundle, like is_granted
new Symfony\Bundle\SecurityBundle\SecurityBundle(),
// Add your dependencies
new Sonata\CoreBundle\SonataCoreBundle(),
new Sonata\BlockBundle\SonataBlockBundle(),
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
//...
// If you haven't already, add the storage bundle
// This example uses SonataDoctrineORMAdmin but
// it works the same with the alternatives
new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
// Then add SonataAdminBundle
new Sonata\AdminBundle\SonataAdminBundle(),
// ...
);
}
.. note::
Since version 2.3 SonatajQueryBundle is not required anymore as assets are available in this
bundle. The bundle is also registered in `bower.io <https://github.com/sonata-project/SonataAdminBundle>`_ so
you can use bower to handle your assets. To make sure you get the dependencies
that match the version of SonataAdminBundle you are using, you can make bower
use the local bower dependency file, like this : ``bower install ./vendor/sonata-project/admin-bundle/bower.json``
.. note::
You must enable translator service in `config.yml`.
.. code-block:: yaml
framework:
translator: { fallbacks: ["%locale%"] }
For more information: http://symfony.com/doc/current/translation.html#configuration
Configuring SonataAdminBundle dependencies
------------------------------------------
You will need to configure SonataAdminBundle's dependencies. For each of the above
mentioned bundles, check their respective installation/configuration instructions
files to see what changes you have to make to your Symfony configuration.
SonataAdminBundle provides a SonataBlockBundle block that's used on the administration
dashboard. To be able to use it, make sure it's enabled on SonataBlockBundle's configuration:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_block:
default_contexts: [cms]
blocks:
# enable the SonataAdminBundle block
sonata.admin.block.admin_list:
contexts: [admin]
.. note::
Don't worry too much if, at this point, you don't yet understand fully
what a block is. SonataBlockBundle is a useful tool, but it's not vital
that you understand it right now.
Cleaning up
-----------
Now, install the assets from the bundles:
.. code-block:: bash
$ php bin/console assets:install
Usually, when installing new bundles, it is a good practice to also delete your cache:
.. code-block:: bash
$ php bin/console cache:clear
At this point, your Symfony installation should be fully functional, with no errors
showing up from SonataAdminBundle or its dependencies. SonataAdminBundle is installed
but not yet configured (more on that in the next section), so you won't be able to
use it yet.
If, at this point or during the installation, you come across any errors, don't panic:
- Read the error message carefully. Try to find out exactly which bundle is causing the error. Is it SonataAdminBundle or one of the dependencies?
- Make sure you followed all the instructions correctly, for both SonataAdminBundle and its dependencies.
- Odds are that someone already had the same problem, and it's documented somewhere. Check Google_, `Sonata Users Group`_ or `Symfony Support`_ to see if you can find a solution.
- Still no luck? Try checking the project's `open issues on GitHub`_.
After you have successfully installed the above bundles you need to configure
SonataAdminBundle for administering your models. All that is needed to quickly
set up SonataAdminBundle is described in the :doc:`getting_started` chapter.
.. _Google: http://www.google.com
.. _`Sonata Users Group`: https://groups.google.com/group/sonata-users
.. _`Symfony Support`: http://symfony.com/support
.. _`open issues on GitHub`: https://github.com/sonata-project/SonataAdminBundle/issues

View File

@@ -0,0 +1,138 @@
Preview Mode
============
Preview Mode is an optional view of an object before it is persisted or updated.
The preview step can be enabled for an admin entity by overriding the public property
``$supportsPreviewMode`` and setting it to true.
.. code-block:: php
<?php
// src/AppBundle/AdminPostAdmin.php
class PostAdmin extends AbstractAdmin
{
public $supportsPreviewMode = true;
/ ..
}
This will show a new button during create/edit mode named preview.
.. figure:: ../images/preview_mode_button.png
:align: center
:alt: Preview Button
While in preview mode two buttons will be shown to approve or decline persistence of the
entity. Decline will send you back to the edit mode with all your changes unpersisted but
still in the form so no data is lost and the entity can be further adjusted.
Accepting the preview will store the entity as if the preview step was never there.
.. figure:: ../images/preview_show.png
:align: center
:alt: Preview Button
Simulating front-end rendering
------------------------------
Preview can be used to render how the object would look like in your front-end environment.
However by default it uses a template similar to the one of the show action and works with
the fields configured to be shown in the show view.
Overriding the preview template ``SonataAdminBundle:CRUD:preview.html.twig`` can be done either
globally through the template configuration for the key 'preview':
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
templates:
preview: AppBundle:CRUD:preview.html.twig
Or per admin entity by overriding the ``getTemplate($name)`` and returning the appropriate template when the key
matches 'preview':
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
// ...
public function getTemplate($name)
{
switch ($name) {
case 'preview':
return 'AppBundle:CRUD:preview.html.twig';
break;
default:
return parent::getTemplate($name);
break;
}
}
In either way the template should be extending your own layout, injecting the form in it
and finally overriding the action buttons to show the approve/decline buttons like the
default preview.html.twig.
The entity is passed to the view in a variable called **object**. If your original view expects
a different object you can just set your own variables prior to calling parent().
.. code-block:: jinja
{# 'AppBundle:CRUD:preview.html.twig #}
{% extends 'AppBundle::layout.html.twig' %}
{% use 'SonataAdminBundle:CRUD:base_edit_form.html.twig' with form as parentForm %}
{% import 'SonataAdminBundle:CRUD:base_edit_form_macro.html.twig' as form_helper %}
{# a block in 'AppBundle::layout.html.twig' expecting article #}
{% block templateContent %}
{% set article = object %}
{{ parent() }}
<div class="sonata-preview-form-container">
{{ block('parentForm') }}
</div>
{% endblock %}
{% block formactions %}
<button class="btn btn-success" type="submit" name="btn_preview_approve">
<i class="fa fa-check"></i>
{{ 'btn_preview_approve'|trans({}, 'SonataAdminBundle') }}
</button>
<button class="btn btn-danger" type="submit" name="btn_preview_decline">
<i class="fa fa-times"></i>
{{ 'btn_preview_decline'|trans({}, 'SonataAdminBundle') }}
</button>
{% endblock %}
Keep in mind that the whole edit form will now appear in your view.
Hiding the fieldset tags with css ``display:none`` will be enough to only show the buttons
(which still have to be styled according to your wishes) and create a nice preview-workflow:
.. code-block:: css
.sonata-preview-form-container .row {
display: none;
};
Or if you prefer less:
.. code-block:: scss
div.sonata-preview-form-container {
.row {
display: none;
};
}

View File

@@ -0,0 +1,407 @@
Routing
=======
The default routes used in the CRUD controller are accessible through the
``Admin`` class.
The ``Admin`` class contains two routing methods:
* ``getRoutes()``: Returns the available routes;
* ``generateUrl($name, $options)``: Generates the related routes.
Routing Definition
------------------
Route names
^^^^^^^^^^^
You can set a ``baseRouteName`` property inside your ``Admin`` class. This
represents the route prefix, to which an underscore and the action name will
be added to generate the actual route names.
.. note::
This is the internal *name* given to a route (it has nothing to do with the route's visible *URL*).
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
protected $baseRouteName = 'sonata_post';
// will result in routes named:
// sonata_post_list
// sonata_post_create
// etc..
// ...
}
If no ``baseRouteName`` is defined then the Admin will generate one for you,
based on the following format: 'admin_vendor_bundlename_entityname' so you will have
route names for your actions like 'admin_vendor_bundlename_entityname_list'.
If the Admin fails to find a baseRouteName for your Admin class a ``RuntimeException``
will be thrown with a related message.
If the admin class is a child of another admin class the route name will be prefixed by the parent route name, example :
.. code-block:: php
<?php
// The parent admin class
class PostAdmin extends AbstractAdmin
{
protected $baseRouteName = 'sonata_post';
// ...
}
// The child admin class
class CommentAdmin extends AbstractAdmin
{
protected $baseRouteName = 'comment'
// will result in routes named :
// sonata_post_comment_list
// sonata_post_comment_create
// etc..
// ...
}
Route patterns (URLs)
^^^^^^^^^^^^^^^^^^^^^
You can use ``baseRoutePattern`` to set a custom URL for a given ``Admin`` class.
For example, to use ``http://yourdomain.com/admin/foo`` as the base URL for
the ``FooAdmin`` class (instead of the default of ``http://yourdomain.com/admin/vendor/bundle/foo``)
use the following code:
.. code-block:: php
<?php
// src/AppBundle/Admin/FooAdmin.php
class FooAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'foo';
}
You will then have route URLs like ``http://yourdomain.com/admin/foo/list`` and
``http://yourdomain.com/admin/foo/1/edit``
If the admin class is a child of another admin class the route pattern will be prefixed by the parent route pattern, example :
.. code-block:: php
<?php
// The parent admin class
class PostAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'post';
// ...
}
// The child admin class
class CommentAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'comment'
// ...
}
For comment you will then have route URLs like ``http://yourdomain.com/admin/post/{postId}/comment/list`` and
``http://yourdomain.com/admin/post/{postId}/comment/{commentId}/edit``
Routing usage
-------------
Inside a CRUD template, a route for the current ``Admin`` class can be generated via
the admin variable's ``generateUrl()`` command:
.. code-block:: html+jinja
<a href="{{ admin.generateUrl('list') }}">List</a>
<a href="{{ admin.generateUrl('list', params|merge('page': 1)) }}">List</a>
Note that you do not need to provide the Admin's route prefix (``baseRouteName``) to
generate a URL for the current Admin, just the action name.
To generate a URL for a different Admin you just use the Route Name with the usual
Twig helpers:
.. code-block:: html+jinja
<a href="{{ path('admin_app_post_list') }}">Post List</a>
Create a route
--------------
You can register new routes by defining them in your ``Admin`` class. Only Admin
routes should be registered this way.
The routes you define in this way are generated within your Admin's context, and
the only required parameter to ``add()`` is the action name. The second parameter
can be used to define the URL format to append to ``baseRoutePattern``, if not set
explicitly this defaults to the action name.
.. code-block:: php
<?php
// src/AppBundle/Admin/MediaAdmin.php
use Sonata\AdminBundle\Route\RouteCollection;
class MediaAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('myCustom'); // Action gets added automatically
$collection->add('view', $this->getRouterIdParameter().'/view');
}
}
Make use of all route parameters
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
As the ``add`` method create a Symfony ``Route``, you can use all constructor arguments of the ``Route`` as parameters
in the ``add`` method to set additional settings like this:
.. code-block:: php
<?php
// src/AppBundle/Admin/MediaAdmin.php
use Sonata\AdminBundle\Route\RouteCollection;
class MediaAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->add('custom_action', $this->getRouterIdParameter().'/custom-action', array(), array(), array(), '', array('https'), array('GET', 'POST'));
}
}
Other steps needed to create your new action
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In addition to defining the route for your new action you also need to create a
handler for it in your Controller. By default Admin classes use ``SonataAdminBundle:CRUD``
as their controller, but this can be changed by altering the third argument when defining
your Admin service (in your admin.yml file).
For example, lets change the Controller for our MediaAdmin class to AppBundle:MediaCRUD:
.. configuration-block::
.. code-block:: yaml
# src/AppBundle/Resources/config/admin.yml
app.admin.media:
class: AppBundle\Admin\MediaAdmin
tags:
- { name: sonata.admin, manager_type: orm, label: "Media" }
arguments:
- ~
- AppBundle\Entity\Page
- 'AppBundle:MediaCRUD' # define the new controller via the third argument
public: true
We now need to create our Controller, the easiest way is to extend the basic Sonata CRUD controller:
.. code-block:: php
<?php
// src/AppBundle/Controller/MediaCRUDController.php
namespace AppBundle\Controller;
use Sonata\AdminBundle\Controller\CRUDController;
class MediaCRUDController extends CRUDController
{
public function myCustomAction()
{
// your code here ...
}
}
Removing a route
----------------
Extending ``Sonata\AdminBundle\Admin\AbstractAdmin`` will give your Admin classes the following
default routes:
* batch
* create
* delete
* export
* edit
* list
* show
You can view all of the current routes defined for an Admin class by using the console to run
.. code-block:: bash
$ php app/console sonata:admin:explain <<admin.service.name>>
for example if your Admin is called sonata.admin.foo you would run
.. code-block:: bash
$ php app/console sonata:admin:explain app.admin.foo
Sonata internally checks for the existence of a route before linking to it. As a result, removing a
route will prevent links to that action from appearing in the administrative interface. For example,
removing the 'create' route will prevent any links to "Add new" from appearing.
Removing a single route
^^^^^^^^^^^^^^^^^^^^^^^
Any single registered route can be easily removed by name:
.. code-block:: php
<?php
// src/AppBundle/Admin/MediaAdmin.php
use Sonata\AdminBundle\Route\RouteCollection;
class MediaAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->remove('delete');
}
}
Removing all routes except named ones
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you want to disable all default Sonata routes except few whitelisted ones, you can use
the ``clearExcept()`` method. This method accepts an array of routes you want to keep active.
.. code-block:: php
<?php
// src/AppBundle/Admin/MediaAdmin.php
use Sonata\AdminBundle\Route\RouteCollection;
class MediaAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
// Only `list` and `edit` route will be active
$collection->clearExcept(array('list', 'edit'));
// You can also pass a single string argument
$collection->clearExcept('list');
}
}
Removing all routes
^^^^^^^^^^^^^^^^^^^
If you want to remove all default routes, you can use ``clear()`` method.
.. code-block:: php
<?php
// src/AppBundle/Admin/MediaAdmin.php
use Sonata\AdminBundle\Route\RouteCollection;
class MediaAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
// All routes are removed
$collection->clear();
}
}
Removing routes only when an Admin is embedded
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To prevent some routes from being available when one Admin is embedded inside another one
(e.g. to remove the "add new" option when you embed ``TagAdmin`` within ``PostAdmin``) you
can use ``hasParentFieldDescription()`` to detect this case and remove the routes.
.. code-block:: php
<?php
// src/AppBundle/Admin/TagAdmin.php
use Sonata\AdminBundle\Route\RouteCollection;
class TagAdmin extends AbstractAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
// prevent display of "Add new" when embedding this form
if ($this->hasParentFieldDescription()) {
$collection->remove('create');
}
}
}
Persistent parameters
---------------------
In some cases, the interface might be required to pass the same parameters
across the different ``Admin``'s actions. Instead of setting them in the
template or doing other weird hacks, you can define a ``getPersistentParameters``
method. This method will be used when a link is being generated.
.. code-block:: php
<?php
// src/AppBundle/Admin/MediaAdmin.php
class MediaAdmin extends AbstractAdmin
{
public function getPersistentParameters()
{
if (!$this->getRequest()) {
return array();
}
return array(
'provider' => $this->getRequest()->get('provider'),
'context' => $this->getRequest()->get('context', 'default'),
);
}
}
If you then call ``$admin->generateUrl('create')`` somewhere, the generated URL looks like this: ``/admin/module/create?context=default``
Changing the default route in a List Action
-------------------------------------------
Usually the identifier column of a list action links to the edit screen. To change the
list action's links to point to a different action, set the ``route`` option in your call to
``ListMapper::addIdentifier()``. For example, to link to show instead of edit:
.. code-block:: php
<?php
// src/AppBundle/Admin/PostAdmin.php
class PostAdmin extends AbstractAdmin
{
public function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('name', null, array(
'route' => array(
'name' => 'show'
)
));
}
}

View File

@@ -0,0 +1,137 @@
Saving hooks
============
When a SonataAdmin is submitted for processing, there are some events called. One
is before any persistence layer interaction and the other is afterward. Also between submitting
and validating for edit and create actions ``preValidate`` event called. The
events are named as follows:
- new object : ``preValidate($object)`` / ``prePersist($object)`` / ``postPersist($object)``
- edited object : ``preValidate($object)`` / ``preUpdate($object)`` / ``postUpdate($object)``
- deleted object : ``preRemove($object)`` / ``postRemove($object)``
It is worth noting that the update events are called whenever the Admin is successfully
submitted, regardless of whether there are any actual persistence layer events. This
differs from the use of ``preUpdate`` and ``postUpdate`` events in DoctrineORM and perhaps some
other persistence layers.
For example: if you submit an edit form without changing any of the values on the form
then there is nothing to change in the database and DoctrineORM would not fire the **Entity**
class's own ``preUpdate`` and ``postUpdate`` events. However, your **Admin** class's
``preUpdate`` and ``postUpdate`` methods *are* called and this can be used to your
advantage.
.. note::
When embedding one Admin within another, for example using the ``sonata_type_admin``
field type, the child Admin's hooks are **not** fired.
Example used with the FOS/UserBundle
------------------------------------
The ``FOSUserBundle`` provides authentication features for your Symfony Project,
and is compatible with Doctrine ORM, Doctrine ODM and Propel. See
`FOSUserBundle on GitHub`_ for more information.
The user management system requires to perform specific calls when the user
password or username are updated. This is how the Admin bundle can be used to
solve the issue by using the ``preUpdate`` saving hook.
.. code-block:: php
<?php
namespace FOS\UserBundle\Admin\Entity;
use Sonata\AdminBundle\Admin\AbstractAdmin;
use FOS\UserBundle\Model\UserManagerInterface;
class UserAdmin extends AbstractAdmin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('username')
->add('email')
->add('plainPassword', 'text')
->end()
->with('Groups')
->add('groups', 'sonata_type_model', array('required' => false))
->end()
->with('Management')
->add('roles', 'sonata_security_roles', array( 'multiple' => true))
->add('locked', null, array('required' => false))
->add('expired', null, array('required' => false))
->add('enabled', null, array('required' => false))
->add('credentialsExpired', null, array('required' => false))
->end()
;
}
public function preUpdate($user)
{
$this->getUserManager()->updateCanonicalFields($user);
$this->getUserManager()->updatePassword($user);
}
public function setUserManager(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* @return UserManagerInterface
*/
public function getUserManager()
{
return $this->userManager;
}
}
The service declaration where the ``UserManager`` is injected into the Admin class.
.. configuration-block::
.. code-block:: xml
<service id="fos.user.admin.user" class="%fos.user.admin.user.class%">
<tag name="sonata.admin" manager_type="orm" group="fos_user" />
<argument />
<argument>%fos.user.admin.user.entity%</argument>
<argument />
<call method="setUserManager">
<argument type="service" id="fos_user.user_manager" />
</call>
</service>
Hooking in the Controller
-------------------------
You may have noticed that the hooks present in the **Admin** do not allow you
to interact with the process of deletion: you can't cancel it. To achieve this
you should be aware that there is also a way to hook on actions in the Controller.
If you define a custom controller that inherits from ``CRUDController``, you can
redefine the following methods:
- new object : ``preCreate($object)``
- edited object : ``preEdit($object)``
- deleted object : ``preDelete($object)``
- show object : ``preShow($object)``
- list objects : ``preList($object)``
If these methods return a **Response**, the process is interrupted and the response
will be returned as is by the controller (if it returns null, the process continues). You
can generate easily a redirection to the object show page by using the method
``redirectTo($object)``.
.. note::
Use case: you need to prohibit the deletion of a specific item. You may do a simple
check in the ``preDelete($object)`` method.
.. _FOSUserBundle on GitHub: https://github.com/FriendsOfSymfony/FOSUserBundle/

View File

@@ -0,0 +1,87 @@
Search
======
The admin comes with a basic global search available in the upper navigation menu. The search iterates over admin classes
and look for filter with the option ``global_search`` set to true. If you are using the ``SonataDoctrineORMBundle``
any text filter will be set to ``true`` by default.
Customization
-------------
The main action is using the template ``SonataAdminBundle:Core:search.html.twig``. And each search is handled by a
``block``, the template for the block is ``SonataAdminBundle:Block:block_search_result.html.twig``.
The default template values can be configured in the configuration section
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
templates:
# other configuration options
search: SonataAdminBundle:Core:search.html.twig
search_result_block: SonataAdminBundle:Block:block_search_result.html.twig
You also need to configure the block in the sonata block config
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_block:
blocks:
sonata.admin.block.search_result:
contexts: [admin]
You can also configure the block template per admin while defining the admin:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument />
<call method="setTemplate">
<argument>search_result_block</argument>
<argument>SonataPostBundle:Block:block_search_result.html.twig</argument>
</call>
</service>
Configure the default search result action
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In general the search result generates a link to the edit action of an item or is using the show action, if the edit
route is disabled or you haven't the required permission. You can change this behaviour by overriding the
``searchResultActions`` property. The defined action list will we checked successive until a route with the required
permissions exists. If no route is found, the item will be displayed as a text.
.. code-block:: php
<?php
// src/AppBundle/Admin/PersonAdmin.php
class PersonAdmin extends AbstractAdmin
{
// ...
protected $searchResultActions = array('edit', 'show');
// ...
}
Performance
-----------
The current implementation can be expensive if you have a lot of entities as the resulting query does a ``LIKE %query% OR LIKE %query%``...
.. note::
There is a work in progress to use an async JavaScript solution to better load data from the database.

View File

@@ -0,0 +1,770 @@
Security
========
User management
---------------
By default, the SonataAdminBundle does not come with any user management,
however it is most likely the application requires such a feature. The Sonata
Project includes a ``SonataUserBundle`` which integrates the ``FOSUserBundle``.
The ``FOSUserBundle`` adds support for a database-backed user system in Symfony.
It provides a flexible framework for user management that aims to handle common
tasks such as user login, registration and password retrieval.
The ``SonataUserBundle`` is just a thin wrapper to include the ``FOSUserBundle``
into the ``AdminBundle``. The ``SonataUserBundle`` includes:
* A default login area
* A default ``user_block`` template which is used to display the current user
and the logout link
* 2 Admin classes: User and Group
* A default class for User and Group.
There is a little magic in the ``SonataAdminBundle``: if the bundle detects the
``SonataUserBundle`` class, then the default ``user_block`` template will be
changed to use the one provided by the ``SonataUserBundle``.
The install process is available on the dedicated
`SonataUserBundle's documentation area`_.
Security handlers
-----------------
The security part is managed by a ``SecurityHandler``, the bundle comes with 3 handlers:
- ``sonata.admin.security.handler.role``: ROLES to handle permissions
- ``sonata.admin.security.handler.acl``: ACL and ROLES to handle permissions
- ``sonata.admin.security.handler.noop``: always returns true, can be used
with the Symfony firewall
The default value is ``sonata.admin.security.handler.noop``, if you want to
change the default value you can set the ``security_handler`` to
``sonata.admin.security.handler.acl`` or ``sonata.admin.security.handler.role``.
To quickly secure an admin the role security can be used. It allows to specify
the actions a user can do with the admin. The ACL security system is more advanced
and allows to secure the objects. For people using the previous ACL
implementation, you can switch the ``security_handler`` to the role security handler.
Configuration
~~~~~~~~~~~~~
Only the security handler is required to determine which type of security to use.
The other parameters are set as default, change them if needed.
Using roles:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
security:
handler: sonata.admin.security.handler.role
Using ACL:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
security:
handler: sonata.admin.security.handler.acl
# acl security information
information:
GUEST: [VIEW, LIST]
STAFF: [EDIT, LIST, CREATE]
EDITOR: [OPERATOR, EXPORT]
ADMIN: [MASTER]
# permissions not related to an object instance and also to be available when objects do not exist
# the DELETE admin permission means the user is allowed to batch delete objects
admin_permissions: [CREATE, LIST, DELETE, UNDELETE, EXPORT, OPERATOR, MASTER]
# permission related to the objects
object_permissions: [VIEW, EDIT, DELETE, UNDELETE, OPERATOR, MASTER, OWNER]
Later, we will explain how to set up ACL with the ``FriendsOfSymfony/UserBundle``.
Role handler
------------
The ``sonata.admin.security.handler.role`` allows you to operate finely on the
actions that can be done (depending on the entity class), without requiring to set up ACL.
Configuration
~~~~~~~~~~~~~
First, activate the role security handler as described above.
Each time a user tries to do an action in the admin, Sonata checks if he is
either a super admin (``ROLE_SUPER_ADMIN``) **or** has the permission.
The permissions are:
========== ==================================================
Permission Description
========== ==================================================
LIST view the list of objects
VIEW view the detail of one object
CREATE create a new object
EDIT update an existing object
DELETE delete an existing object
EXPORT (for the native Sonata export links)
ALL grants LIST, VIEW, CREATE, EDIT, DELETE and EXPORT
========== ==================================================
Each permission is relative to an admin: if you try to get a list in FooAdmin (declared as ``app.admin.foo``
service), Sonata will check if the user has the ``ROLE_APP_ADMIN_FOO_EDIT`` or ``ROLE_APP_ADMIN_FOO_ALL`` roles.
The role name will be based on the name of your admin service. For instance, ``acme.blog.post.admin`` will become ``ROLE_ACME_BLOG_POST_ADMIN_{ACTION}``.
.. note::
If your admin service is named like ``my.blog.admin.foo_bar`` (note the underscore ``_``) it will become: ``ROLE_MY_BLOG_ADMIN_FOO_BAR_{ACTION}``
So our ``security.yml`` file may look something like this:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
# ...
role_hierarchy:
# for convenience, I decided to gather Sonata roles here
ROLE_SONATA_FOO_READER:
- ROLE_SONATA_ADMIN_DEMO_FOO_LIST
- ROLE_SONATA_ADMIN_DEMO_FOO_VIEW
ROLE_SONATA_FOO_EDITOR:
- ROLE_SONATA_ADMIN_DEMO_FOO_CREATE
- ROLE_SONATA_ADMIN_DEMO_FOO_EDIT
ROLE_SONATA_FOO_ADMIN:
- ROLE_SONATA_ADMIN_DEMO_FOO_DELETE
- ROLE_SONATA_ADMIN_DEMO_FOO_EXPORT
# those are the roles I will use (less verbose)
ROLE_STAFF: [ROLE_USER, ROLE_SONATA_FOO_READER]
ROLE_ADMIN: [ROLE_STAFF, ROLE_SONATA_FOO_EDITOR, ROLE_SONATA_FOO_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
# you could alternatively use for an admin who has all rights
ROLE_ALL_ADMIN: [ROLE_STAFF, ROLE_SONATA_FOO_ALL]
# set access_strategy to unanimous, else you may have unexpected behaviors
access_decision_manager:
strategy: unanimous
Note that we also set ``access_strategy`` to unanimous.
It means that if one voter (for example Sonata) refuses access, access will be denied.
For more information on this subject, please see `changing the access decision strategy`_
in the Symfony documentation.
Usage
~~~~~
You can now test if a user is authorized from an Admin class:
.. code-block:: php
if ($this->hasAccess('list')) {
// ...
}
From a controller extending ``Sonata\AdminBundle\Controller\CRUDController``:
.. code-block:: php
if ($this->admin->hasAccess('list')) {
// ...
}
Or from a Twig template:
.. code-block:: jinja
{% if is_granted('VIEW') %}
<p>Hello there!</p>
{% endif %}
Note that you do not have to re-specify the prefix.
Sonata checks those permissions for the action it handles internally.
Of course you will have to recheck them in your own code.
Yon can also create your own permissions, for example ``EMAIL``
(which will turn into role ``ROLE_APP_ADMIN_FOO_EMAIL``).
Going further
~~~~~~~~~~~~~
Because Sonata role handler supplements Symfony security, but does not override it, you are free to do more advanced operations.
For example, you can `create your own voter`_
Customizing the handler behavior
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you want to change the handler behavior (for example, to pass the current object to voters), extend
``Sonata\AdminBundle\Security\Handler\RoleSecurityHandler``, and override the ``isGranted`` method.
Then declare your handler as a service:
.. configuration-block::
.. code-block:: xml
<service id="app.security.handler.role" class="AppBundle\Security\Handler\RoleSecurityHandler" public="false">
<argument type="service" id="security.context" on-invalid="null" />
<argument type="collection">
<argument>ROLE_SUPER_ADMIN</argument>
</argument>
</service>
And specify it as Sonata security handler on your configuration:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
security:
handler: app.security.handler.role
ACL and FriendsOfSymfony/UserBundle
-----------------------------------
If you want an easy way to handle users, please use:
- `FOSUserBundle <https://github.com/FriendsOfSymfony/FOSUserBundle>`_: handles
users and groups stored in RDBMS or MongoDB
- `SonataUserBundle <https://github.com/sonata-project/SonataUserBundle>`_: integrates the
``FriendsOfSymfony/UserBundle`` with the ``AdminBundle``
The security integration is a work in progress and has some known issues:
- ACL permissions are immutables
- A listener must be implemented that creates the object Access Control List
with the required rules if objects are created outside the Admin
Configuration
~~~~~~~~~~~~~
Before you can use ``FriendsOfSymfony/FOSUserBundle`` you need to set it up as
described in the documentation of the bundle. In step 4 you need to create a
User class (in a custom UserBundle). Do it as follows:
.. code-block:: php
<?php
// src/AppBundle/Entity/User.php
namespace AppBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="fos_user")
*/
class User extends BaseUser
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
}
In your ``app/config/config.yml`` you then need to put the following:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\User
The following configuration for the SonataUserBundle defines:
- the ``FriendsOfSymfony/FOSUserBundle`` as a security provider
- the login form for authentication
- the access control: resources with related required roles, the important
part is the admin configuration
- the ``acl`` option to enable the ACL
- the ``AdminPermissionMap`` defines the permissions of the Admin class
.. configuration-block::
.. code-block:: yaml
# src/AppBundle/Resources/config/services.yml
# Symfony >= 3
services:
security.acl.permission.map:
class: Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap
# optionally use a custom MaskBuilder
#sonata.admin.security.mask.builder:
# class: Sonata\AdminBundle\Security\Acl\Permission\MaskBuilder
# Symfony < 3
parameters:
security.acl.permission.map.class: Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap
# optionally use a custom MaskBuilder
#sonata.admin.security.mask.builder.class: Sonata\AdminBundle\Security\Acl\Permission\MaskBuilder
In ``app/config/security.yml``:
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
providers:
fos_userbundle:
id: fos_user.user_manager
firewalls:
main:
pattern: .*
form-login:
provider: fos_userbundle
login_path: /login
use_forward: false
check_path: /login_check
failure_path: null
logout: true
anonymous: true
access_control:
# The WDT has to be allowed to anonymous users to avoid requiring the login with the AJAX request
- { path: ^/wdt/, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/profiler/, role: IS_AUTHENTICATED_ANONYMOUSLY }
# AsseticBundle paths used when using the controller for assets
- { path: ^/js/, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/css/, role: IS_AUTHENTICATED_ANONYMOUSLY }
# URL of FOSUserBundle which need to be available to anonymous users
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY } # for the case of a failed login
- { path: ^/user/new$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/check-confirmation-email$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/confirm/, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/confirmed$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/request-reset-password$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/send-resetting-email$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/check-resetting-email$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/user/reset-password/, role: IS_AUTHENTICATED_ANONYMOUSLY }
# Secured part of the site
# This config requires being logged for the whole site and having the admin role for the admin part.
# Change these rules to adapt them to your needs
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
acl:
connection: default
- Install the ACL tables ``php app/console init:acl``
- Create a new root user:
.. code-block:: bash
$ php app/console fos:user:create --super-admin
Please choose a username:root
Please choose an email:root@domain.com
Please choose a password:root
Created user root
If you have Admin classes, you can install or update the related CRUD ACL rules:
.. code-block:: bash
$ php app/console sonata:admin:setup-acl
Starting ACL AdminBundle configuration
> install ACL for sonata.media.admin.media
- add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_GUEST, permissions: ["VIEW","LIST"]
- add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_STAFF, permissions: ["EDIT","LIST","CREATE"]
- add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_EDITOR, permissions: ["OPERATOR","EXPORT"]
- add role: ROLE_SONATA_MEDIA_ADMIN_MEDIA_ADMIN, permissions: ["MASTER"]
... skipped ...
If you already have objects, you can generate the object ACL rules for each
object of an admin:
.. code-block:: bash
$ php app/console sonata:admin:generate-object-acl
Optionally, you can specify an object owner, and step through each admin. See
the help of the command for more information.
If you try to access to the admin class you should see the login form, just
log in with the ``root`` user.
An Admin is displayed in the dashboard (and menu) when the user has the role
``LIST``. To change this override the ``showIn`` method in the Admin class.
Roles and Access control lists
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A user can have several roles when working with an application. Each Admin class
has several roles, and each role specifies the permissions of the user for the
``Admin`` class. Or more specifically, what the user can do with the domain object(s)
the ``Admin`` class is created for.
By default each ``Admin`` class contains the following roles, override the
property ``$securityInformation`` to change this:
- ``ROLE_SONATA_..._GUEST``
a guest that is allowed to ``VIEW`` an object and a ``LIST`` of objects;
- ``ROLE_SONATA_..._STAFF``
probably the biggest part of the users, a staff user has the same permissions
as guests and is additionally allowed to ``EDIT`` and ``CREATE`` new objects;
- ``ROLE_SONATA_..._EDITOR``
an editor is granted all access and, compared to the staff users, is allowed to ``DELETE``;
- ``ROLE_SONATA_..._ADMIN``
an administrative user is granted all access and on top of that, the user is allowed to grant other users access.
Owner:
- when an object is created, the currently logged in user is set as owner for
that object and is granted all access for that object;
- this means the user owning the object is always allowed to ``DELETE`` the
object, even when they only have the staff role.
Vocabulary used for Access Control Lists:
- **Role:** a user role;
- **ACL:** a list of access rules, the Admin uses 2 types;
- **Admin ACL:** created from the Security information of the Admin class
for each admin and shares the Access Control Entries that specify what
the user can do (permissions) with the admin;
- **Object ACL:** also created from the security information of the ``Admin``
class however created for each object, it uses 2 scopes:
- **Class-Scope:** the class scope contains the rules that are valid
for all object of a certain class;
- **Object-Scope:** specifies the owner;
- **Sid:** Security identity, an ACL role for the Class-Scope ACL and the
user for the Object-Scope ACL;
- **Oid:** Object identity, identifies the ACL, for the admin ACL this is
the admin code, for the object ACL this is the object id;
- **ACE:** a role (or sid) and its permissions;
- **Permission:** this tells what the user is allowed to do with the Object
identity;
- **Bitmask:** a permission can have several bitmasks, each bitmask
represents a permission. When permission ``VIEW`` is requested and it
contains the ``VIEW`` and ``EDIT`` bitmask and the user only has the
``EDIT`` permission, then the permission ``VIEW`` is granted.
- **PermissionMap:** configures the bitmasks for each permission, to change
the default mapping create a voter for the domain class of the Admin.
There can be many voters that may have different permission maps. However,
prevent that multiple voters vote on the same class with overlapping bitmasks.
See the cookbook article "`Advanced ACL concepts
<http://symfony.com/doc/current/cookbook/security/acl_advanced.html#pre-authorization-decisions.>`_"
for the meaning of the different permissions.
How is access granted?
~~~~~~~~~~~~~~~~~~~~~~
In the application the security context is asked if access is granted for a role
or a permission (``admin.isGranted``):
- **Token:** a token identifies a user between requests;
- **Voter:** sort of judge that returns whether access is granted or denied, if the
voter should not vote for a case, it returns abstain;
- **AccessDecisionManager:** decides whether access is granted or denied according
a specific strategy. It grants access if at least one (affirmative strategy),
all (unanimous strategy) or more then half (consensus strategy) of the
counted votes granted access;
- **RoleVoter:** votes for all attributes stating with ``ROLE_`` and grants
access if the user has this role;
- **RoleHierarchyVoter:** when the role ``ROLE_SONATA_ADMIN`` is voted for,
it also votes "granted" if the user has the role ``ROLE_SUPER_ADMIN``;
- **AclVoter:** grants access for the permissions of the ``Admin`` class if
the user has the permission, the user has a permission that is included in
the bitmasks of the permission requested to vote for or the user owns the
object.
Create a custom voter or a custom permission map
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some occasions you need to create a custom voter or a custom permission map
because for example you want to restrict access using extra rules:
- create a custom voter class that extends the ``AclVoter``
.. code-block:: php
<?php
// src/AppBundle/Security/Authorization/Voter/UserAclVoter.php
namespace AppBundle\Security\Authorization\Voter;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Acl\Voter\AclVoter;
class UserAclVoter extends AclVoter
{
public function supportsClass($class)
{
// support the Class-Scope ACL for votes with the custom permission map
// return $class === 'Sonata\UserBundle\Admin\Entity\UserAdmin' || is_subclass_of($class, 'FOS\UserBundle\Model\UserInterface');
// if you use php >=5.3.7 you can check the inheritance with is_a($class, 'Sonata\UserBundle\Admin\Entity\UserAdmin');
// support the Object-Scope ACL
return is_subclass_of($class, 'FOS\UserBundle\Model\UserInterface');
}
public function supportsAttribute($attribute)
{
return $attribute === 'EDIT' || $attribute === 'DELETE';
}
public function vote(TokenInterface $token, $object, array $attributes)
{
if (!$this->supportsClass(get_class($object))) {
return self::ACCESS_ABSTAIN;
}
foreach ($attributes as $attribute) {
if ($this->supportsAttribute($attribute) && $object instanceof UserInterface) {
if ($object->isSuperAdmin() && !$token->getUser()->isSuperAdmin()) {
// deny a non super admin user to edit a super admin user
return self::ACCESS_DENIED;
}
}
}
// use the parent vote with the custom permission map:
// return parent::vote($token, $object, $attributes);
// otherwise leave the permission voting to the AclVoter that is using the default permission map
return self::ACCESS_ABSTAIN;
}
}
- optionally create a custom permission map, copy to start the
``Sonata\AdminBundle\Security\Acl\Permission\AdminPermissionMap.php`` to
your bundle
- declare the voter and permission map as a service
.. configuration-block::
.. code-block:: xml
<!-- src/AppBundle/Resources/config/services.xml -->
<!-- <service id="security.acl.user_permission.map" class="AppBundle\Security\Acl\Permission\UserAdminPermissionMap" public="false"></service> -->
<service id="security.acl.voter.user_permissions" class="AppBundle\Security\Authorization\Voter\UserAclVoter" public="false">
<tag name="monolog.logger" channel="security" />
<argument type="service" id="security.acl.provider" />
<argument type="service" id="security.acl.object_identity_retrieval_strategy" />
<argument type="service" id="security.acl.security_identity_retrieval_strategy" />
<argument type="service" id="security.acl.permission.map" />
<argument type="service" id="logger" on-invalid="null" />
<tag name="security.voter" priority="255" />
</service>
- change the access decision strategy to ``unanimous``
.. configuration-block::
.. code-block:: yaml
# app/config/security.yml
security:
access_decision_manager:
# strategy value can be: affirmative, unanimous or consensus
strategy: unanimous
- to make this work the permission needs to be checked using the Object ACL
- modify the template (or code) where applicable:
.. code-block:: html+jinja
{% if admin.hasAccess('edit', user_object) %}
{# ... #}
{% endif %}
- because the object ACL permission is checked, the ACL for the object must
have been created, otherwise the ``AclVoter`` will deny ``EDIT`` access
for a non super admin user trying to edit another non super admin user.
This is automatically done when the object is created using the Admin.
If objects are also created outside the Admin, have a look at the
``createSecurityObject`` method in the ``AclSecurityHandler``.
Usage
~~~~~
Every time you create a new ``Admin`` class, you should start with the command
``php app/console sonata:admin:setup-acl`` so the ACL database will be updated
with the latest roles and permissions.
In the templates, or in your code, you can use the Admin method ``hasAccess()``:
- check for an admin that the user is allowed to ``EDIT``:
.. code-block:: html+jinja
{# use the admin security method #}
{% if admin.hasAccess('edit') %}
{# ... #}
{% endif %}
{# or use the default is_granted Symfony helper, the following will give the same result #}
{% if is_granted('ROLE_SUPER_ADMIN') or is_granted('EDIT', admin) %}
{# ... #}
{% endif %}
- check for an admin that the user is allowed to ``DELETE``, the object is added
to also check if the object owner is allowed to ``DELETE``:
.. code-block:: html+jinja
{# use the admin security method #}
{% if admin.hasAccess('delete', object) %}
{# ... #}
{% endif %}
{# or use the default is_granted Symfony helper, the following will give the same result #}
{% if is_granted('ROLE_SUPER_ADMIN') or is_granted('DELETE', object) %}
{# ... #}
{% endif %}
List filtering
~~~~~~~~~~~~~~
List filtering using ACL is available as a third party bundle:
`CoopTilleulsAclSonataAdminExtensionBundle <https://github.com/coopTilleuls/CoopTilleulsAclSonataAdminExtensionBundle>`_.
When enabled, the logged in user will only see the objects for which it has the `VIEW` right (or superior).
ACL editor
----------
SonataAdminBundle provides a user-friendly ACL editor
interface.
It will be automatically available if the ``sonata.admin.security.handler.acl``
security handler is used and properly configured.
The ACL editor is only available for users with `OWNER` or `MASTER` permissions
on the object instance.
The `OWNER` and `MASTER` permissions can only be edited by an user with the
`OWNER` permission on the object instance.
.. figure:: ../images/acl_editor.png
:align: center
:alt: The ACL editor
:width: 700px
User list customization
~~~~~~~~~~~~~~~~~~~~~~~
By default, the ACL editor allows to set permissions for all users managed by
``FOSUserBundle``.
To customize displayed user override
``Sonata\AdminBundle\Controller\CRUDController::getAclUsers()``. This method must
return an iterable collection of users.
.. code-block:: php
protected function getAclUsers()
{
$userManager = $container->get('fos_user.user_manager');
// Display only kevin and anne
$users[] = $userManager->findUserByUsername('kevin');
$users[] = $userManager->findUserByUsername('anne');
return new \ArrayIterator($users);
}
Role list customization
~~~~~~~~~~~~~~~~~~~~~~~
By default, the ACL editor allows to set permissions for all roles.
To customize displayed role override
``Sonata\AdminBundle\Controller\CRUDController::getAclRoles()``. This method must
return an iterable collection of roles.
.. code-block:: php
protected function getAclRoles()
{
// Display only ROLE_BAPTISTE and ROLE_HELENE
$roles = array(
'ROLE_BAPTISTE',
'ROLE_HELENE'
);
return new \ArrayIterator($roles);
}
Custom user manager
~~~~~~~~~~~~~~~~~~~
If your project does not use `FOSUserBundle`, you can globally configure another
service to use when retrieving your users.
- Create a service with a method called ``findUsers()`` returning an iterable
collection of users
- Update your admin configuration to reference your service name
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
security:
# the name of your service
acl_user_manager: my_user_manager
.. _`SonataUserBundle's documentation area`: https://sonata-project.org/bundles/user/master/doc/reference/installation.html
.. _`changing the access decision strategy`: http://symfony.com/doc/2.2/cookbook/security/voters.html#changing-the-access-decision-strategy
.. _`create your own voter`: http://symfony.com/doc/2.2/cookbook/security/voters.html

View File

@@ -0,0 +1,198 @@
Templates
=========
``SonataAdminBundle`` comes with a significant amount of ``twig`` files used to display the
different parts of each ``Admin`` action's page. If you read the ``Templates`` part of the
:doc:`architecture` section of this guide, you should know by now how these are organized in
the ``views`` folder. If you haven't, now would be a good time to do it.
Besides these, some other views files are included from the storage layer. As their content and
structure are specific to each implementation, they are not discussed here, but it's important
that you keep in mind that they exist and are as relevant as the view files included
directly in ``SonataAdminBundle``.
Global Templates
----------------
``SonataAdminBundle`` views are implemented using ``twig`` files, and take full advantage of its
inheritance capabilities. As such, even the most simple page is actually rendered using many
different ``twig`` files. At the end of that ``twig`` inheritance hierarchy is always one of two files:
* layout: SonataAdminBundle::standard_layout.html.twig
* ajax: SonataAdminBundle::ajax_layout.html.twig
As you might have guessed from their names, the first is used in 'standard' request and the other
for AJAX calls. The ``SonataAdminBundle::standard_layout.html.twig`` contains several elements which
exist across the whole page, like the logo, title, upper menu and menu. It also includes the base CSS
and JavaScript files and libraries used across the whole administration section. The AJAX template
doesn't include any of these elements.
Dashboard Template
------------------
The template used for rendering the dashboard can also be configured. See the :doc:`dashboard` page
for more information
CRUDController Actions Templates
--------------------------------
As seen before, the ``CRUDController`` has several actions that allow you to manipulate your
model instances. Each of those actions uses a specific template file to render its content.
By default, ``SonataAdminBundle`` uses the following templates for their matching action:
* ``list`` : SonataAdminBundle:CRUD:list.html.twig
* ``show`` : SonataAdminBundle:CRUD:show.html.twig
* ``edit`` : SonataAdminBundle:CRUD:edit.html.twig
* ``history`` : SonataAdminBundle:CRUD:history.html.twig
* ``preview`` : SonataAdminBundle:CRUD:preview.html.twig
* ``delete`` : SonataAdminBundle:CRUD:delete.html.twig
* ``batch_confirmation`` : SonataAdminBundle:CRUD:batch_confirmation.html.twig
* ``acl`` : SonataAdminBundle:CRUD:acl.html.twig
Notice that all these templates extend other templates, and some do only that. This inheritance
architecture is designed to help you easily make customizations by extending these templates
in your own bundle, rather than rewriting everything.
If you look closely, all of these templates ultimately extend the ``base_template`` variable that's
passed from the controller. This variable will always take the value of one of the above mentioned
global templates, and this is how changes made to those files affect all the ``SonataAdminBundle``
interface.
Row Templates
-------------
It is possible to completely change how each row of results is rendered in the
list view, by customizing the ``inner_list_row`` and ``base_list_field`` templates.
For more information about this, see the :doc:`../cookbook/recipe_row_templates`
cookbook entry.
Other Templates
---------------
There are several other templates that can be customized, enabling you to fine-tune
``SonataAdminBundle``:
* ``user_block`` : customizes the Twig block rendered by default in the top right
corner of the admin interface, containing user information.
Empty by default, see ``SonataUserBundle`` for a real example.
* ``add_block`` : customizes the Twig block rendered by default in the top right
corner of the admin interface, providing quick access to create operations on
available admin classes.
* ``history_revision_timestamp:`` customizes the way timestamps are rendered when
using history related actions.
* ``action`` : a generic template you can use for your custom actions
* ``short_object_description`` : used by the ``getShortObjectDescriptionAction``
action from the ``HelperController``, this template displays a small
description of a model instance.
* ``list_block`` : the template used to render the dashboard's admin mapping lists.
More info on the :doc:`dashboard` page.
* batch: template used to render the checkboxes that precede each instance on list views.
* ``select`` : when loading list views as part of sonata_admin form types, this
template is used to create a button that allows you to select the matching line.
* ``pager_links`` : renders the list of pages displayed at the end of the list view
(when more than one page exists)
* ``pager_results`` : renders the dropdown that lets you choose the number of
elements per page on list views
Configuring templates
---------------------
Like said before, the main goal of this template structure is to make it easy for you
to customize the ones you need. You can simply extend the ones you want in your own bundle,
and tell ``SonataAdminBundle`` to use your templates instead of the default ones. You can do so
in several ways.
You can specify your templates in the config.yml file, like so:
.. configuration-block::
.. code-block:: yaml
# app/config/config.yml
sonata_admin:
templates:
layout: SonataAdminBundle::standard_layout.html.twig
ajax: SonataAdminBundle::ajax_layout.html.twig
list: SonataAdminBundle:CRUD:list.html.twig
show: SonataAdminBundle:CRUD:show.html.twig
show_compare: SonataAdminBundle:CRUD:show_compare.html.twig
edit: SonataAdminBundle:CRUD:edit.html.twig
history: SonataAdminBundle:CRUD:history.html.twig
preview: SonataAdminBundle:CRUD:preview.html.twig
delete: SonataAdminBundle:CRUD:delete.html.twig
batch: SonataAdminBundle:CRUD:list__batch.html.twig
acl: SonataAdminBundle:CRUD:acl.html.twig
action: SonataAdminBundle:CRUD:action.html.twig
select: SonataAdminBundle:CRUD:list__select.html.twig
filter: SonataAdminBundle:Form:filter_admin_fields.html.twig
dashboard: SonataAdminBundle:Core:dashboard.html.twig
search: SonataAdminBundle:Core:search.html.twig
batch_confirmation: SonataAdminBundle:CRUD:batch_confirmation.html.twig
inner_list_row: SonataAdminBundle:CRUD:list_inner_row.html.twig
base_list_field: SonataAdminBundle:CRUD:base_list_field.html.twig
list_block: SonataAdminBundle:Block:block_admin_list.html.twig
user_block: SonataAdminBundle:Core:user_block.html.twig
add_block: SonataAdminBundle:Core:add_block.html.twig
pager_links: SonataAdminBundle:Pager:links.html.twig
pager_results: SonataAdminBundle:Pager:results.html.twig
tab_menu_template: SonataAdminBundle:Core:tab_menu_template.html.twig
history_revision_timestamp: SonataAdminBundle:CRUD:history_revision_timestamp.html.twig
short_object_description: SonataAdminBundle:Helper:short-object-description.html.twig
search_result_block: SonataAdminBundle:Block:block_search_result.html.twig
action_create: SonataAdminBundle:CRUD:dashboard__action_create.html.twig
button_acl: SonataAdminBundle:Button:acl_button.html.twig
button_create: SonataAdminBundle:Button:create_button.html.twig
button_edit: SonataAdminBundle:Button:edit_button.html.twig
button_history: SonataAdminBundle:Button:history_button.html.twig
button_list: SonataAdminBundle:Button:list_button.html.twig
button_show: SonataAdminBundle:Button:show_button.html.twig
Notice that this is a global change, meaning it will affect all model mappings automatically,
both for ``Admin`` mappings defined by you and by other bundles.
If you wish, you can specify custom templates on a per ``Admin`` mapping basis. Internally,
the ``CRUDController`` fetches this information from the ``Admin`` class instance, so you can
specify the templates to use in the ``Admin`` service definition:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.post" class="AppBundle\Admin\PostAdmin">
<tag name="sonata.admin" manager_type="orm" group="Content" label="Post" />
<argument />
<argument>AppBundle\Entity\Post</argument>
<argument />
<call method="setTemplate">
<argument>edit</argument>
<argument>AppBundle:PostAdmin:edit.html.twig</argument>
</call>
</service>
.. code-block:: yaml
services:
app.admin.post:
class: AppBundle\Admin\PostAdmin
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Post" }
arguments:
- ~
- AppBundle\Entity\Post
- ~
calls:
- [ setTemplate, [edit, AppBundle:PostAdmin:edit.html.twig]]
public: true
.. note::
A ``setTemplates(array $templates)`` (notice the plural) function also exists, that allows
you to set multiple templates at once. Notice that, if used outside of the service definition
context, ``setTemplates(array $templates)`` will replace the whole template list for that
``Admin`` class, meaning you have to explicitly pass the full template list in the
``$templates`` argument.
Changes made using the ``setTemplate()`` and ``setTemplates()`` functions override the customizations
made in the configuration file, so you can specify a global custom template and then override that
customization on a specific ``Admin`` class.

View File

@@ -0,0 +1,187 @@
Translation
===========
There are two main catalogue names in an Admin class:
* ``SonataAdminBundle``: this catalogue is used to translate shared messages
across different Admins
* ``messages``: this catalogue is used to translate the messages for the current
Admin
Ideally the ``messages`` catalogue should be changed to avoid any issues with
other Admin classes.
You have two options to configure the catalogue for the Admin class:
* override the ``$translationDomain`` property
.. code-block:: php
<?php
class PageAdmin extends AbstractAdmin
{
protected $translationDomain = 'SonataPageBundle'; // default is 'messages'
}
* inject the value through the container
.. configuration-block::
.. code-block:: xml
<service id="sonata.page.admin.page" class="Sonata\PageBundle\Admin\PageAdmin">
<tag name="sonata.admin" manager_type="orm" group="sonata_page" label="Page" />
<argument />
<argument>Application\Sonata\PageBundle\Entity\Page</argument>
<argument />
<call method="setTranslationDomain">
<argument>SonataPageBundle</argument>
</call>
</service>
An Admin instance always gets the ``translator`` instance, so it can be used to
translate messages within the ``configureFields`` method or in templates.
.. code-block:: jinja
{# the classical call by using the twig trans helper #}
{{ 'message_create_snapshots'|trans({}, 'SonataPageBundle') }}
{# by using the admin trans method with hardcoded catalogue #}
{{ 'message_create_snapshots'|trans({}, 'SonataPageBundle') }}
{# by using the admin trans with the configured catalogue #}
{{ 'message_create_snapshots'|trans({}, admin.translationdomain) }}
The last solution is most flexible, as no catalogue parameters are hardcoded, and is the recommended one to use.
Translate field labels
----------------------
The Admin bundle comes with a customized form field template. The most notable
change from the original one is the use of the translation domain provided by
either the Admin instance or the field description to translate labels.
Overriding the translation domain
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The translation domain (message catalog) can be overridden at either the form
group or individual field level.
If a translation domain is set at the group level it will cascade down to all
fields within the group.
Overriding the translation domain is of particular use when using
:doc:`extensions`, where the extension and the translations would
be defined in one bundle, but implemented in many different Admin instances.
Setting the translation domain on an individual field:
.. code-block:: php
$formMapper
->with('form.my_group')
->add('publishable', 'checkbox', array(), array(
'translation_domain' => 'MyTranslationDomain',
))
->end()
;
The following example sets the default translation domain on a form group and
over-rides that setting for one of the fields:
.. code-block:: php
$formMapper
->with('form.my_group', array('translation_domain' => 'MyDomain'))
->add('publishable', 'checkbox', array(), array(
'translation_domain' => 'AnotherDomain',
))
->add('start_date', 'date', array(), array())
->end()
;
Translation can also be disabled on a specific field by setting
``translation_domain`` to ``false``.
Setting the label name
^^^^^^^^^^^^^^^^^^^^^^
By default, the label is set to a sanitized version of the field name. A custom
label can be defined as the third argument of the ``add`` method:
.. code-block:: php
class PageAdmin extends AbstractAdmin
{
public function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('isValid', null, array(
'required' => false,
'label' => 'label.is_valid',
))
;
}
}
Label strategies
^^^^^^^^^^^^^^^^
There is another option for rapid prototyping or to avoid spending too much time
adding the ``label`` key to all option fields: **Label Strategies**. By default
labels are generated by using a simple rule:
``isValid => Is Valid``
The ``AdminBundle`` comes with different key label generation strategies:
* ``sonata.admin.label.strategy.native``: DEFAULT - Makes the string human readable
``isValid`` => ``Is Valid``
* ``sonata.admin.label.strategy.form_component``: The default behavior from the Form Component
``isValid`` => ``Isvalid``
* ``sonata.admin.label.strategy.underscore``: Changes the name into a token suitable
for translation by prepending "form.label" to an underscored version of the field name
``isValid`` => ``form.label_is_valid``
* ``sonata.admin.label.strategy.noop``: does not alter the string
``isValid`` => ``isValid``
``sonata.admin.label.strategy.underscore`` will be better for i18n applications
and ``sonata.admin.label.strategy.native`` will be better for native (single) language
apps based on the field name. It is reasonable to start with the ``native`` strategy
and then, when the application needs to be translated using generic keys, the
configuration can be switched to ``underscore``.
The strategy can be quickly configured when the Admin class is registered in
the Container:
.. configuration-block::
.. code-block:: xml
<service id="app.admin.project" class="AppBundle\Admin\ProjectAdmin">
<tag
name="sonata.admin"
manager_type="orm"
group="Project"
label="Project"
label_translator_strategy="sonata.admin.label.strategy.native"
/>
<argument />
<argument>AppBundle\Entity\Project</argument>
<argument />
</service>
.. note::
In all cases the label will be used by the ``Translator``. The strategy is
just a quick way to generate translatable keys. It all depends on the
project's requirements.
.. note::
When the strategy method is called, ``context`` (breadcrumb, datagrid, filter,
form, list, show, etc.) and ``type`` (usually link or label) arguments are passed.
For example, the call may look like: ``getLabel($label_key, 'breadcrumb', 'link')``

View File

@@ -0,0 +1,80 @@
Troubleshooting
===============
The toString method
-------------------
Sometimes the bundle needs to display your model objects, in order to do it,
objects are converted to string by using the `__toString`_ magic method.
Take care to never return anything else than a string in this method.
For example, if your method looks like that :
.. code-block:: php
<?php
// src/AppBundle/Entity/Post.php
class Post
{
// ...
public function __toString()
{
return $this->getTitle();
}
// ...
}
You cannot be sure your object will *always* have a title when the bundle will want to convert it to a string.
So in order to avoid any fatal error, you must return an empty string
(or anything you prefer) for when the title is missing, like this :
.. code-block:: php
<?php
// src/AppBundle/Entity/Post.php
class Post
{
// ...
public function __toString()
{
return $this->getTitle() ?: '';
}
// ...
}
.. _`__toString`: http://www.php.net/manual/en/language.oop5.magic.php#object.tostring
Large filters and long URLs problem
-----------------------------------
If you will try to add hundreds of filters to a single admin class, you will get a problem - very long generated filter form URL.
In most cases you will get server response like *Error 400 Bad Request* OR *Error 414 Request-URI Too Long*. According to
`a StackOverflow discussion <http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers>`_
"safe" URL length is just around 2000 characters.
You can fix this issue by adding a simple JQuery piece of code on your edit template :
.. code-block:: javascript
$(function() {
// Add class 'had-value-on-load' to inputs/selects with values.
$(".sonata-filter-form input").add(".sonata-filter-form select").each(function(){
if($(this).val()) {
$(this).addClass('had-value-on-load');
}
});
// REMOVE ALL EMPTY INPUT FROM FILTER FORM (except inputs, which has class 'had-value-on-load')
$(".sonata-filter-form").submit(function() {
$(".sonata-filter-form input").add(".sonata-filter-form select").each(function(){
if(!$(this).val() && !$(this).hasClass('had-value-on-load')) {
$(this).remove()
};
});
});
});