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\Persistence\Generic; 17 18use TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface; 19use TYPO3\CMS\Extbase\Object\ObjectManagerInterface; 20use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap; 21use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; 22use TYPO3\CMS\Extbase\Persistence\ObjectStorage; 23 24/** 25 * A proxy that can replace any object and replaces itself in it's parent on 26 * first access (call, get, set, isset, unset). 27 * @internal only to be used within Extbase, not part of TYPO3 Core API. 28 */ 29class LazyObjectStorage extends ObjectStorage implements LoadingStrategyInterface 30{ 31 /** 32 * This field is only needed to make debugging easier: 33 * If you call current() on a class that implements Iterator, PHP will return the first field of the object 34 * instead of calling the current() method of the interface. 35 * We use this unusual behavior of PHP to return the warning below in this case. 36 * 37 * @var string 38 */ 39 private $warning = 'You should never see this warning. If you do, you probably used PHP array functions like current() on the TYPO3\\CMS\\Extbase\\Persistence\\Generic\\LazyObjectStorage. To retrieve the first result, you can use the rewind() and current() methods.'; 40 41 /** 42 * @var DataMapper|null 43 */ 44 protected $dataMapper; 45 46 /** 47 * The object this property is contained in. 48 * 49 * @var \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface 50 */ 51 protected $parentObject; 52 53 /** 54 * The name of the property represented by this proxy. 55 * 56 * @var string 57 */ 58 protected $propertyName; 59 60 /** 61 * The raw field value. 62 * 63 * @var mixed 64 */ 65 protected $fieldValue; 66 67 /** 68 * @var bool 69 */ 70 protected $isInitialized = false; 71 72 /** 73 * @var ObjectManagerInterface 74 */ 75 protected $objectManager; 76 77 /** 78 * @param ObjectManagerInterface $objectManager 79 */ 80 public function injectObjectManager(ObjectManagerInterface $objectManager) 81 { 82 $this->objectManager = $objectManager; 83 } 84 85 /** 86 * Returns the state of the initialization 87 * 88 * @return bool 89 */ 90 public function isInitialized() 91 { 92 return $this->isInitialized; 93 } 94 95 /** 96 * Constructs this proxy instance. 97 * 98 * @param DomainObjectInterface $parentObject The object instance this proxy is part of 99 * @param string $propertyName The name of the proxied property in it's parent 100 * @param mixed $fieldValue The raw field value. 101 * @param ?DataMapper $dataMapper 102 */ 103 public function __construct($parentObject, $propertyName, $fieldValue, ?DataMapper $dataMapper = null) 104 { 105 $this->parentObject = $parentObject; 106 $this->propertyName = $propertyName; 107 $this->fieldValue = $fieldValue; 108 reset($this->storage); 109 $this->dataMapper = $dataMapper; 110 } 111 112 /** 113 * Object initialization called when object is created with ObjectManager, after constructor 114 */ 115 public function initializeObject() 116 { 117 if (!$this->dataMapper) { 118 $this->dataMapper = $this->objectManager->get(DataMapper::class); 119 } 120 } 121 122 /** 123 * This is a function lazy load implementation. 124 */ 125 protected function initialize() 126 { 127 if (!$this->isInitialized) { 128 $this->isInitialized = true; 129 $objects = $this->dataMapper->fetchRelated($this->parentObject, $this->propertyName, $this->fieldValue, false); 130 foreach ($objects as $object) { 131 parent::attach($object); 132 } 133 $this->_memorizeCleanState(); 134 if (!$this->isStorageAlreadyMemorizedInParentCleanState()) { 135 $this->parentObject->_memorizeCleanState($this->propertyName); 136 } 137 } 138 } 139 140 /** 141 * @return bool 142 */ 143 protected function isStorageAlreadyMemorizedInParentCleanState() 144 { 145 return $this->parentObject->_getCleanProperty($this->propertyName) === $this; 146 } 147 148 // Delegation to the ObjectStorage methods below 149 /** 150 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $storage 151 * 152 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::addAll 153 */ 154 public function addAll(ObjectStorage $storage) 155 { 156 $this->initialize(); 157 parent::addAll($storage); 158 } 159 160 /** 161 * @param DomainObjectInterface $object The object to add. 162 * @param mixed $data The data to associate with the object. 163 * 164 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::attach 165 */ 166 public function attach($object, $data = null) 167 { 168 $this->initialize(); 169 parent::attach($object, $data); 170 } 171 172 /** 173 * @param DomainObjectInterface $object The object to look for. 174 * @return bool 175 * 176 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::contains 177 */ 178 public function contains($object) 179 { 180 $this->initialize(); 181 return parent::contains($object); 182 } 183 184 /** 185 * Counts the elements in the storage array 186 * 187 * @throws Exception 188 * @return int The number of elements in the ObjectStorage 189 */ 190 public function count() 191 { 192 $columnMap = $this->dataMapper->getDataMap(get_class($this->parentObject))->getColumnMap($this->propertyName); 193 $numberOfElements = null; 194 if (!$this->isInitialized && $columnMap->getTypeOfRelation() === ColumnMap::RELATION_HAS_MANY) { 195 $numberOfElements = $this->dataMapper->countRelated($this->parentObject, $this->propertyName, $this->fieldValue); 196 } else { 197 $this->initialize(); 198 $numberOfElements = count($this->storage); 199 } 200 if ($numberOfElements === null) { 201 throw new Exception('The number of elements could not be determined.', 1252514486); 202 } 203 return $numberOfElements; 204 } 205 206 /** 207 * @return DomainObjectInterface The object at the current iterator position. 208 * 209 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::current 210 */ 211 public function current() 212 { 213 $this->initialize(); 214 return parent::current(); 215 } 216 217 /** 218 * @param DomainObjectInterface $object The object to remove. 219 * 220 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::detach 221 */ 222 public function detach($object) 223 { 224 $this->initialize(); 225 parent::detach($object); 226 } 227 228 /** 229 * @return string The index corresponding to the position of the iterator. 230 * 231 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::key 232 */ 233 public function key() 234 { 235 $this->initialize(); 236 return parent::key(); 237 } 238 239 /** 240 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::next 241 */ 242 public function next() 243 { 244 $this->initialize(); 245 parent::next(); 246 } 247 248 /** 249 * @param DomainObjectInterface $value The object to look for, or the key in the storage. 250 * @return bool 251 * 252 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::offsetExists 253 */ 254 public function offsetExists($value) 255 { 256 $this->initialize(); 257 return parent::offsetExists($value); 258 } 259 260 /** 261 * @param DomainObjectInterface $value The object to look for, or its key in the storage. 262 * @return mixed 263 * 264 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::offsetGet 265 */ 266 public function offsetGet($value) 267 { 268 $this->initialize(); 269 return parent::offsetGet($value); 270 } 271 272 /** 273 * @param DomainObjectInterface $object The object to add. 274 * @param mixed $info The data to associate with the object. 275 * 276 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::offsetSet 277 */ 278 public function offsetSet($object, $info) 279 { 280 $this->initialize(); 281 parent::offsetSet($object, $info); 282 } 283 284 /** 285 * @param DomainObjectInterface $value The object to remove, or its key in the storage. 286 * 287 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::offsetUnset 288 */ 289 public function offsetUnset($value) 290 { 291 $this->initialize(); 292 parent::offsetUnset($value); 293 } 294 295 /** 296 * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage $storage The storage containing the elements to remove. 297 * 298 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::removeAll 299 */ 300 public function removeAll(ObjectStorage $storage) 301 { 302 $this->initialize(); 303 parent::removeAll($storage); 304 } 305 306 /** 307 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::rewind 308 */ 309 public function rewind() 310 { 311 $this->initialize(); 312 parent::rewind(); 313 } 314 315 /** 316 * @return bool 317 * 318 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::valid 319 */ 320 public function valid() 321 { 322 $this->initialize(); 323 return parent::valid(); 324 } 325 326 /** 327 * @return array 328 * 329 * @see \TYPO3\CMS\Extbase\Persistence\ObjectStorage::toArray 330 */ 331 public function toArray() 332 { 333 $this->initialize(); 334 return parent::toArray(); 335 } 336 337 /** 338 * @param mixed $object 339 * @return int|null 340 */ 341 public function getPosition($object) 342 { 343 $this->initialize(); 344 return parent::getPosition($object); 345 } 346} 347