]> git.immae.eu Git - github/wallabag/wallabag.git/blob - vendor/symfony/form/Symfony/Component/Form/FormRenderer.php
gitignore vendor
[github/wallabag/wallabag.git] / vendor / symfony / form / Symfony / Component / Form / FormRenderer.php
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Form;
13
14 use Symfony\Component\Form\Exception\LogicException;
15 use Symfony\Component\Form\Exception\BadMethodCallException;
16 use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
17
18 /**
19 * Renders a form into HTML using a rendering engine.
20 *
21 * @author Bernhard Schussek <bschussek@gmail.com>
22 */
23 class FormRenderer implements FormRendererInterface
24 {
25 const CACHE_KEY_VAR = 'unique_block_prefix';
26
27 /**
28 * @var FormRendererEngineInterface
29 */
30 private $engine;
31
32 /**
33 * @var CsrfProviderInterface
34 */
35 private $csrfProvider;
36
37 /**
38 * @var array
39 */
40 private $blockNameHierarchyMap = array();
41
42 /**
43 * @var array
44 */
45 private $hierarchyLevelMap = array();
46
47 /**
48 * @var array
49 */
50 private $variableStack = array();
51
52 public function __construct(FormRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null)
53 {
54 $this->engine = $engine;
55 $this->csrfProvider = $csrfProvider;
56 }
57
58 /**
59 * {@inheritdoc}
60 */
61 public function getEngine()
62 {
63 return $this->engine;
64 }
65
66 /**
67 * {@inheritdoc}
68 */
69 public function setTheme(FormView $view, $themes)
70 {
71 $this->engine->setTheme($view, $themes);
72 }
73
74 /**
75 * {@inheritdoc}
76 */
77 public function renderCsrfToken($intention)
78 {
79 if (null === $this->csrfProvider) {
80 throw new BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.');
81 }
82
83 return $this->csrfProvider->generateCsrfToken($intention);
84 }
85
86 /**
87 * {@inheritdoc}
88 */
89 public function renderBlock(FormView $view, $blockName, array $variables = array())
90 {
91 $resource = $this->engine->getResourceForBlockName($view, $blockName);
92
93 if (!$resource) {
94 throw new LogicException(sprintf('No block "%s" found while rendering the form.', $blockName));
95 }
96
97 $viewCacheKey = $view->vars[self::CACHE_KEY_VAR];
98
99 // The variables are cached globally for a view (instead of for the
100 // current suffix)
101 if (!isset($this->variableStack[$viewCacheKey])) {
102 $this->variableStack[$viewCacheKey] = array();
103
104 // The default variable scope contains all view variables, merged with
105 // the variables passed explicitly to the helper
106 $scopeVariables = $view->vars;
107
108 $varInit = true;
109 } else {
110 // Reuse the current scope and merge it with the explicitly passed variables
111 $scopeVariables = end($this->variableStack[$viewCacheKey]);
112
113 $varInit = false;
114 }
115
116 // Merge the passed with the existing attributes
117 if (isset($variables['attr']) && isset($scopeVariables['attr'])) {
118 $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']);
119 }
120
121 // Merge the passed with the exist *label* attributes
122 if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) {
123 $variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']);
124 }
125
126 // Do not use array_replace_recursive(), otherwise array variables
127 // cannot be overwritten
128 $variables = array_replace($scopeVariables, $variables);
129
130 $this->variableStack[$viewCacheKey][] = $variables;
131
132 // Do the rendering
133 $html = $this->engine->renderBlock($view, $resource, $blockName, $variables);
134
135 // Clear the stack
136 array_pop($this->variableStack[$viewCacheKey]);
137
138 if ($varInit) {
139 unset($this->variableStack[$viewCacheKey]);
140 }
141
142 return $html;
143 }
144
145 /**
146 * {@inheritdoc}
147 */
148 public function searchAndRenderBlock(FormView $view, $blockNameSuffix, array $variables = array())
149 {
150 $renderOnlyOnce = 'row' === $blockNameSuffix || 'widget' === $blockNameSuffix;
151
152 if ($renderOnlyOnce && $view->isRendered()) {
153 return '';
154 }
155
156 // The cache key for storing the variables and types
157 $viewCacheKey = $view->vars[self::CACHE_KEY_VAR];
158 $viewAndSuffixCacheKey = $viewCacheKey.$blockNameSuffix;
159
160 // In templates, we have to deal with two kinds of block hierarchies:
161 //
162 // +---------+ +---------+
163 // | Theme B | -------> | Theme A |
164 // +---------+ +---------+
165 //
166 // form_widget -------> form_widget
167 // ^
168 // |
169 // choice_widget -----> choice_widget
170 //
171 // The first kind of hierarchy is the theme hierarchy. This allows to
172 // override the block "choice_widget" from Theme A in the extending
173 // Theme B. This kind of inheritance needs to be supported by the
174 // template engine and, for example, offers "parent()" or similar
175 // functions to fall back from the custom to the parent implementation.
176 //
177 // The second kind of hierarchy is the form type hierarchy. This allows
178 // to implement a custom "choice_widget" block (no matter in which theme),
179 // or to fallback to the block of the parent type, which would be
180 // "form_widget" in this example (again, no matter in which theme).
181 // If the designer wants to explicitly fallback to "form_widget" in his
182 // custom "choice_widget", for example because he only wants to wrap
183 // a <div> around the original implementation, he can simply call the
184 // widget() function again to render the block for the parent type.
185 //
186 // The second kind is implemented in the following blocks.
187 if (!isset($this->blockNameHierarchyMap[$viewAndSuffixCacheKey])) {
188 // INITIAL CALL
189 // Calculate the hierarchy of template blocks and start on
190 // the bottom level of the hierarchy (= "_<id>_<section>" block)
191 $blockNameHierarchy = array();
192 foreach ($view->vars['block_prefixes'] as $blockNamePrefix) {
193 $blockNameHierarchy[] = $blockNamePrefix.'_'.$blockNameSuffix;
194 }
195 $hierarchyLevel = count($blockNameHierarchy) - 1;
196
197 $hierarchyInit = true;
198 } else {
199 // RECURSIVE CALL
200 // If a block recursively calls searchAndRenderBlock() again, resume rendering
201 // using the parent type in the hierarchy.
202 $blockNameHierarchy = $this->blockNameHierarchyMap[$viewAndSuffixCacheKey];
203 $hierarchyLevel = $this->hierarchyLevelMap[$viewAndSuffixCacheKey] - 1;
204
205 $hierarchyInit = false;
206 }
207
208 // The variables are cached globally for a view (instead of for the
209 // current suffix)
210 if (!isset($this->variableStack[$viewCacheKey])) {
211 $this->variableStack[$viewCacheKey] = array();
212
213 // The default variable scope contains all view variables, merged with
214 // the variables passed explicitly to the helper
215 $scopeVariables = $view->vars;
216
217 $varInit = true;
218 } else {
219 // Reuse the current scope and merge it with the explicitly passed variables
220 $scopeVariables = end($this->variableStack[$viewCacheKey]);
221
222 $varInit = false;
223 }
224
225 // Load the resource where this block can be found
226 $resource = $this->engine->getResourceForBlockNameHierarchy($view, $blockNameHierarchy, $hierarchyLevel);
227
228 // Update the current hierarchy level to the one at which the resource was
229 // found. For example, if looking for "choice_widget", but only a resource
230 // is found for its parent "form_widget", then the level is updated here
231 // to the parent level.
232 $hierarchyLevel = $this->engine->getResourceHierarchyLevel($view, $blockNameHierarchy, $hierarchyLevel);
233
234 // The actually existing block name in $resource
235 $blockName = $blockNameHierarchy[$hierarchyLevel];
236
237 // Escape if no resource exists for this block
238 if (!$resource) {
239 throw new LogicException(sprintf(
240 'Unable to render the form as none of the following blocks exist: "%s".',
241 implode('", "', array_reverse($blockNameHierarchy))
242 ));
243 }
244
245 // Merge the passed with the existing attributes
246 if (isset($variables['attr']) && isset($scopeVariables['attr'])) {
247 $variables['attr'] = array_replace($scopeVariables['attr'], $variables['attr']);
248 }
249
250 // Merge the passed with the exist *label* attributes
251 if (isset($variables['label_attr']) && isset($scopeVariables['label_attr'])) {
252 $variables['label_attr'] = array_replace($scopeVariables['label_attr'], $variables['label_attr']);
253 }
254
255 // Do not use array_replace_recursive(), otherwise array variables
256 // cannot be overwritten
257 $variables = array_replace($scopeVariables, $variables);
258
259 // In order to make recursive calls possible, we need to store the block hierarchy,
260 // the current level of the hierarchy and the variables so that this method can
261 // resume rendering one level higher of the hierarchy when it is called recursively.
262 //
263 // We need to store these values in maps (associative arrays) because within a
264 // call to widget() another call to widget() can be made, but for a different view
265 // object. These nested calls should not override each other.
266 $this->blockNameHierarchyMap[$viewAndSuffixCacheKey] = $blockNameHierarchy;
267 $this->hierarchyLevelMap[$viewAndSuffixCacheKey] = $hierarchyLevel;
268
269 // We also need to store the variables for the view so that we can render other
270 // blocks for the same view using the same variables as in the outer block.
271 $this->variableStack[$viewCacheKey][] = $variables;
272
273 // Do the rendering
274 $html = $this->engine->renderBlock($view, $resource, $blockName, $variables);
275
276 // Clear the stack
277 array_pop($this->variableStack[$viewCacheKey]);
278
279 // Clear the caches if they were filled for the first time within
280 // this function call
281 if ($hierarchyInit) {
282 unset($this->blockNameHierarchyMap[$viewAndSuffixCacheKey]);
283 unset($this->hierarchyLevelMap[$viewAndSuffixCacheKey]);
284 }
285
286 if ($varInit) {
287 unset($this->variableStack[$viewCacheKey]);
288 }
289
290 if ($renderOnlyOnce) {
291 $view->setRendered();
292 }
293
294 return $html;
295 }
296
297 /**
298 * {@inheritdoc}
299 */
300 public function humanize($text)
301 {
302 return ucfirst(trim(strtolower(preg_replace(array('/([A-Z])/', '/[_\s]+/'), array('_$1', ' '), $text))));
303 }
304 }