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