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->setValue($value); 139 $rowId = $this->getRowId($identifierMetadata); 140 $this->rows[$rowId] = [$identifierPersistentName => $identifierMetadata]; 141 return $this; 142 } 143 144 /** 145 * Array 146 */ 147 if (!is_array($value)) { 148 LogUtility::msg("The tabular value is nor a string nor an array"); 149 return $this; 150 } 151 152 153 /** 154 * List of columns ({@link MetadataFormDataStore form html way} 155 * 156 * For example: for {@link Aliases}, a form will have two fields 157 * alias-path: 158 * 0: path1 159 * 1: path2 160 * alias-type: 161 * 0: redirect 162 * 1: redirect 163 * 164 * or just a list ({@link References} 165 * references: 166 * 0: reference-1 167 * 1: $colValue-2 168 */ 169 $keys = array_keys($value); 170 $firstElement = array_shift($keys); 171 if (!is_numeric($firstElement)) { 172 /** 173 * Check which kind of key is used 174 * Resistance to the property key ! 175 */ 176 $identifierName = $identifierMetadataObject::getPersistentName(); 177 $identifierNameType = self::PERSISTENT_NAME; 178 if (!isset($value[$identifierName])) { 179 $identifierNameType = self::IDENTIFIER_NAME; 180 $identifierName = $identifierMetadataObject::getName(); 181 } 182 $identifierValues = $value[$identifierName]; 183 if ($identifierValues === null || $identifierValues === "") { 184 // No data 185 return $this; 186 } 187 $index = 0; 188 if (!is_array($identifierValues)) { 189 // only one value 190 $identifierValues = [$identifierValues]; 191 } 192 foreach ($identifierValues as $identifierValue) { 193 $row = []; 194 if ($identifierValue === "") { 195 // an empty row in the table 196 continue; 197 } 198 $row[$identifierPersistentName] = MetadataSystem::toMetadataObject($identifierMetadataObject, $this) 199 ->setFromStoreValue($identifierValue); 200 foreach ($this->getChildrenClass() as $childClass) { 201 if ($childClass === get_class($identifierMetadataObject)) { 202 continue; 203 } 204 $metadataChildObject = MetadataSystem::toMetadataObject($childClass, $this); 205 $name = $metadataChildObject::getPersistentName(); 206 if ($identifierNameType === self::IDENTIFIER_NAME) { 207 $name = $metadataChildObject::getName(); 208 } 209 $childValue = $value[$name]; 210 if (is_array($childValue)) { 211 $childValue = $childValue[$index]; 212 $index++; 213 } 214 $metadataChildObject->setFromStoreValue($childValue); 215 $row[$metadataChildObject::getPersistentName()] = $metadataChildObject; 216 } 217 $this->rows[] = $row; 218 } 219 220 return $this; 221 } 222 223 /** 224 * List of row (frontmatter, dokuwiki, ...) 225 */ 226 $childClassesByPersistentName = []; 227 foreach ($this->getChildrenObject() as $childObject) { 228 $childClassesByPersistentName[$childObject::getPersistentName()] = $childObject; 229 } 230 foreach ($value as $item) { 231 232 /** 233 * By default, the single value is the identifier 234 * 235 * (Note that from {@link MetadataFormDataStore}, tabular data 236 * should be build before via the {@link self::buildFromReadStore()} 237 * as tabular data is represented as a series of column) 238 * 239 */ 240 if (is_string($item)) { 241 try { 242 $identifierMetadata = MetadataSystem::toMetadataObject($identifierMetadataObject, $this)->setFromStoreValue($item); 243 } catch (ExceptionBadArgument $e) { 244 throw ExceptionRuntimeInternal::withMessageAndError("The $identifierMetadataObject should be known", $e); 245 } 246 $identifierValue = $this->getRowId($identifierMetadata); 247 $this->rows[$identifierValue] = [$identifierPersistentName => $identifierMetadata]; 248 continue; 249 } 250 if (!is_array($item)) { 251 LogUtility::msg("The tabular value is not a string nor an array"); 252 } 253 $row = []; 254 $idValue = null; 255 foreach ($item as $colName => $colValue) { 256 $childClass = $childClassesByPersistentName[$colName]; 257 if ($childClass === null) { 258 LogUtility::msg("The column ($colName) does not have a metadata definition"); 259 continue; 260 } 261 $childObject = MetadataSystem::toMetadataObject($childClass, $this); 262 $childObject->setFromStoreValueWithoutException($colValue); 263 $row[$childObject::getPersistentName()] = $childObject; 264 if ($childObject::getPersistentName() === $identifierPersistentName) { 265 $idValue = $this->getRowId($childObject); 266 } 267 } 268 if ($idValue === null) { 269 LogUtility::msg("The value for the identifier ($identifierPersistentName) was not found"); 270 continue; 271 } 272 $this->rows[$idValue] = $row; 273 274 } 275 return $this; 276 } 277 278 279 public function valueIsNotNull(): bool 280 { 281 return $this->rows !== null; 282 } 283 284 public 285 function remove(String $identifierValue): MetadataTabular 286 { 287 $this->buildCheck(); 288 if ($this->rows === null) { 289 return $this; 290 } 291 unset($this->rows[$identifierValue]); 292 return $this; 293 } 294 295 296 public 297 function has($identifierValue): bool 298 { 299 $this->buildCheck(); 300 if ($this->rows === null) { 301 return false; 302 } 303 return isset($this->rows[$identifierValue]); 304 } 305 306 public 307 function getSize(): int 308 { 309 $this->buildCheck(); 310 if ($this->rows === null) { 311 return 0; 312 } 313 return sizeof($this->rows); 314 } 315 316 public function getRow($id): ?array 317 { 318 $this->buildCheck(); 319 $normalizedValue = $this->getUidObject() 320 ->setFromStoreValueWithoutException($id) 321 ->getValue(); 322 if(is_object($normalizedValue)){ 323 $normalizedValue = $normalizedValue->__toString(); 324 } 325 return $this->rows[$normalizedValue]; 326 } 327 328 static public function getDataType(): string 329 { 330 return DataType::TABULAR_TYPE_VALUE; 331 } 332 333 /** 334 * @throws ExceptionNotFound - if the identifier or it's value was not found 335 * @var Metadata[] $metas 336 */ 337 public function addRow(array $metas): MetadataTabular 338 { 339 $row = []; 340 $identifier = null; 341 foreach ($metas as $meta) { 342 $row[$meta::getPersistentName()] = $meta; 343 if (get_class($meta) === $this->getUidClass()) { 344 $identifier = $meta->getValue(); 345 } 346 } 347 if ($identifier === null) { 348 throw new ExceptionNotFound("The identifier value was not found in the row"); 349 } 350 $this->rows[$identifier] = $row; 351 return $this; 352 } 353 354 private function rowsToStore(array $defaultRows): array 355 { 356 $rowsArray = []; 357 ksort($defaultRows); 358 foreach ($defaultRows as $row) { 359 $rowArray = []; 360 foreach ($row as $metadata) { 361 $toStoreValue = $metadata->toStoreValue(); 362 $toDefaultStoreValue = $metadata->toStoreDefaultValue(); 363 if ( 364 $toStoreValue !== null 365 && $toStoreValue !== $toDefaultStoreValue 366 ) { 367 $rowArray[$metadata::getPersistentName()] = $toStoreValue; 368 } 369 } 370 $rowsArray[] = $rowArray; 371 } 372 return $rowsArray; 373 } 374 375 /** 376 * @param Metadata $identifierMetadata 377 * @return mixed 378 * Due to dokuwiki, the same id may be a markup or media path 379 * We build the object from the same id but the url is not the same 380 * This function takes the metadata, get the value and 381 * return the identifier suitable for an array 382 */ 383 private function getRowId(Metadata $identifierMetadata) 384 { 385 try { 386 $identifierValue = $identifierMetadata->getValue(); // normalize if any 387 } catch (ExceptionNotFound $e) { 388 throw ExceptionRuntimeInternal::withMessageAndError("The meta identifier ($identifierMetadata) should have a value", $e); 389 } 390 if (DataType::isObject($identifierValue)) { 391 /** 392 * An object cannot be the key of an array 393 */ 394 $identifierValue = $identifierValue->__toString(); 395 } 396 return $identifierValue; 397 } 398 399} 400