src/Entity/BaseEntity.php line 12

Open in your IDE?
  1. <?php
  2. namespace App\Entity;
  3. use Doctrine\ORM\Mapping as ORM;
  4. use InvalidArgumentException;
  5. /**
  6. * @ORM\MappedSuperclass
  7. * @ORM\Table(indexes={@ORM\Index(name="IdxTokenHash", columns={"__token"})})
  8. */
  9. abstract class BaseEntity
  10. {
  11. /**
  12. * @ORM\Column(type="string", unique=true, nullable=true)
  13. */
  14. protected $uuid;
  15. /**
  16. * @ORM\Column(type="json", nullable=true)
  17. */
  18. protected ?array $__data = [];
  19. /**
  20. * @ORM\Column(type="json", nullable=true)
  21. */
  22. protected ?array $__schema = null;
  23. /**
  24. * @ORM\Column(type="string", unique=true, length=768, nullable=true, name="__token")
  25. */
  26. protected $__tokenHash;
  27. /**
  28. * Método mágico para acceder a atributos en __data.
  29. */
  30. public function __call(string $method, array $arguments)
  31. {
  32. // Ignorar métodos de lifecycle callbacks de Doctrine
  33. if (in_array($method, ['onPrePersist', 'onPostPersist', 'onPreUpdate', 'onPostUpdate', 'onPreRemove', 'onPostRemove', 'onPostLoad'])) {
  34. // No hacer nada, permitir que Doctrine maneje estos métodos
  35. return null;
  36. }
  37. if (preg_match('/^(get|set|add|remove)([A-Z]\w*)$/', $method, $matches)) {
  38. if (method_exists($this, $method)) {
  39. return call_user_func_array([$this, $method], $arguments);
  40. }
  41. $property = lcfirst($matches[2]);
  42. $snakeCaseProperty = $this->camelToSnake($property);
  43. $action = $matches[1];
  44. switch ($action) {
  45. case 'get':
  46. return $this->getDataFromArray($property, $snakeCaseProperty);
  47. case 'set':
  48. return $this->setDataFromArray($property, $arguments[0] ?? null, $snakeCaseProperty);
  49. case 'add':
  50. return $this->addToArray($property, $arguments[0], $snakeCaseProperty);
  51. case 'remove':
  52. return $this->removeFromArray($property, $arguments[0], $snakeCaseProperty);
  53. }
  54. }
  55. throw new \BadMethodCallException("El método {$method} no está definido.");
  56. }
  57. /**
  58. * Obtiene un valor de __data si existe.
  59. */
  60. public function getDataFromArray(string $key, string $alternativeKey = null)
  61. {
  62. return $this->__data[$key]
  63. ?? ($alternativeKey ? ($this->__data[$alternativeKey] ?? null) : null);
  64. }
  65. /**
  66. * Establece un valor en __data, validándolo contra __schema si existe.
  67. */
  68. public function setDataFromArray(string $key, $value, string $alternativeKey = null): self
  69. {
  70. if ($this->__schema !== null) {
  71. $this->validateData($key, $value);
  72. }
  73. if (!isset($this->__data[$key])) {
  74. $key = $alternativeKey;
  75. }
  76. $this->__data[$key] = $value;
  77. return $this;
  78. }
  79. /**
  80. * Agrega un valor a un array en __data.
  81. */
  82. public function addToArray(string $key, $value, string $alternativeKey = null): self
  83. {
  84. // Resolver la clave correcta sin sobrescribir $key
  85. $resolvedKey = array_key_exists($key, $this->__data) ? $key : ($alternativeKey ?? $key);
  86. // Asegurarse de que la clave esté inicializada como array
  87. if (!isset($this->__data[$resolvedKey]) || !is_array($this->__data[$resolvedKey])) {
  88. $this->__data[$resolvedKey] = [];
  89. }
  90. // Evitar duplicados si es necesario
  91. if (!in_array($value, $this->__data[$resolvedKey], true)) {
  92. $this->__data[$resolvedKey][] = $value;
  93. }
  94. return $this;
  95. }
  96. /**
  97. * Elimina un valor de un array en __data.
  98. */
  99. public function removeFromArray(string $key, $value, string $alternativeKey = null): self
  100. {
  101. $key = $this->__data[$key] ?? ($alternativeKey ? $alternativeKey : $key);
  102. if (isset($this->__data[$key]) && is_array($this->__data[$key])) {
  103. $this->__data[$key] = array_filter($this->__data[$key], function ($item) use ($value) {
  104. return $item !== $value;
  105. });
  106. // Reindexar el array
  107. $this->__data[$key] = array_values($this->__data[$key]);
  108. }
  109. return $this;
  110. }
  111. /**
  112. * Obtiene el esquema completo de la entidad.
  113. */
  114. public function getSchema(): ?array
  115. {
  116. return $this->__schema;
  117. }
  118. /**
  119. * Valida un dato contra el esquema definido en __schema.
  120. */
  121. private function validateData(string $key, $value): void
  122. {
  123. if (!isset($this->__schema['properties'][$key])) {
  124. throw new InvalidArgumentException("El campo '{$key}' no está definido en el esquema.");
  125. }
  126. $schemaField = $this->__schema['properties'][$key];
  127. $type = $schemaField['type'] ?? 'string';
  128. if (!$this->isValidType($value, $type)) {
  129. throw new InvalidArgumentException("El campo '{$key}' debe ser de tipo '{$type}', recibido: " . gettype($value));
  130. }
  131. if (($schemaField['required'] ?? false) && $value === null) {
  132. throw new InvalidArgumentException("El campo '{$key}' es obligatorio y no puede ser nulo.");
  133. }
  134. }
  135. /**
  136. * Comprueba si un valor es válido según el tipo definido en el esquema.
  137. */
  138. private function isValidType($value, string $expectedType): bool
  139. {
  140. switch ($expectedType) {
  141. case 'string':
  142. return is_string($value);
  143. case 'integer':
  144. return is_int($value);
  145. case 'boolean':
  146. return is_bool($value);
  147. case 'float':
  148. return is_float($value);
  149. case 'array':
  150. return is_array($value);
  151. default:
  152. return false;
  153. }
  154. }
  155. /**
  156. * Convierte una cadena en camelCase a snake_case.
  157. */
  158. private function camelToSnake(string $input): string
  159. {
  160. return strtolower(preg_replace('/([a-z])([1-9]|[A-Z])/', '$1_$2', $input));
  161. }
  162. public function getData(): array
  163. {
  164. return $this->__data??[];
  165. }
  166. public function setData(?array $_data): BaseEntity
  167. {
  168. $this->__data = $_data??[];
  169. return $this;
  170. }
  171. public function getTokenHash(): ?string
  172. {
  173. return $this->__tokenHash;
  174. }
  175. public function setTokenHash(?string $__tokenHash): static
  176. {
  177. $this->__tokenHash = $__tokenHash;
  178. return $this;
  179. }
  180. /**
  181. * Get the unique identifier for this entity.
  182. */
  183. public function getUuid(): ?string
  184. {
  185. return $this->uuid;
  186. }
  187. /**
  188. * Set the unique identifier for this entity.
  189. */
  190. public function setUuid(?string $uuid): static
  191. {
  192. $this->uuid = $uuid;
  193. return $this;
  194. }
  195. }