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