1<?php 2 3 4namespace ComboStrap\Meta\Api; 5 6 7use ComboStrap\DataType; 8use ComboStrap\ExceptionBadArgument; 9use ComboStrap\ExceptionNotFound; 10use ComboStrap\ExceptionRuntime; 11use ComboStrap\ExceptionRuntimeInternal; 12use ComboStrap\LogUtility; 13use ComboStrap\Meta\Field\Aliases; 14use ComboStrap\References; 15 16/** 17 * Class MetadataTabular 18 * @package ComboStrap 19 * A list of row represented as a list of column 20 * ie an entity with a map that has a key 21 * 22 * The value of a tabular is an array of row (where a row is an associative array) 23 */ 24abstract class MetadataTabular extends Metadata 25{ 26 27 /** 28 * In the array, the identifier may be the persistent one 29 * or the identifier one 30 */ 31 const PERSISTENT_NAME = "persistent"; 32 const IDENTIFIER_NAME = "identifier"; 33 34 35 /** 36 * A array of rows where 37 * - the key is the `identifier value` 38 * - the value is a list of column metadata 39 * * the key is the {@link Metadata::getPersistentName()} 40 * * the value is the metadata 41 * 42 * @var Metadata[][] 43 */ 44 protected ?array $rows = null; 45 46 47 /** 48 * @return array - the rows in associate array format 49 * where the identifier value is the key 50 * @throws ExceptionNotFound 51 */ 52 public function getValue(): array 53 { 54 $this->buildCheck(); 55 if ($this->rows === null) { 56 throw new ExceptionNotFound("No tabular data found."); 57 } 58 return $this->rows; 59 } 60 61 public function setValue($value): Metadata 62 { 63 if ($value === null) { 64 return $this; 65 } 66 if (!is_array($value)) { 67 throw new ExceptionRuntime("The data set is not an array (The tabular data is an array of rows)"); 68 } 69 $keys = array_keys($value); 70 foreach ($keys as $key) { 71 if (!is_numeric($key)) { 72 throw new ExceptionRuntime("The element of the array are not rows. The index ($key) should be numeric and is not"); 73 } 74 } 75 $this->rows = $value; 76 return $this; 77 78 } 79 80 81 public function toStoreValue(): ?array 82 { 83 $this->buildCheck(); 84 if ($this->rows === null) { 85 return null; 86 } 87 return $this->rowsToStore($this->rows); 88 } 89 90 /** 91 * /** 92 * A list of rows that contains a list of metadata 93 * with their value 94 * Each row has the key has value 95 * @return Metadata[][] - an array of rows 96 * @throws ExceptionNotFound 97 */ 98 public abstract function getDefaultValue(): array; 99 100 public function toStoreDefaultValue(): ?array 101 { 102 try { 103 $defaultRows = $this->getDefaultValue(); 104 } catch (ExceptionNotFound $e) { 105 return null; 106 } 107 108 return $this->rowsToStore($defaultRows); 109 } 110 111 /** 112 * The value is: 113 * * a string for a unique identifier value 114 * * an array of columns or an array of rows 115 */ 116 public function setFromStoreValueWithoutException($value): Metadata 117 { 118 119 if ($value === null) { 120 return $this; 121 } 122 123 /** 124 * Value of the metadata id 125 */ 126 $identifierMetadataObject = $this->getUidObject(); 127 $identifierPersistentName = $identifierMetadataObject::getPersistentName(); 128 129 /** 130 * Single value 131 */ 132 if (is_string($value)) { 133 /** 134 * @var Metadata $identifierMetadata 135 */ 136 $identifierMetadata = (new $identifierMetadataObject()); 137 $identifierMetadata 138 ->setResource($this->getResource()) 139 ->setValue($value); 140 $rowId = $this->getRowId($identifierMetadata); 141 $this->rows[$rowId] = [$identifierPersistentName => $identifierMetadata]; 142 return $this; 143 } 144 145 /** 146 * Array 147 */ 148 if (!is_array($value)) { 149 LogUtility::msg("The tabular value is nor a string nor an array"); 150 return $this; 151 } 152 153 154 /** 155 * List of columns ({@link MetadataFormDataStore form html way} 156 * 157 * For example: for {@link Aliases}, a form will have two fields 158 * alias-path: 159 * 0: path1 160 * 1: path2 161 * alias-type: 162 * 0: redirect 163 * 1: redirect 164 * 165 * or just a list ({@link References} 166 * references: 167 * 0: reference-1 168 * 1: $colValue-2 169 */ 170 $keys = array_keys($value); 171 $firstElement = array_shift($keys); 172 if (!is_numeric($firstElement)) { 173 /** 174 * Check which kind of key is used 175 * Resistance to the property key ! 176 */ 177 $identifierName = $identifierMetadataObject::getPersistentName(); 178 $identifierNameType = self::PERSISTENT_NAME; 179 if (!isset($value[$identifierName])) { 180 $identifierNameType = self::IDENTIFIER_NAME; 181 $identifierName = $identifierMetadataObject::getName(); 182 } 183 $identifierValues = $value[$identifierName]; 184 if ($identifierValues === null || $identifierValues === "") { 185 // No data 186 return $this; 187 } 188 $index = 0; 189 if (!is_array($identifierValues)) { 190 // only one value 191 $identifierValues = [$identifierValues]; 192 } 193 foreach ($identifierValues as $identifierValue) { 194 $row = []; 195 if ($identifierValue === "") { 196 // an empty row in the table 197 continue; 198 } 199 $row[$identifierPersistentName] = MetadataSystem::toMetadataObject($identifierMetadataObject, $this) 200 ->setFromStoreValue($identifierValue); 201 foreach ($this->getChildrenClass() as $childClass) { 202 if ($childClass === get_class($identifierMetadataObject)) { 203 continue; 204 } 205 $metadataChildObject = MetadataSystem::toMetadataObject($childClass, $this); 206 $name = $metadataChildObject::getPersistentName(); 207 if ($identifierNameType === self::IDENTIFIER_NAME) { 208 $name = $metadataChildObject::getName(); 209 } 210 $childValue = $value[$name]; 211 if (is_array($childValue)) { 212 $childValue = $childValue[$index]; 213 $index++; 214 } 215 $metadataChildObject->setFromStoreValue($childValue); 216 $row[$metadataChildObject::getPersistentName()] = $metadataChildObject; 217 } 218 $this->rows[] = $row; 219 } 220 221 return $this; 222 } 223 224 /** 225 * List of row (frontmatter, dokuwiki, ...) 226 */ 227 $childClassesByPersistentName = []; 228 foreach ($this->getChildrenObject() as $childObject) { 229 $childClassesByPersistentName[$childObject::getPersistentName()] = $childObject; 230 } 231 foreach ($value as $item) { 232 233 /** 234 * By default, the single value is the identifier 235 * 236 * (Note that from {@link MetadataFormDataStore}, tabular data 237 * should be build before via the {@link self::buildFromReadStore()} 238 * as tabular data is represented as a series of column) 239 * 240 */ 241 if (is_string($item)) { 242 try { 243 $identifierMetadata = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)->setFromStoreValue($item); 244 } catch (ExceptionBadArgument $e) { 245 throw ExceptionRuntimeInternal::withMessageAndError("The $identifierMetadataObject should be known", $e); 246 } 247 $identifierValue = $this->getRowId($identifierMetadata); 248 $this->rows[$identifierValue] = [$identifierPersistentName => $identifierMetadata]; 249 continue; 250 } 251 if (!is_array($item)) { 252 LogUtility::msg("The tabular value is not a string nor an array"); 253 } 254 $row = []; 255 $idValue = null; 256 foreach ($item as $colName => $colValue) { 257 $childClass = $childClassesByPersistentName[$colName] ?? null; 258 if ($childClass === null) { 259 LogUtility::internalError("The column ($colName) does not have a metadata definition"); 260 continue; 261 } 262 $childObject = MetadataSystem::toMetadataObject($childClass, $this); 263 $childObject->setFromStoreValueWithoutException($colValue); 264 $row[$childObject::getPersistentName()] = $childObject; 265 if ($childObject::getPersistentName() === $identifierPersistentName) { 266 $idValue = $this->getRowId($childObject); 267 } 268 } 269 if ($idValue === null) { 270 LogUtility::msg("The value for the identifier ($identifierPersistentName) was not found"); 271 continue; 272 } 273 $this->rows[$idValue] = $row; 274 275 } 276 return $this; 277 } 278 279 280 public function valueIsNotNull(): bool 281 { 282 return $this->rows !== null; 283 } 284 285 public 286 function remove(string $identifierValue): MetadataTabular 287 { 288 $this->buildCheck(); 289 if ($this->rows === null) { 290 return $this; 291 } 292 unset($this->rows[$identifierValue]); 293 return $this; 294 } 295 296 297 public 298 function has($identifierValue): bool 299 { 300 $this->buildCheck(); 301 if ($this->rows === null) { 302 return false; 303 } 304 return isset($this->rows[$identifierValue]); 305 } 306 307 public 308 function getSize(): int 309 { 310 $this->buildCheck(); 311 if ($this->rows === null) { 312 return 0; 313 } 314 return sizeof($this->rows); 315 } 316 317 public function getRow($id): ?array 318 { 319 $this->buildCheck(); 320 $normalizedValue = $this->getUidObject() 321 ->setFromStoreValueWithoutException($id) 322 ->getValue(); 323 if (is_object($normalizedValue)) { 324 $normalizedValue = $normalizedValue->__toString(); 325 } 326 return $this->rows[$normalizedValue] ?? null; 327 } 328 329 static public function getDataType(): string 330 { 331 return DataType::TABULAR_TYPE_VALUE; 332 } 333 334 /** 335 * @throws ExceptionNotFound - if the identifier or it's value was not found 336 * @var Metadata[] $metas 337 */ 338 public function addRow(array $metas): MetadataTabular 339 { 340 $row = []; 341 $identifier = null; 342 foreach ($metas as $meta) { 343 $row[$meta::getPersistentName()] = $meta; 344 if (get_class($meta) === $this->getUidClass()) { 345 $identifier = $meta->getValue(); 346 } 347 } 348 if ($identifier === null) { 349 throw new ExceptionNotFound("The identifier value was not found in the row"); 350 } 351 $this->rows[$identifier] = $row; 352 return $this; 353 } 354 355 private function rowsToStore(array $defaultRows): array 356 { 357 $rowsArray = []; 358 ksort($defaultRows); 359 foreach ($defaultRows as $row) { 360 $rowArray = []; 361 foreach ($row as $metadata) { 362 $toStoreValue = $metadata->toStoreValue(); 363 $toDefaultStoreValue = $metadata->toStoreDefaultValue(); 364 if ( 365 $toStoreValue !== null 366 && $toStoreValue !== $toDefaultStoreValue 367 ) { 368 $rowArray[$metadata::getPersistentName()] = $toStoreValue; 369 } 370 } 371 $rowsArray[] = $rowArray; 372 } 373 return $rowsArray; 374 } 375 376 /** 377 * @param Metadata $identifierMetadata 378 * @return mixed 379 * Due to dokuwiki, the same id may be a markup or media path 380 * We build the object from the same id but the url is not the same 381 * This function takes the metadata, get the value and 382 * return the identifier suitable for an array 383 */ 384 private function getRowId(Metadata $identifierMetadata) 385 { 386 try { 387 $identifierValue = $identifierMetadata->getValue(); // normalize if any 388 } catch (ExceptionNotFound $e) { 389 throw ExceptionRuntimeInternal::withMessageAndError("The meta identifier ($identifierMetadata) should have a value", $e); 390 } 391 if (DataType::isObject($identifierValue)) { 392 /** 393 * An object cannot be the key of an array 394 */ 395 $identifierValue = $identifierValue->__toString(); 396 } 397 return $identifierValue; 398 } 399 400} 401