1<?php 2namespace TYPO3\CMS\Extbase\Validation\Validator; 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 17use TYPO3\CMS\Extbase\Reflection\ObjectAccess; 18 19/** 20 * A generic object validator which allows for specifying property validators 21 */ 22class GenericObjectValidator extends AbstractValidator implements ObjectValidatorInterface 23{ 24 /** 25 * @var \SplObjectStorage[] 26 */ 27 protected $propertyValidators = []; 28 29 /** 30 * Checks if the given value is valid according to the validator, and returns 31 * the Error Messages object which occurred. 32 * 33 * @param mixed $value The value that should be validated 34 * @return \TYPO3\CMS\Extbase\Error\Result 35 */ 36 public function validate($value) 37 { 38 if (is_object($value) && $this->isValidatedAlready($value)) { 39 return $this->result; 40 } 41 42 $this->result = new \TYPO3\CMS\Extbase\Error\Result(); 43 if ($this->acceptsEmptyValues === false || $this->isEmpty($value) === false) { 44 if (!is_object($value)) { 45 $this->addError('Object expected, %1$s given.', 1241099149, [gettype($value)]); 46 } elseif ($this->isValidatedAlready($value) === false) { 47 $this->markInstanceAsValidated($value); 48 $this->isValid($value); 49 } 50 } 51 52 return $this->result; 53 } 54 55 /** 56 * Load the property value to be used for validation. 57 * 58 * In case the object is a doctrine proxy, we need to load the real instance first. 59 * 60 * @param object $object 61 * @param string $propertyName 62 * @return mixed 63 */ 64 protected function getPropertyValue($object, $propertyName) 65 { 66 // @todo add support for lazy loading proxies, if needed 67 if (ObjectAccess::isPropertyGettable($object, $propertyName)) { 68 return ObjectAccess::getProperty($object, $propertyName); 69 } 70 return ObjectAccess::getProperty($object, $propertyName, true); 71 } 72 73 /** 74 * Checks if the specified property of the given object is valid, and adds 75 * found errors to the $messages object. 76 * 77 * @param mixed $value The value to be validated 78 * @param \Traversable $validators The validators to be called on the value 79 * @param string $propertyName Name of ther property to check 80 */ 81 protected function checkProperty($value, $validators, $propertyName) 82 { 83 /** @var \TYPO3\CMS\Extbase\Error\Result $result */ 84 $result = null; 85 foreach ($validators as $validator) { 86 if ($validator instanceof ObjectValidatorInterface) { 87 $validator->setValidatedInstancesContainer($this->validatedInstancesContainer); 88 } 89 $currentResult = $validator->validate($value); 90 if ($currentResult->hasMessages()) { 91 if ($result == null) { 92 $result = $currentResult; 93 } else { 94 $result->merge($currentResult); 95 } 96 } 97 } 98 if ($result != null) { 99 $this->result->forProperty($propertyName)->merge($result); 100 } 101 } 102 103 /** 104 * Checks if the given value is valid according to the property validators. 105 * 106 * @param mixed $object The value that should be validated 107 */ 108 protected function isValid($object) 109 { 110 foreach ($this->propertyValidators as $propertyName => $validators) { 111 $propertyValue = $this->getPropertyValue($object, $propertyName); 112 $this->checkProperty($propertyValue, $validators, $propertyName); 113 } 114 } 115 116 /** 117 * Checks the given object can be validated by the validator implementation 118 * 119 * @param mixed $object The object to be checked 120 * @return bool TRUE if the given value is an object 121 */ 122 public function canValidate($object) 123 { 124 return is_object($object); 125 } 126 127 /** 128 * Adds the given validator for validation of the specified property. 129 * 130 * @param string $propertyName Name of the property to validate 131 * @param ValidatorInterface $validator The property validator 132 */ 133 public function addPropertyValidator($propertyName, ValidatorInterface $validator) 134 { 135 if (!isset($this->propertyValidators[$propertyName])) { 136 $this->propertyValidators[$propertyName] = new \SplObjectStorage(); 137 } 138 $this->propertyValidators[$propertyName]->attach($validator); 139 } 140 141 /** 142 * @param object $object 143 * @return bool 144 */ 145 protected function isValidatedAlready($object) 146 { 147 if ($this->validatedInstancesContainer === null) { 148 $this->validatedInstancesContainer = new \SplObjectStorage(); 149 } 150 if ($this->validatedInstancesContainer->contains($object)) { 151 return true; 152 } 153 154 return false; 155 } 156 157 /** 158 * @param $object 159 */ 160 protected function markInstanceAsValidated($object): void 161 { 162 $this->validatedInstancesContainer->attach($object); 163 } 164 165 /** 166 * Returns all property validators - or only validators of the specified property 167 * 168 * @param string $propertyName Name of the property to return validators for 169 * @return array An array of validators 170 */ 171 public function getPropertyValidators($propertyName = null) 172 { 173 if ($propertyName !== null) { 174 return $this->propertyValidators[$propertyName] ?? []; 175 } 176 return $this->propertyValidators; 177 } 178 179 /** 180 * @var \SplObjectStorage 181 */ 182 protected $validatedInstancesContainer; 183 184 /** 185 * Allows to set a container to keep track of validated instances. 186 * 187 * @param \SplObjectStorage $validatedInstancesContainer A container to keep track of validated instances 188 */ 189 public function setValidatedInstancesContainer(\SplObjectStorage $validatedInstancesContainer) 190 { 191 $this->validatedInstancesContainer = $validatedInstancesContainer; 192 } 193} 194