82854de303bca503a66e4e68da14ea2da6eb6cba
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Mvc / Controller / ActionController.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Mvc\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Messaging\FlashMessage;
18 use TYPO3\CMS\Core\Page\PageRenderer;
19 use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
20 use TYPO3\CMS\Extbase\Mvc\Exception\StopActionException;
21 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
22 use TYPO3\CMS\Extbase\Mvc\Web\Request as WebRequest;
23 use TYPO3\CMS\Extbase\Validation\Validator\AbstractCompositeValidator;
24 use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator;
25 use TYPO3Fluid\Fluid\View\TemplateView;
26
27 /**
28 * A multi action controller. This is by far the most common base class for Controllers.
29 *
30 * @api
31 */
32 class ActionController extends AbstractController
33 {
34 /**
35 * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService
36 */
37 protected $reflectionService;
38
39 /**
40 * @var \TYPO3\CMS\Extbase\Service\CacheService
41 */
42 protected $cacheService;
43
44 /**
45 * The current view, as resolved by resolveView()
46 *
47 * @var ViewInterface
48 * @api
49 */
50 protected $view = null;
51
52 /**
53 * @var string
54 * @api
55 */
56 protected $namespacesViewObjectNamePattern = '@vendor\@extension\View\@controller\@action@format';
57
58 /**
59 * A list of formats and object names of the views which should render them.
60 *
61 * Example:
62 *
63 * array('html' => 'Tx_MyExtension_View_MyHtmlView', 'json' => 'F3...
64 *
65 * @var array
66 */
67 protected $viewFormatToObjectNameMap = [];
68
69 /**
70 * The default view object to use if none of the resolved views can render
71 * a response for the current request.
72 *
73 * @var string
74 * @api
75 */
76 protected $defaultViewObjectName = \TYPO3\CMS\Fluid\View\TemplateView::class;
77
78 /**
79 * Name of the action method
80 *
81 * @var string
82 * @api
83 */
84 protected $actionMethodName = 'indexAction';
85
86 /**
87 * Name of the special error action method which is called in case of errors
88 *
89 * @var string
90 * @api
91 */
92 protected $errorMethodName = 'errorAction';
93
94 /**
95 * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
96 * @api
97 */
98 protected $mvcPropertyMappingConfigurationService;
99
100 /**
101 * The current request.
102 *
103 * @var \TYPO3\CMS\Extbase\Mvc\Request
104 * @api
105 */
106 protected $request;
107
108 /**
109 * The response which will be returned by this action controller
110 *
111 * @var \TYPO3\CMS\Extbase\Mvc\Response
112 * @api
113 */
114 protected $response;
115
116 /**
117 * @param \TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService
118 */
119 public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
120 {
121 $this->reflectionService = $reflectionService;
122 }
123
124 /**
125 * @param \TYPO3\CMS\Extbase\Service\CacheService $cacheService
126 */
127 public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
128 {
129 $this->cacheService = $cacheService;
130 }
131
132 /**
133 * @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService
134 */
135 public function injectMvcPropertyMappingConfigurationService(\TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMappingConfigurationService)
136 {
137 $this->mvcPropertyMappingConfigurationService = $mvcPropertyMappingConfigurationService;
138 }
139
140 /**
141 * Handles a request. The result output is returned by altering the given response.
142 *
143 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request The request object
144 * @param \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response The response, modified by this handler
145 *
146 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException
147 */
148 public function processRequest(\TYPO3\CMS\Extbase\Mvc\RequestInterface $request, \TYPO3\CMS\Extbase\Mvc\ResponseInterface $response)
149 {
150 if (!$this->canProcessRequest($request)) {
151 throw new \TYPO3\CMS\Extbase\Mvc\Exception\UnsupportedRequestTypeException(static::class . ' does not support requests of type "' . get_class($request) . '". Supported types are: ' . implode(' ', $this->supportedRequestTypes), 1187701131);
152 }
153
154 if ($response instanceof \TYPO3\CMS\Extbase\Mvc\Web\Response && $request instanceof WebRequest) {
155 $response->setRequest($request);
156 }
157 $this->request = $request;
158 $this->request->setDispatched(true);
159 $this->response = $response;
160 $this->uriBuilder = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder::class);
161 $this->uriBuilder->setRequest($request);
162 $this->actionMethodName = $this->resolveActionMethodName();
163 $this->initializeActionMethodArguments();
164 $this->initializeActionMethodValidators();
165 $this->mvcPropertyMappingConfigurationService->initializePropertyMappingConfigurationFromRequest($request, $this->arguments);
166 $this->initializeAction();
167 $actionInitializationMethodName = 'initialize' . ucfirst($this->actionMethodName);
168 if (method_exists($this, $actionInitializationMethodName)) {
169 call_user_func([$this, $actionInitializationMethodName]);
170 }
171 $this->mapRequestArgumentsToControllerArguments();
172 $this->controllerContext = $this->buildControllerContext();
173 $this->view = $this->resolveView();
174 if ($this->view !== null) {
175 $this->initializeView($this->view);
176 }
177 $this->callActionMethod();
178 $this->renderAssetsForRequest($request);
179 }
180
181 /**
182 * Method which initializes assets that should be attached to the response
183 * for the given $request, which contains parameters that an override can
184 * use to determine which assets to add via PageRenderer.
185 *
186 * This default implementation will attempt to render the sections "HeaderAssets"
187 * and "FooterAssets" from the template that is being rendered, inserting the
188 * rendered content into either page header or footer, as appropriate. Both
189 * sections are optional and can be used one or both in combination.
190 *
191 * You can add assets with this method without worrying about duplicates, if
192 * for example you do this in a plugin that gets used multiple time on a page.
193 *
194 * @param \TYPO3\CMS\Extbase\Mvc\RequestInterface $request
195 */
196 protected function renderAssetsForRequest($request)
197 {
198 if (!$this->view instanceof TemplateView) {
199 // Only TemplateView (from Fluid engine, so this includes all TYPO3 Views based
200 // on TYPO3's AbstractTemplateView) supports renderSection(). The method is not
201 // declared on ViewInterface - so we must assert a specific class. We silently skip
202 // asset processing if the View doesn't match, so we don't risk breaking custom Views.
203 return;
204 }
205 $pageRenderer = $this->objectManager->get(PageRenderer::class);
206 $variables = ['request' => $request, 'arguments' => $this->arguments];
207 $headerAssets = $this->view->renderSection('HeaderAssets', $variables, true);
208 $footerAssets = $this->view->renderSection('FooterAssets', $variables, true);
209 if (!empty(trim($headerAssets))) {
210 $pageRenderer->addHeaderData($headerAssets);
211 }
212 if (!empty(trim($footerAssets))) {
213 $pageRenderer->addFooterData($footerAssets);
214 }
215 }
216
217 /**
218 * Implementation of the arguments initilization in the action controller:
219 * Automatically registers arguments of the current action
220 *
221 * Don't override this method - use initializeAction() instead.
222 *
223 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException
224 * @see initializeArguments()
225 */
226 protected function initializeActionMethodArguments()
227 {
228 $methodParameters = $this->reflectionService->getMethodParameters(static::class, $this->actionMethodName);
229 foreach ($methodParameters as $parameterName => $parameterInfo) {
230 $dataType = null;
231 if (isset($parameterInfo['type'])) {
232 $dataType = $parameterInfo['type'];
233 } elseif ($parameterInfo['array']) {
234 $dataType = 'array';
235 }
236 if ($dataType === null) {
237 throw new \TYPO3\CMS\Extbase\Mvc\Exception\InvalidArgumentTypeException('The argument type for parameter $' . $parameterName . ' of method ' . static::class . '->' . $this->actionMethodName . '() could not be detected.', 1253175643);
238 }
239 $defaultValue = $parameterInfo['hasDefaultValue'] === true ? $parameterInfo['defaultValue'] : null;
240 $this->arguments->addNewArgument($parameterName, $dataType, $parameterInfo['optional'] === false, $defaultValue);
241 }
242 }
243
244 /**
245 * Adds the needed validators to the Arguments:
246 *
247 * - Validators checking the data type from the @param annotation
248 * - Custom validators specified with validate annotations.
249 * - Model-based validators (validate annotations in the model)
250 * - Custom model validator classes
251 */
252 protected function initializeActionMethodValidators()
253 {
254 $methodParameters = $this->reflectionService->getMethodParameters(static::class, $this->actionMethodName);
255
256 /** @var ConjunctionValidator[] $validatorConjunctions */
257 $validatorConjunctions = [];
258 foreach ($methodParameters as $parameterName => $methodParameter) {
259 /** @var ConjunctionValidator $validatorConjunction */
260 $validatorConjunction = $this->objectManager->get(ConjunctionValidator::class);
261
262 // @todo: remove check for old underscore model name syntax once it's possible
263 if (strpbrk($methodParameter['type'], '_\\') === false) {
264 // this checks if the type is a simply type and then adds a
265 // validator. StringValidator and such for example.
266 $typeValidator = $this->validatorResolver->createValidator($methodParameter['type']);
267
268 if ($typeValidator !== null) {
269 $validatorConjunction->addValidator($typeValidator);
270 }
271 }
272
273 $validatorConjunctions[$parameterName] = $validatorConjunction;
274
275 foreach ($methodParameter['validators'] as $validator) {
276 $validatorConjunctions[$parameterName]->addValidator(
277 $this->objectManager->get($validator['className'], $validator['options'])
278 );
279 }
280 }
281
282 /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
283 foreach ($this->arguments as $argument) {
284 $validator = $validatorConjunctions[$argument->getName()];
285
286 $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType());
287 if (!empty($baseValidatorConjunction) && $validator instanceof AbstractCompositeValidator) {
288 $validator->addValidator($baseValidatorConjunction);
289 }
290 $argument->setValidator($validator);
291 }
292 }
293
294 /**
295 * Resolves and checks the current action method name
296 *
297 * @return string Method name of the current action
298 * @throws \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException if the action specified in the request object does not exist (and if there's no default action either).
299 */
300 protected function resolveActionMethodName()
301 {
302 $actionMethodName = $this->request->getControllerActionName() . 'Action';
303 if (!method_exists($this, $actionMethodName)) {
304 throw new \TYPO3\CMS\Extbase\Mvc\Exception\NoSuchActionException('An action "' . $actionMethodName . '" does not exist in controller "' . static::class . '".', 1186669086);
305 }
306 return $actionMethodName;
307 }
308
309 /**
310 * Calls the specified action method and passes the arguments.
311 *
312 * If the action returns a string, it is appended to the content in the
313 * response object. If the action doesn't return anything and a valid
314 * view exists, the view is rendered automatically.
315 *
316 * @api
317 */
318 protected function callActionMethod()
319 {
320 $preparedArguments = [];
321 /** @var \TYPO3\CMS\Extbase\Mvc\Controller\Argument $argument */
322 foreach ($this->arguments as $argument) {
323 $preparedArguments[] = $argument->getValue();
324 }
325 $validationResult = $this->arguments->getValidationResults();
326 if (!$validationResult->hasErrors()) {
327 $this->emitBeforeCallActionMethodSignal($preparedArguments);
328 $actionResult = call_user_func_array([$this, $this->actionMethodName], $preparedArguments);
329 } else {
330 $methodTagsValues = $this->reflectionService->getMethodTagsValues(static::class, $this->actionMethodName);
331 $ignoreValidationAnnotations = $methodTagsValues['ignorevalidation'] ?? [];
332
333 // if there exist errors which are not ignored with @TYPO3\CMS\Extbase\Annotation\IgnoreValidation => call error method
334 // else => call action method
335 $shouldCallActionMethod = true;
336 foreach ($validationResult->getSubResults() as $argumentName => $subValidationResult) {
337 if (!$subValidationResult->hasErrors()) {
338 continue;
339 }
340 if (in_array($argumentName, $ignoreValidationAnnotations, true)) {
341 continue;
342 }
343 $shouldCallActionMethod = false;
344 break;
345 }
346 if ($shouldCallActionMethod) {
347 $this->emitBeforeCallActionMethodSignal($preparedArguments);
348 $actionResult = call_user_func_array([$this, $this->actionMethodName], $preparedArguments);
349 } else {
350 $actionResult = call_user_func([$this, $this->errorMethodName]);
351 }
352 }
353
354 if ($actionResult === null && $this->view instanceof ViewInterface) {
355 $this->response->appendContent($this->view->render());
356 } elseif (is_string($actionResult) && $actionResult !== '') {
357 $this->response->appendContent($actionResult);
358 } elseif (is_object($actionResult) && method_exists($actionResult, '__toString')) {
359 $this->response->appendContent((string)$actionResult);
360 }
361 }
362
363 /**
364 * Emits a signal before the current action is called
365 *
366 * @param array $preparedArguments
367 */
368 protected function emitBeforeCallActionMethodSignal(array $preparedArguments)
369 {
370 $this->signalSlotDispatcher->dispatch(__CLASS__, 'beforeCallActionMethod', [static::class, $this->actionMethodName, $preparedArguments]);
371 }
372
373 /**
374 * Prepares a view for the current action.
375 * By default, this method tries to locate a view with a name matching the current action.
376 *
377 * @return ViewInterface
378 * @api
379 */
380 protected function resolveView()
381 {
382 $viewObjectName = $this->resolveViewObjectName();
383 if ($viewObjectName !== false) {
384 /** @var $view ViewInterface */
385 $view = $this->objectManager->get($viewObjectName);
386 $this->setViewConfiguration($view);
387 if ($view->canRender($this->controllerContext) === false) {
388 unset($view);
389 }
390 }
391 if (!isset($view) && $this->defaultViewObjectName != '') {
392 /** @var $view ViewInterface */
393 $view = $this->objectManager->get($this->defaultViewObjectName);
394 $this->setViewConfiguration($view);
395 if ($view->canRender($this->controllerContext) === false) {
396 unset($view);
397 }
398 }
399 if (!isset($view)) {
400 $view = $this->objectManager->get(\TYPO3\CMS\Extbase\Mvc\View\NotFoundView::class);
401 $view->assign('errorMessage', 'No template was found. View could not be resolved for action "'
402 . $this->request->getControllerActionName() . '" in class "' . $this->request->getControllerObjectName() . '"');
403 }
404 $view->setControllerContext($this->controllerContext);
405 if (method_exists($view, 'injectSettings')) {
406 $view->injectSettings($this->settings);
407 }
408 $view->initializeView();
409 // In TYPO3.Flow, solved through Object Lifecycle methods, we need to call it explicitly
410 $view->assign('settings', $this->settings);
411 // same with settings injection.
412 return $view;
413 }
414
415 /**
416 * @param ViewInterface $view
417 */
418 protected function setViewConfiguration(ViewInterface $view)
419 {
420 // Template Path Override
421 $extbaseFrameworkConfiguration = $this->configurationManager->getConfiguration(
422 ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK
423 );
424
425 // set TemplateRootPaths
426 $viewFunctionName = 'setTemplateRootPaths';
427 if (method_exists($view, $viewFunctionName)) {
428 $setting = 'templateRootPaths';
429 $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
430 // no need to bother if there is nothing to set
431 if ($parameter) {
432 $view->$viewFunctionName($parameter);
433 }
434 }
435
436 // set LayoutRootPaths
437 $viewFunctionName = 'setLayoutRootPaths';
438 if (method_exists($view, $viewFunctionName)) {
439 $setting = 'layoutRootPaths';
440 $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
441 // no need to bother if there is nothing to set
442 if ($parameter) {
443 $view->$viewFunctionName($parameter);
444 }
445 }
446
447 // set PartialRootPaths
448 $viewFunctionName = 'setPartialRootPaths';
449 if (method_exists($view, $viewFunctionName)) {
450 $setting = 'partialRootPaths';
451 $parameter = $this->getViewProperty($extbaseFrameworkConfiguration, $setting);
452 // no need to bother if there is nothing to set
453 if ($parameter) {
454 $view->$viewFunctionName($parameter);
455 }
456 }
457 }
458
459 /**
460 * Handles the path resolving for *rootPath(s)
461 *
462 * numerical arrays get ordered by key ascending
463 *
464 * @param array $extbaseFrameworkConfiguration
465 * @param string $setting parameter name from TypoScript
466 *
467 * @return array
468 */
469 protected function getViewProperty($extbaseFrameworkConfiguration, $setting)
470 {
471 $values = [];
472 if (
473 !empty($extbaseFrameworkConfiguration['view'][$setting])
474 && is_array($extbaseFrameworkConfiguration['view'][$setting])
475 ) {
476 $values = $extbaseFrameworkConfiguration['view'][$setting];
477 }
478
479 return $values;
480 }
481
482 /**
483 * Determines the fully qualified view object name.
484 *
485 * @return mixed The fully qualified view object name or FALSE if no matching view could be found.
486 * @api
487 */
488 protected function resolveViewObjectName()
489 {
490 $vendorName = $this->request->getControllerVendorName();
491 if ($vendorName === null) {
492 return false;
493 }
494
495 $possibleViewName = str_replace(
496 [
497 '@vendor',
498 '@extension',
499 '@controller',
500 '@action'
501 ],
502 [
503 $vendorName,
504 $this->request->getControllerExtensionName(),
505 $this->request->getControllerName(),
506 ucfirst($this->request->getControllerActionName())
507 ],
508 $this->namespacesViewObjectNamePattern
509 );
510 $format = $this->request->getFormat();
511 $viewObjectName = str_replace('@format', ucfirst($format), $possibleViewName);
512 if (class_exists($viewObjectName) === false) {
513 $viewObjectName = str_replace('@format', '', $possibleViewName);
514 }
515 if (isset($this->viewFormatToObjectNameMap[$format]) && class_exists($viewObjectName) === false) {
516 $viewObjectName = $this->viewFormatToObjectNameMap[$format];
517 }
518 return class_exists($viewObjectName) ? $viewObjectName : false;
519 }
520
521 /**
522 * Initializes the view before invoking an action method.
523 *
524 * Override this method to solve assign variables common for all actions
525 * or prepare the view in another way before the action is called.
526 *
527 * @param ViewInterface $view The view to be initialized
528 *
529 * @api
530 */
531 protected function initializeView(ViewInterface $view)
532 {
533 }
534
535 /**
536 * Initializes the controller before invoking an action method.
537 *
538 * Override this method to solve tasks which all actions have in
539 * common.
540 *
541 * @api
542 */
543 protected function initializeAction()
544 {
545 }
546
547 /**
548 * A special action which is called if the originally intended action could
549 * not be called, for example if the arguments were not valid.
550 *
551 * The default implementation sets a flash message, request errors and forwards back
552 * to the originating action. This is suitable for most actions dealing with form input.
553 *
554 * We clear the page cache by default on an error as well, as we need to make sure the
555 * data is re-evaluated when the user changes something.
556 *
557 * @return string
558 * @api
559 */
560 protected function errorAction()
561 {
562 $this->clearCacheOnError();
563 $this->addErrorFlashMessage();
564 $this->forwardToReferringRequest();
565
566 return $this->getFlattenedValidationErrorMessage();
567 }
568
569 /**
570 * Clear cache of current page on error. Needed because we want a re-evaluation of the data.
571 * Better would be just do delete the cache for the error action, but that is not possible right now.
572 */
573 protected function clearCacheOnError()
574 {
575 $extbaseSettings = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
576 if (isset($extbaseSettings['persistence']['enableAutomaticCacheClearing']) && $extbaseSettings['persistence']['enableAutomaticCacheClearing'] === '1') {
577 if (isset($GLOBALS['TSFE'])) {
578 $pageUid = $GLOBALS['TSFE']->id;
579 $this->cacheService->clearPageCache([$pageUid]);
580 }
581 }
582 }
583
584 /**
585 * If an error occurred during this request, this adds a flash message describing the error to the flash
586 * message container.
587 */
588 protected function addErrorFlashMessage()
589 {
590 $errorFlashMessage = $this->getErrorFlashMessage();
591 if ($errorFlashMessage !== false) {
592 $this->addFlashMessage($errorFlashMessage, '', FlashMessage::ERROR);
593 }
594 }
595
596 /**
597 * A template method for displaying custom error flash messages, or to
598 * display no flash message at all on errors. Override this to customize
599 * the flash message in your action controller.
600 *
601 * @return string The flash message or FALSE if no flash message should be set
602 * @api
603 */
604 protected function getErrorFlashMessage()
605 {
606 return 'An error occurred while trying to call ' . static::class . '->' . $this->actionMethodName . '()';
607 }
608
609 /**
610 * If information on the request before the current request was sent, this method forwards back
611 * to the originating request. This effectively ends processing of the current request, so do not
612 * call this method before you have finished the necessary business logic!
613 *
614 * @throws StopActionException
615 */
616 protected function forwardToReferringRequest()
617 {
618 $referringRequest = $this->request->getReferringRequest();
619 if ($referringRequest !== null) {
620 $originalRequest = clone $this->request;
621 $this->request->setOriginalRequest($originalRequest);
622 $this->request->setOriginalRequestMappingResults($this->arguments->getValidationResults());
623 $this->forward(
624 $referringRequest->getControllerActionName(),
625 $referringRequest->getControllerName(),
626 $referringRequest->getControllerExtensionName(),
627 $referringRequest->getArguments()
628 );
629 }
630 }
631
632 /**
633 * Returns a string with a basic error message about validation failure.
634 * We may add all validation error messages to a log file in the future,
635 * but for security reasons (@see #54074) we do not return these here.
636 *
637 * @return string
638 */
639 protected function getFlattenedValidationErrorMessage()
640 {
641 $outputMessage = 'Validation failed while trying to call ' . static::class . '->' . $this->actionMethodName . '().' . PHP_EOL;
642 return $outputMessage;
643 }
644
645 /**
646 * Returns a map of action method names and their parameters.
647 *
648 * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
649 *
650 * @return array Array of method parameters by action name
651 * @deprecated
652 */
653 public static function getActionMethodParameters($objectManager)
654 {
655 trigger_error(
656 'Method ' . __METHOD__ . ' is deprecated and will be removed in TYPO3 v10.0.',
657 E_USER_DEPRECATED
658 );
659
660 $reflectionService = $objectManager->get(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
661
662 $result = [];
663
664 $className = get_called_class();
665 $methodNames = get_class_methods($className);
666 foreach ($methodNames as $methodName) {
667 if (strlen($methodName) > 6 && strpos($methodName, 'Action', strlen($methodName) - 6) !== false) {
668 $result[$methodName] = $reflectionService->getMethodParameters($className, $methodName);
669 }
670 }
671
672 return $result;
673 }
674 }