diff --git a/typo3/sysext/core/Documentation/Changelog/13.2/Feature-102951-ProvidePSR-7RequestInExtbaseValidators.rst b/typo3/sysext/core/Documentation/Changelog/13.2/Feature-102951-ProvidePSR-7RequestInExtbaseValidators.rst new file mode 100644 index 0000000000000000000000000000000000000000..3471e090cd8cb13533ab2edba5a6814b1243f557 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/13.2/Feature-102951-ProvidePSR-7RequestInExtbaseValidators.rst @@ -0,0 +1,27 @@ +.. include:: /Includes.rst.txt + +.. _feature-102951-1709643048: + +============================================================== +Feature: #102951 - Provide PSR-7 request in extbase validators +============================================================== + +See :issue:`102951` + +Description +=========== + +Extbase :php:`abstractValidator` does now provide a getter and a setter +for the PSR-7 Request object. Validators extending :php:`AbstractValidator` +will include the PSR-7 request object, if the validator has been instantiated +by extbase :php:`ValidationResolver`. + + +Impact +====== + +Extension developers can now create custom validators which consume data +available from the PSR-7 request object (e.g. request attribute +:php:`frontend.user`). + +.. index:: PHP-API, ext:extbase diff --git a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php index 10d3485df21ec0d4cf702d4cfcc8f567ac93fed4..9390162d847d0f94e8bca43c98d8660c5608d1a4 100644 --- a/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php +++ b/typo3/sysext/extbase/Classes/Mvc/Controller/ActionController.php @@ -293,15 +293,22 @@ abstract class ActionController implements ControllerInterface continue; } /** @var ConjunctionValidator $validator */ - $validator = $this->validatorResolver->createValidator(ConjunctionValidator::class, []); + $validator = $this->validatorResolver->createValidator(ConjunctionValidator::class); foreach ($classSchemaMethodParameter->getValidators() as $validatorDefinition) { /** @var ValidatorInterface $validatorInstance */ - $validatorInstance = $this->validatorResolver->createValidator($validatorDefinition['className'], $validatorDefinition['options']); + $validatorInstance = $this->validatorResolver->createValidator( + $validatorDefinition['className'], + $validatorDefinition['options'], + $this->request + ); $validator->addValidator( $validatorInstance ); } - $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType()); + $baseValidatorConjunction = $this->validatorResolver->getBaseValidatorConjunction( + $argument->getDataType(), + $this->request + ); if ($baseValidatorConjunction->count() > 0) { $validator->addValidator($baseValidatorConjunction); } @@ -319,7 +326,10 @@ abstract class ActionController implements ControllerInterface { /** @var Argument $argument */ foreach ($this->arguments as $argument) { - $validator = $this->validatorResolver->getBaseValidatorConjunction($argument->getDataType()); + $validator = $this->validatorResolver->getBaseValidatorConjunction( + $argument->getDataType(), + $this->request + ); if ($validator !== null) { $argument->setValidator($validator); } diff --git a/typo3/sysext/extbase/Classes/Validation/Validator/AbstractValidator.php b/typo3/sysext/extbase/Classes/Validation/Validator/AbstractValidator.php index 6cee038749e949e68b0a07c5b78c176b8d25fbe3..e3322fcd6d3768996a76de0119dc4df28d5b6214 100644 --- a/typo3/sysext/extbase/Classes/Validation/Validator/AbstractValidator.php +++ b/typo3/sysext/extbase/Classes/Validation/Validator/AbstractValidator.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Validation\Validator; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Extbase\Error\Result; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3\CMS\Extbase\Validation\Error; @@ -52,6 +53,7 @@ abstract class AbstractValidator implements ValidatorInterface protected array $options = []; protected Result $result; + protected ?ServerRequestInterface $request = null; public function setOptions(array $options): void { @@ -59,6 +61,14 @@ abstract class AbstractValidator implements ValidatorInterface $this->initializeTranslationOptions($options); } + /** + * @todo: Add to ValidatorInterface in TYPO3 v14 + */ + public function setRequest(?ServerRequestInterface $request): void + { + $this->request = $request; + } + /** * Checks if the given value is valid according to the validator, and returns * the error messages object which occurred. @@ -116,6 +126,14 @@ abstract class AbstractValidator implements ValidatorInterface return $this->options; } + /** + * @todo: Add to ValidatorInterface in TYPO3 v14 + */ + public function getRequest(): ?ServerRequestInterface + { + return $this->request; + } + /** * TRUE if the given $value is NULL or an empty string ('') */ diff --git a/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php b/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php index 820085de02550c82b781c814bc7726a8f750a077..589cc9808c145ad5cc37d0b213d4a8da5b3ff397 100644 --- a/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php +++ b/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php @@ -17,6 +17,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Validation; +use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\PropertyInfo\Type; use TYPO3\CMS\Core\Log\LogManager; use TYPO3\CMS\Core\SingletonInterface; @@ -24,6 +25,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Reflection\ReflectionService; use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility; use TYPO3\CMS\Extbase\Validation\Exception\NoSuchValidatorException; +use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator; use TYPO3\CMS\Extbase\Validation\Validator\CollectionValidator; use TYPO3\CMS\Extbase\Validation\Validator\ConjunctionValidator; use TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator; @@ -46,14 +48,26 @@ class ValidatorResolver implements SingletonInterface * * @param string $validatorType Either one of the built-in data types or fully qualified validator class name * @param array $validatorOptions Options to be passed to the validator + * @param ServerRequestInterface $request PSR-7 request object */ - public function createValidator(string $validatorType, array $validatorOptions = []): ?ValidatorInterface - { + public function createValidator( + string $validatorType, + array $validatorOptions = [], + ?ServerRequestInterface $request = null + ): ?ValidatorInterface { try { $validatorObjectName = ValidatorClassNameResolver::resolve($validatorType); /** @var ValidatorInterface $validator */ $validator = GeneralUtility::makeInstance($validatorObjectName); $validator->setOptions($validatorOptions); + + /** + * @todo Remove condition in TYPO3 v14 when ValidatorInterface is changed + */ + if ($validator instanceof AbstractValidator) { + $validator->setRequest($request); + } + return $validator; } catch (NoSuchValidatorException $e) { GeneralUtility::makeInstance(LogManager::class)->getLogger(__CLASS__)->debug($e->getMessage()); @@ -67,14 +81,16 @@ class ValidatorResolver implements SingletonInterface * * @param string $targetClassName The data type to search a validator for. Usually the fully qualified object name */ - public function getBaseValidatorConjunction(string $targetClassName): ConjunctionValidator - { + public function getBaseValidatorConjunction( + string $targetClassName, + ?ServerRequestInterface $request = null + ): ConjunctionValidator { if (!isset($this->baseValidatorConjunctions[$targetClassName])) { $conjunctionValidator = GeneralUtility::makeInstance(ConjunctionValidator::class); $this->baseValidatorConjunctions[$targetClassName] = $conjunctionValidator; // The simpleType check reduces lookups to the class loader if (!TypeHandlingUtility::isSimpleType($targetClassName) && class_exists($targetClassName)) { - $this->buildBaseValidatorConjunction($conjunctionValidator, $targetClassName); + $this->buildBaseValidatorConjunction($conjunctionValidator, $targetClassName, $request); } } return $this->baseValidatorConjunctions[$targetClassName]; @@ -100,8 +116,11 @@ class ValidatorResolver implements SingletonInterface * @throws NoSuchValidatorException * @throws \InvalidArgumentException */ - protected function buildBaseValidatorConjunction(ConjunctionValidator $conjunctionValidator, string $targetClassName): void - { + protected function buildBaseValidatorConjunction( + ConjunctionValidator $conjunctionValidator, + string $targetClassName, + ?ServerRequestInterface $request + ): void { $classSchema = $this->reflectionService->getClassSchema($targetClassName); // Model based validator @@ -132,7 +151,8 @@ class ValidatorResolver implements SingletonInterface CollectionValidator::class, [ 'elementType' => $primaryCollectionValueType->getClassName() ?? $primaryCollectionValueType->getBuiltinType(), - ] + ], + $request ); $objectValidator->addPropertyValidator($property->getName(), $collectionValidator); } elseif (class_exists($propertyTargetClassName) @@ -154,7 +174,7 @@ class ValidatorResolver implements SingletonInterface // relations in models. Still, the question remains why it excludes core types and singletons. // It makes sense on a theoretical level, but I don't see a technical issue allowing these as // well. - $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName); + $validatorForProperty = $this->getBaseValidatorConjunction($propertyTargetClassName, $request); if ($validatorForProperty->count() > 0) { $objectValidator->addPropertyValidator($property->getName(), $validatorForProperty); } @@ -167,7 +187,11 @@ class ValidatorResolver implements SingletonInterface // \TYPO3\CMS\Extbase\Validation\ValidatorResolver::createValidator once again. However, to // keep things simple for now, we still use the method createValidator here. In the future, // createValidator must only accept FQCN's. - $newValidator = $this->createValidator($validatorDefinition['className'], $validatorDefinition['options']); + $newValidator = $this->createValidator( + $validatorDefinition['className'], + $validatorDefinition['options'], + $request + ); if ($newValidator === null) { throw new NoSuchValidatorException( 'Invalid validate annotation in ' . $targetClassName . '::' . $property->getName() . ': ' . diff --git a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php index 95fda7ae4e10fcdb2be224d4d8830a52fcfec69a..a758516f1fa8729b7fb9b8226312c0bae602662e 100644 --- a/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php +++ b/typo3/sysext/extbase/Tests/Functional/Mvc/Controller/ActionControllerTest.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Extbase\Tests\Functional\Mvc\Controller; use PHPUnit\Framework\Attributes\Test; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Core\Page\PageRenderer; use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity; @@ -145,6 +146,7 @@ final class ActionControllerTest extends FunctionalTestCase self::assertInstanceOf(\SplObjectStorage::class, $validators); $validators->rewind(); self::assertInstanceOf(CustomValidator::class, $validators->current()); + self::assertInstanceOf(ServerRequestInterface::class, $validators->current()->getRequest()); } #[Test] @@ -170,6 +172,7 @@ final class ActionControllerTest extends FunctionalTestCase self::assertCount(1, $validators); $validators->rewind(); self::assertInstanceOf(NotEmptyValidator::class, $validators->current()); + self::assertInstanceOf(ServerRequestInterface::class, $validators->current()->getRequest()); } #[Test] diff --git a/typo3/sysext/form/Classes/Domain/Factory/ArrayFormFactory.php b/typo3/sysext/form/Classes/Domain/Factory/ArrayFormFactory.php index 84e9b56bfb1d944f46058d2fc2f0709a8637b9a5..ea1da36fe10301df62750b1c47c0dcabab90b612 100644 --- a/typo3/sysext/form/Classes/Domain/Factory/ArrayFormFactory.php +++ b/typo3/sysext/form/Classes/Domain/Factory/ArrayFormFactory.php @@ -21,6 +21,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Form\Domain\Factory; +use Psr\Http\Message\ServerRequestInterface; use Symfony\Component\DependencyInjection\Attribute\Autoconfigure; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService; @@ -45,8 +46,11 @@ class ArrayFormFactory extends AbstractFormFactory * @throws RenderingException * @internal */ - public function build(array $configuration, string $prototypeName = null): FormDefinition - { + public function build( + array $configuration, + string $prototypeName = null, + ServerRequestInterface $request = null + ): FormDefinition { if (empty($prototypeName)) { $prototypeName = $configuration['prototypeName'] ?? 'standard'; } @@ -68,7 +72,7 @@ class ArrayFormFactory extends AbstractFormFactory ); if (isset($configuration['renderables'])) { foreach ($configuration['renderables'] as $pageConfiguration) { - $this->addNestedRenderable($pageConfiguration, $form); + $this->addNestedRenderable($pageConfiguration, $form, $request); } } @@ -91,8 +95,11 @@ class ArrayFormFactory extends AbstractFormFactory * @throws IdentifierNotValidException * @throws UnknownCompositRenderableException */ - protected function addNestedRenderable(array $nestedRenderableConfiguration, CompositeRenderableInterface $parentRenderable) - { + protected function addNestedRenderable( + array $nestedRenderableConfiguration, + CompositeRenderableInterface $parentRenderable, + ServerRequestInterface $request + ) { if (!isset($nestedRenderableConfiguration['identifier'])) { throw new IdentifierNotValidException('Identifier not set.', 1329289436); } @@ -100,6 +107,9 @@ class ArrayFormFactory extends AbstractFormFactory $renderable = $parentRenderable->createPage($nestedRenderableConfiguration['identifier'], $nestedRenderableConfiguration['type']); } elseif ($parentRenderable instanceof AbstractSection) { $renderable = $parentRenderable->createElement($nestedRenderableConfiguration['identifier'], $nestedRenderableConfiguration['type']); + if (method_exists($renderable, 'setRequest')) { + $renderable->setRequest($request); + } } else { throw new UnknownCompositRenderableException('Unknown composit renderable "' . get_class($parentRenderable) . '"', 1479593622); } @@ -118,7 +128,7 @@ class ArrayFormFactory extends AbstractFormFactory if ($renderable instanceof CompositeRenderableInterface) { foreach ($childRenderables as $elementConfiguration) { - $this->addNestedRenderable($elementConfiguration, $renderable); + $this->addNestedRenderable($elementConfiguration, $renderable, $request); } } diff --git a/typo3/sysext/form/Classes/Domain/Factory/FormFactoryInterface.php b/typo3/sysext/form/Classes/Domain/Factory/FormFactoryInterface.php index 08aca7b329f642b818cfd42dd6ba958b396d5399..570eeb7a55fc57318e539c5f1ae3743a1713a4d7 100644 --- a/typo3/sysext/form/Classes/Domain/Factory/FormFactoryInterface.php +++ b/typo3/sysext/form/Classes/Domain/Factory/FormFactoryInterface.php @@ -21,6 +21,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Form\Domain\Factory; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Form\Domain\Model\FormDefinition; /** @@ -43,7 +44,12 @@ interface FormFactoryInterface * * @param array $configuration factory-specific configuration array * @param string $prototypeName The name of the "PrototypeName" to use; it is factory-specific to implement this. + * @param ServerRequestInterface $request The PSR-7 request object * @return FormDefinition a newly built form definition */ - public function build(array $configuration, string $prototypeName = null): FormDefinition; + public function build( + array $configuration, + string $prototypeName = null, + ServerRequestInterface $request = null + ): FormDefinition; } diff --git a/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php b/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php index a464ecbf3f9117e854e1d6f9de53ef7f605ee7f3..be1e2fa6071c20553fc0d14f90e4da462f040f3a 100644 --- a/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php +++ b/typo3/sysext/form/Classes/Domain/Model/Renderable/AbstractRenderable.php @@ -21,6 +21,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Form\Domain\Model\Renderable; +use Psr\Http\Message\ServerRequestInterface; use TYPO3\CMS\Core\Cache\CacheManager; use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; @@ -124,6 +125,18 @@ abstract class AbstractRenderable implements RenderableInterface, VariableRender $this->identifier = $identifier; } + protected ?ServerRequestInterface $request = null; + + public function getRequest(): ?ServerRequestInterface + { + return $this->request; + } + + public function setRequest(?ServerRequestInterface $request): void + { + $this->request = $request; + } + /** * Set multiple properties of this object at once. * Every property which has a corresponding set* method can be set using @@ -197,7 +210,7 @@ abstract class AbstractRenderable implements RenderableInterface, VariableRender $this->validatorResolver = $container->get(ValidatorResolver::class); } /** @var ValidatorInterface $validator */ - $validator = $this->validatorResolver->createValidator($implementationClassName, $defaultOptions); + $validator = $this->validatorResolver->createValidator($implementationClassName, $defaultOptions, $this->request); $this->addValidator($validator); return $validator; } diff --git a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php index bacc286ba23663f67697b1f05f6f298f18375405..3e8136b8ee4fdd82de5ce45f9ab81d4075aef2c8 100644 --- a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php +++ b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php @@ -356,7 +356,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess $honeypotNameFromSession = $this->getHoneypotNameFromSession($this->lastDisplayedPage); if ($honeypotNameFromSession) { $honeypotElement = $this->lastDisplayedPage->createElement($honeypotNameFromSession, $renderingOptions['honeypot']['formElementToUse']); - $validator = $this->validatorResolver->createValidator(EmptyValidator::class); + $validator = $this->validatorResolver->createValidator(EmptyValidator::class, [], $this->request); $honeypotElement->addValidator($validator); } } @@ -400,7 +400,7 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess $referenceElement = $this->currentPage->getElements()[$randomElementNumber]; $honeypotElement = $this->currentPage->createElement($honeypotName, $renderingOptions['honeypot']['formElementToUse']); - $validator = $this->validatorResolver->createValidator(EmptyValidator::class); + $validator = $this->validatorResolver->createValidator(EmptyValidator::class, [], $this->request); $honeypotElement->addValidator($validator); if (random_int(0, 1) === 1) { diff --git a/typo3/sysext/form/Classes/ViewHelpers/RenderViewHelper.php b/typo3/sysext/form/Classes/ViewHelpers/RenderViewHelper.php index e617fe06203309ad0a1a2df624ee329e9c1e0b7c..a0bc6af13f176e7351ea318d61e1abd2c9cbf270 100644 --- a/typo3/sysext/form/Classes/ViewHelpers/RenderViewHelper.php +++ b/typo3/sysext/form/Classes/ViewHelpers/RenderViewHelper.php @@ -86,14 +86,14 @@ final class RenderViewHelper extends AbstractViewHelper $prototypeName = $overrideConfiguration['prototypeName'] ?? 'standard'; } - // Even though getContainer() is internal, we can't get container injected here due to static scope - /** @var FormFactoryInterface $factory */ - $factory = GeneralUtility::getContainer()->get($factoryClass); - - $formDefinition = $factory->build($overrideConfiguration, $prototypeName); /** @var RenderingContext $renderingContext */ /** @var RequestInterface $request */ $request = $renderingContext->getRequest(); + + // Even though getContainer() is internal, we can't get container injected here due to static scope + /** @var FormFactoryInterface $factory */ + $factory = GeneralUtility::getContainer()->get($factoryClass); + $formDefinition = $factory->build($overrideConfiguration, $prototypeName, $request); $form = $formDefinition->bind($request); return $form->render(); diff --git a/typo3/sysext/form/Tests/Functional/Domain/Runtime/FormRuntimeTest.php b/typo3/sysext/form/Tests/Functional/Domain/Runtime/FormRuntimeTest.php index 32f6b37349500a53cb5f8b02c63ece18331374c3..9fb112592b12cf76e86ff62b705aa34510220764 100644 --- a/typo3/sysext/form/Tests/Functional/Domain/Runtime/FormRuntimeTest.php +++ b/typo3/sysext/form/Tests/Functional/Domain/Runtime/FormRuntimeTest.php @@ -108,7 +108,7 @@ final class FormRuntimeTest extends FunctionalTestCase ], ], ], - ]); + ], null, new ServerRequest()); } private function loadDefaultYamlConfigurations(): void diff --git a/typo3/sysext/form/Tests/Functional/ViewHelpers/RenderFormValueViewHelperTest.php b/typo3/sysext/form/Tests/Functional/ViewHelpers/RenderFormValueViewHelperTest.php index 39f8ec9fbaae818d8ebaaa823cd24c72c75fb111..d2bab947178d2218fc0be57692cc8b2f0428c473 100644 --- a/typo3/sysext/form/Tests/Functional/ViewHelpers/RenderFormValueViewHelperTest.php +++ b/typo3/sysext/form/Tests/Functional/ViewHelpers/RenderFormValueViewHelperTest.php @@ -105,7 +105,7 @@ final class RenderFormValueViewHelperTest extends FunctionalTestCase ], ], ], - ]); + ], null, new ServerRequest()); } private function loadDefaultYamlConfigurations(): void diff --git a/typo3/sysext/form/Tests/Unit/Domain/Factory/ArrayFormFactoryTest.php b/typo3/sysext/form/Tests/Unit/Domain/Factory/ArrayFormFactoryTest.php index 7b6f0ba768545a16e78b2ae84dd91019bce0c681..6cc64772d0d596558d4959ef5b359ddf0d97cdef 100644 --- a/typo3/sysext/form/Tests/Unit/Domain/Factory/ArrayFormFactoryTest.php +++ b/typo3/sysext/form/Tests/Unit/Domain/Factory/ArrayFormFactoryTest.php @@ -18,6 +18,7 @@ declare(strict_types=1); namespace TYPO3\CMS\Form\Tests\Unit\Domain\Factory; use PHPUnit\Framework\Attributes\Test; +use TYPO3\CMS\Core\Http\ServerRequest; use TYPO3\CMS\Form\Domain\Exception\IdentifierNotValidException; use TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory; use TYPO3\CMS\Form\Domain\Model\FormElements\Section; @@ -35,7 +36,8 @@ final class ArrayFormFactoryTest extends UnitTestCase $section = new Section('test', 'page'); $arrayFormFactory = $this->getAccessibleMock(ArrayFormFactory::class, null); - $arrayFormFactory->_call('addNestedRenderable', [], $section); + $request = new ServerRequest(); + $arrayFormFactory->_call('addNestedRenderable', [], $section, $request); } #[Test] @@ -49,7 +51,8 @@ final class ArrayFormFactoryTest extends UnitTestCase 'type' => 'Foo', ]; $arrayFormFactory = $this->getAccessibleMock(ArrayFormFactory::class, null); - $result = $arrayFormFactory->_call('addNestedRenderable', $configuration, $section); + $request = new ServerRequest(); + $result = $arrayFormFactory->_call('addNestedRenderable', $configuration, $section, $request); self::assertSame($unknownElement, $result); } }