<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use InvalidArgumentException;
/**
* @ORM\MappedSuperclass
* @ORM\Table(indexes={@ORM\Index(name="IdxTokenHash", columns={"__token"})})
*/
abstract class BaseEntity
{
/**
* @ORM\Column(type="string", unique=true, nullable=true)
*/
protected $uuid;
/**
* @ORM\Column(type="json", nullable=true)
*/
protected ?array $__data = [];
/**
* @ORM\Column(type="json", nullable=true)
*/
protected ?array $__schema = null;
/**
* @ORM\Column(type="string", unique=true, length=768, nullable=true, name="__token")
*/
protected $__tokenHash;
/**
* Método mágico para acceder a atributos en __data.
*/
public function __call(string $method, array $arguments)
{
// Ignorar métodos de lifecycle callbacks de Doctrine
if (in_array($method, ['onPrePersist', 'onPostPersist', 'onPreUpdate', 'onPostUpdate', 'onPreRemove', 'onPostRemove', 'onPostLoad'])) {
// No hacer nada, permitir que Doctrine maneje estos métodos
return null;
}
if (preg_match('/^(get|set|add|remove)([A-Z]\w*)$/', $method, $matches)) {
if (method_exists($this, $method)) {
return call_user_func_array([$this, $method], $arguments);
}
$property = lcfirst($matches[2]);
$snakeCaseProperty = $this->camelToSnake($property);
$action = $matches[1];
switch ($action) {
case 'get':
return $this->getDataFromArray($property, $snakeCaseProperty);
case 'set':
return $this->setDataFromArray($property, $arguments[0] ?? null, $snakeCaseProperty);
case 'add':
return $this->addToArray($property, $arguments[0], $snakeCaseProperty);
case 'remove':
return $this->removeFromArray($property, $arguments[0], $snakeCaseProperty);
}
}
throw new \BadMethodCallException("El método {$method} no está definido.");
}
/**
* Obtiene un valor de __data si existe.
*/
public function getDataFromArray(string $key, string $alternativeKey = null)
{
return $this->__data[$key]
?? ($alternativeKey ? ($this->__data[$alternativeKey] ?? null) : null);
}
/**
* Establece un valor en __data, validándolo contra __schema si existe.
*/
public function setDataFromArray(string $key, $value, string $alternativeKey = null): self
{
if ($this->__schema !== null) {
$this->validateData($key, $value);
}
if (!isset($this->__data[$key])) {
$key = $alternativeKey;
}
$this->__data[$key] = $value;
return $this;
}
/**
* Agrega un valor a un array en __data.
*/
public function addToArray(string $key, $value, string $alternativeKey = null): self
{
// Resolver la clave correcta sin sobrescribir $key
$resolvedKey = array_key_exists($key, $this->__data) ? $key : ($alternativeKey ?? $key);
// Asegurarse de que la clave esté inicializada como array
if (!isset($this->__data[$resolvedKey]) || !is_array($this->__data[$resolvedKey])) {
$this->__data[$resolvedKey] = [];
}
// Evitar duplicados si es necesario
if (!in_array($value, $this->__data[$resolvedKey], true)) {
$this->__data[$resolvedKey][] = $value;
}
return $this;
}
/**
* Elimina un valor de un array en __data.
*/
public function removeFromArray(string $key, $value, string $alternativeKey = null): self
{
$key = $this->__data[$key] ?? ($alternativeKey ? $alternativeKey : $key);
if (isset($this->__data[$key]) && is_array($this->__data[$key])) {
$this->__data[$key] = array_filter($this->__data[$key], function ($item) use ($value) {
return $item !== $value;
});
// Reindexar el array
$this->__data[$key] = array_values($this->__data[$key]);
}
return $this;
}
/**
* Obtiene el esquema completo de la entidad.
*/
public function getSchema(): ?array
{
return $this->__schema;
}
/**
* Valida un dato contra el esquema definido en __schema.
*/
private function validateData(string $key, $value): void
{
if (!isset($this->__schema['properties'][$key])) {
throw new InvalidArgumentException("El campo '{$key}' no está definido en el esquema.");
}
$schemaField = $this->__schema['properties'][$key];
$type = $schemaField['type'] ?? 'string';
if (!$this->isValidType($value, $type)) {
throw new InvalidArgumentException("El campo '{$key}' debe ser de tipo '{$type}', recibido: " . gettype($value));
}
if (($schemaField['required'] ?? false) && $value === null) {
throw new InvalidArgumentException("El campo '{$key}' es obligatorio y no puede ser nulo.");
}
}
/**
* Comprueba si un valor es válido según el tipo definido en el esquema.
*/
private function isValidType($value, string $expectedType): bool
{
switch ($expectedType) {
case 'string':
return is_string($value);
case 'integer':
return is_int($value);
case 'boolean':
return is_bool($value);
case 'float':
return is_float($value);
case 'array':
return is_array($value);
default:
return false;
}
}
/**
* Convierte una cadena en camelCase a snake_case.
*/
private function camelToSnake(string $input): string
{
return strtolower(preg_replace('/([a-z])([1-9]|[A-Z])/', '$1_$2', $input));
}
public function getData(): array
{
return $this->__data??[];
}
public function setData(?array $_data): BaseEntity
{
$this->__data = $_data??[];
return $this;
}
public function getTokenHash(): ?string
{
return $this->__tokenHash;
}
public function setTokenHash(?string $__tokenHash): static
{
$this->__tokenHash = $__tokenHash;
return $this;
}
/**
* Get the unique identifier for this entity.
*/
public function getUuid(): ?string
{
return $this->uuid;
}
/**
* Set the unique identifier for this entity.
*/
public function setUuid(?string $uuid): static
{
$this->uuid = $uuid;
return $this;
}
}