-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature #738 Refactored the way configuration is managed (javiereguiluz)
This PR was squashed before being merged into the master branch (closes #738). Discussion ---------- Refactored the way configuration is managed For end-users this pull request doesn't change anything. This is just a refactor. You don't have to change anything or care for anything. Everything will work as before, except a bit faster. --- For people looking into the bundle internals: previously we processed the configuration in two steps (the first half of the config before the app started and the other half during runtime). Now we process the entire configuration before the app runs. We do this using a compiler pass. For that reason performance is better because nothing related to config is parsed during runtime. The other important change is the introduction of `ConfigPass` classes. Instead of having one single class parsing the entire config, we now have smaller and dedicated class to parse the config in several passes. --- Tests pass, except for PHP 5.3 and `COMPOSER_FLAGS=--prefer-lowest` which shows error messages like this one ([see full details](https://travis-ci.org/javiereguiluz/EasyAdminBundle/jobs/99936347)): ``` Template name "@EasyAdmin/default/list.html.twig" is not valid (format is "bundle:section:template.format.engine"). (500 Internal Server Error) ``` Commits ------- 95729a6 Refactored the way configuration is managed
- Loading branch information
Showing
510 changed files
with
110,634 additions
and
27,744 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,53 +9,57 @@ | |
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace JavierEguiluz\Bundle\EasyAdminBundle\Configuration\Normalizer; | ||
namespace JavierEguiluz\Bundle\EasyAdminBundle\Configuration; | ||
|
||
/** | ||
* Merges all the actions that can be configured in the backend and normalizes | ||
* them to get the final action configuration for each entity view. | ||
* | ||
* @author Javier Eguiluz <[email protected]> | ||
*/ | ||
class ActionNormalizer implements NormalizerInterface | ||
class ActionConfigPass implements ConfigPassInterface | ||
{ | ||
private $defaultActionConfiguration = array( | ||
'name' => null, // either the name of a controller method or an application route (it depends on the 'type' option) | ||
'type' => 'method', // 'method' if the action is a controller method; 'route' if it's an application route | ||
'label' => null, // action label (displayed as link or button) (if 'null', autogenerate it) | ||
'css_class' => '', // the CSS class applied to the button/link displayed by the action | ||
'icon' => null, // the name of the FontAwesome icon to display next to the 'label' (doesn't include the 'fa-' prefix) | ||
private $defaultActionConfig = array( | ||
// either the name of a controller method or an application route (it depends on the 'type' option) | ||
'name' => null, | ||
// 'method' if the action is a controller method; 'route' if it's an application route | ||
'type' => 'method', | ||
// action label (displayed as link or button) (if 'null', autogenerate it) | ||
'label' => null, | ||
// the CSS class applied to the button/link displayed by the action | ||
'css_class' => '', | ||
// the name of the FontAwesome icon to display next to the 'label' (doesn't include the 'fa-' prefix) | ||
'icon' => null, | ||
); | ||
|
||
public function normalize(array $backendConfiguration) | ||
public function process(array $backendConfig) | ||
{ | ||
$entitiesConfiguration = array(); | ||
|
||
foreach ($backendConfiguration['entities'] as $entityName => $entityConfiguration) { | ||
$entitiesConfig = array(); | ||
foreach ($backendConfig['entities'] as $entityName => $entityConfig) { | ||
// first, define the disabled actions | ||
$actionsDisabledByBackend = $backendConfiguration['disabled_actions']; | ||
$actionsDisabledByEntity = isset($entityConfiguration['disabled_actions']) ? $entityConfiguration['disabled_actions'] : array(); | ||
$actionsDisabledByBackend = $backendConfig['disabled_actions']; | ||
$actionsDisabledByEntity = isset($entityConfig['disabled_actions']) ? $entityConfig['disabled_actions'] : array(); | ||
$disabledActions = array_unique(array_merge($actionsDisabledByBackend, $actionsDisabledByEntity)); | ||
$entityConfiguration['disabled_actions'] = $disabledActions; | ||
$entityConfig['disabled_actions'] = $disabledActions; | ||
|
||
// second, define the actions of each entity view | ||
foreach (array('edit', 'list', 'new', 'show') as $view) { | ||
$defaultActions = $this->getDefaultActions($view); | ||
$backendActions = isset($backendConfiguration[$view]['actions']) ? $backendConfiguration[$view]['actions'] : array(); | ||
$backendActions = $this->normalizeActionsConfiguration($backendActions, $defaultActions); | ||
$backendActions = isset($backendConfig[$view]['actions']) ? $backendConfig[$view]['actions'] : array(); | ||
$backendActions = $this->normalizeActionsConfig($backendActions, $defaultActions); | ||
|
||
$defaultViewActions = array_replace($defaultActions, $backendActions); | ||
$defaultViewActions = $this->filterRemovedActions($defaultViewActions); | ||
|
||
$entityActions = isset($entityConfiguration[$view]['actions']) ? $entityConfiguration[$view]['actions'] : array(); | ||
$entityActions = $this->normalizeActionsConfiguration($entityActions, $defaultViewActions); | ||
$entityActions = isset($entityConfig[$view]['actions']) ? $entityConfig[$view]['actions'] : array(); | ||
$entityActions = $this->normalizeActionsConfig($entityActions, $defaultViewActions); | ||
|
||
$viewActions = array_replace($defaultViewActions, $entityActions); | ||
$viewActions = $this->filterRemovedActions($viewActions); | ||
|
||
// 'list' action is mandatory for all views | ||
if (!array_key_exists('list', $viewActions)) { | ||
$viewActions = array_merge($viewActions, $this->normalizeActionsConfiguration(array('list'))); | ||
$viewActions = array_merge($viewActions, $this->normalizeActionsConfig(array('list'))); | ||
} | ||
|
||
if (isset($viewActions['delete'])) { | ||
|
@@ -66,15 +70,15 @@ public function normalize(array $backendConfiguration) | |
} | ||
} | ||
|
||
$entityConfiguration[$view]['actions'] = $viewActions; | ||
$entityConfig[$view]['actions'] = $viewActions; | ||
} | ||
|
||
$entitiesConfiguration[$entityName] = $entityConfiguration; | ||
$entitiesConfig[$entityName] = $entityConfig; | ||
} | ||
|
||
$backendConfiguration['entities'] = $entitiesConfiguration; | ||
$backendConfig['entities'] = $entitiesConfig; | ||
|
||
return $backendConfiguration; | ||
return $backendConfig; | ||
} | ||
|
||
/** | ||
|
@@ -89,20 +93,20 @@ public function normalize(array $backendConfiguration) | |
private function getDefaultActions($view) | ||
{ | ||
// basic configuration for default actions | ||
$actions = $this->normalizeActionsConfiguration(array( | ||
$actions = $this->normalizeActionsConfig(array( | ||
array('name' => 'delete', 'label' => 'action.delete', 'icon' => 'trash'), | ||
array('name' => 'edit', 'label' => 'action.edit', 'icon' => 'edit'), | ||
array('name' => 'new', 'label' => 'action.new'), | ||
array('name' => 'edit', 'label' => 'action.edit', 'icon' => 'edit'), | ||
array('name' => 'new', 'label' => 'action.new'), | ||
array('name' => 'search', 'label' => 'action.search'), | ||
array('name' => 'show', 'label' => 'action.show'), | ||
array('name' => 'list', 'label' => 'action.list'), | ||
array('name' => 'show', 'label' => 'action.show'), | ||
array('name' => 'list', 'label' => 'action.list'), | ||
)); | ||
|
||
// define which actions are enabled for each view | ||
$actionsPerView = array( | ||
'edit' => array('delete' => $actions['delete'], 'list' => $actions['list']), | ||
'list' => array('show' => $actions['show'], 'edit' => $actions['edit'], 'search' => $actions['search'], 'new' => $actions['new']), | ||
'new' => array('list' => $actions['list']), | ||
'new' => array('list' => $actions['list']), | ||
'show' => array('delete' => $actions['delete'], 'list' => $actions['list'], 'edit' => $actions['edit']), | ||
); | ||
|
||
|
@@ -114,7 +118,7 @@ private function getDefaultActions($view) | |
|
||
/** | ||
* Transforms the different action configuration formats into a normalized | ||
* and expanded format. These are the two simple formats allowed: | ||
* and expanded format. These are the two simple formats allowed:. | ||
* | ||
* # Config format #1: no custom option | ||
* easy_admin: | ||
|
@@ -130,32 +134,32 @@ private function getDefaultActions($view) | |
* list: | ||
* actions: ['search', { name: 'show', label: 'Show', 'icon': 'user' }, 'grantAccess'] | ||
* | ||
* @param array $actionsConfiguration | ||
* @param array $defaultActionsConfiguration | ||
* @param array $actionsConfig | ||
* @param array $defaultActionsConfig | ||
* | ||
* @return array | ||
*/ | ||
private function normalizeActionsConfiguration(array $actionsConfiguration, array $defaultActionsConfiguration = array()) | ||
private function normalizeActionsConfig(array $actionsConfig, array $defaultActionsConfig = array()) | ||
{ | ||
$configuration = array(); | ||
|
||
foreach ($actionsConfiguration as $action) { | ||
foreach ($actionsConfig as $action) { | ||
if (is_string($action)) { | ||
// config format #1 | ||
$actionConfiguration = array('name' => $action); | ||
$actionConfig = array('name' => $action); | ||
} elseif (is_array($action)) { | ||
// config format #2 | ||
$actionConfiguration = $action; | ||
$actionConfig = $action; | ||
} else { | ||
throw new \RuntimeException('The values of the "actions" option can only be strings or arrays.'); | ||
} | ||
|
||
// 'name' is the only mandatory option for actions | ||
if (!isset($actionConfiguration['name'])) { | ||
if (!isset($actionConfig['name'])) { | ||
throw new \RuntimeException('When using the expanded configuration format for actions, you must define their "name" option.'); | ||
} | ||
|
||
$actionName = $actionConfiguration['name']; | ||
$actionName = $actionConfig['name']; | ||
|
||
// 'name' value is used as the class method name or the Symfony route name | ||
// check that its value complies with the PHP method name rules (the leading dash | ||
|
@@ -164,43 +168,43 @@ private function normalizeActionsConfiguration(array $actionsConfiguration, arra | |
throw new \InvalidArgumentException(sprintf('The name of the "%s" action contains invalid characters (allowed: letters, numbers, underscores; the first character cannot be a number).', $actionName)); | ||
} | ||
|
||
$normalizedConfiguration = array_replace($this->defaultActionConfiguration, $actionConfiguration); | ||
$normalizedConfig = array_replace($this->defaultActionConfig, $actionConfig); | ||
|
||
$actionName = $normalizedConfiguration['name']; | ||
$actionName = $normalizedConfig['name']; | ||
|
||
// use the special 'action.<action name>' label for the default actions | ||
// only if the user hasn't defined a custom label (which can also be an empty string) | ||
if (null === $normalizedConfiguration['label'] && in_array($actionName, array('delete', 'edit', 'new', 'search', 'show', 'list'))) { | ||
$normalizedConfiguration['label'] = 'action.'.$actionName; | ||
if (null === $normalizedConfig['label'] && in_array($actionName, array('delete', 'edit', 'new', 'search', 'show', 'list'))) { | ||
$normalizedConfig['label'] = 'action.'.$actionName; | ||
} | ||
|
||
// the rest of actions without a custom label use their name as label | ||
if (null === $normalizedConfiguration['label']) { | ||
if (null === $normalizedConfig['label']) { | ||
// copied from Symfony\Component\Form\FormRenderer::humanize() (author: Bernhard Schussek <[email protected]>) | ||
$label = ucfirst(trim(strtolower(preg_replace(array('/([A-Z])/', '/[_\s]+/'), array('_$1', ' '), $actionName)))); | ||
$normalizedConfiguration['label'] = $label; | ||
$normalizedConfig['label'] = $label; | ||
} | ||
|
||
if (count($defaultActionsConfiguration)) { | ||
if (count($defaultActionsConfig)) { | ||
// if the user defines an action with the same name of a default action, | ||
// he/she is in fact overriding the default configuration of that action. | ||
// for example: actions: ['delete', 'list'] | ||
// this condition ensures that when the user doesn't define the value for | ||
// some option of the action (for example the icon or the label) that | ||
// option is actually added with the right default value. Otherwise, | ||
// those options would be 'null' and the template would show some issues | ||
if (array_key_exists($actionName, $defaultActionsConfiguration)) { | ||
if (array_key_exists($actionName, $defaultActionsConfig)) { | ||
// remove null config options but maintain empty options (this allows to set an empty label for the action) | ||
$normalizedConfiguration = array_filter($normalizedConfiguration, function ($element) { return null !== $element; }); | ||
$normalizedConfiguration = array_replace($defaultActionsConfiguration[$actionName], $normalizedConfiguration); | ||
$normalizedConfig = array_filter($normalizedConfig, function ($element) { return null !== $element; }); | ||
$normalizedConfig = array_replace($defaultActionsConfig[$actionName], $normalizedConfig); | ||
} | ||
} | ||
|
||
// Add default classes ("action-{actionName}") to each action configuration | ||
$normalizedConfiguration['css_class'] .= ' action-'.$normalizedConfiguration['name']; | ||
$normalizedConfiguration['css_class'] = ltrim($normalizedConfiguration['css_class']); | ||
$normalizedConfig['css_class'] .= ' action-'.$normalizedConfig['name']; | ||
$normalizedConfig['css_class'] = ltrim($normalizedConfig['css_class']); | ||
|
||
$configuration[$actionName] = $normalizedConfiguration; | ||
$configuration[$actionName] = $normalizedConfig; | ||
} | ||
|
||
return $configuration; | ||
|
@@ -209,22 +213,22 @@ private function normalizeActionsConfiguration(array $actionsConfiguration, arra | |
/** | ||
* Removes the actions marked as deleted from the given actions configuration. | ||
* | ||
* @param array $actionsConfiguration | ||
* @param array $actionsConfig | ||
* | ||
* @return array | ||
*/ | ||
private function filterRemovedActions(array $actionsConfiguration) | ||
private function filterRemovedActions(array $actionsConfig) | ||
{ | ||
// if the name of the action starts with a dash ('-'), remove it | ||
$removedActions = array_filter($actionsConfiguration, function ($action) { | ||
$removedActions = array_filter($actionsConfig, function ($action) { | ||
return '-' === $action['name']{0}; | ||
}); | ||
|
||
if (empty($removedActions)) { | ||
return $actionsConfiguration; | ||
return $actionsConfig; | ||
} | ||
|
||
return array_filter($actionsConfiguration, function ($action) use ($removedActions) { | ||
return array_filter($actionsConfig, function ($action) use ($removedActions) { | ||
// e.g. '-search' action name removes both '-search' and 'search' (if exists) | ||
return !array_key_exists($action['name'], $removedActions) | ||
&& !array_key_exists('-'.$action['name'], $removedActions); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the EasyAdminBundle. | ||
* | ||
* (c) Javier Eguiluz <[email protected]> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace JavierEguiluz\Bundle\EasyAdminBundle\Configuration; | ||
|
||
/** | ||
* The interface that must be implemented by all the classes that normalize, | ||
* parse, complete or manipulate in any way the original backend configuration | ||
* in order to generate the final backend configuration. This allows the | ||
* end-user to use shortcuts and syntactic sugar to define the backend configuration. | ||
* | ||
* @author Javier Eguiluz <[email protected]> | ||
* | ||
* @internal | ||
*/ | ||
interface ConfigPassInterface | ||
{ | ||
/** | ||
* @param array $backendConfig | ||
* | ||
* @return array | ||
*/ | ||
public function process(array $backendConfig); | ||
} |
Oops, something went wrong.