203 lines
5.5 KiB
PHP
203 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* This file is part of CodeIgniter 4 framework.
|
|
*
|
|
* (c) CodeIgniter Foundation <admin@codeigniter.com>
|
|
*
|
|
* For the full copyright and license information, please view
|
|
* the LICENSE file that was distributed with this source code.
|
|
*/
|
|
|
|
namespace CodeIgniter\DataConverter;
|
|
|
|
use Closure;
|
|
use CodeIgniter\DataCaster\DataCaster;
|
|
use CodeIgniter\Entity\Entity;
|
|
|
|
/**
|
|
* PHP data <==> DataSource data converter
|
|
*
|
|
* @see \CodeIgniter\DataConverter\DataConverterTest
|
|
*
|
|
* @template TEntity of object
|
|
*/
|
|
final class DataConverter
|
|
{
|
|
/**
|
|
* The data caster.
|
|
*/
|
|
private readonly DataCaster $dataCaster;
|
|
|
|
/**
|
|
* @param array<string, class-string> $castHandlers Custom convert handlers
|
|
*
|
|
* @internal
|
|
*/
|
|
public function __construct(
|
|
/**
|
|
* Type definitions.
|
|
*
|
|
* @var array<string, string> [column => type]
|
|
*/
|
|
private readonly array $types,
|
|
array $castHandlers = [],
|
|
/**
|
|
* Helper object.
|
|
*/
|
|
private readonly ?object $helper = null,
|
|
/**
|
|
* Static reconstruct method name or closure to reconstruct an object.
|
|
* Used by reconstruct().
|
|
*
|
|
* @phpstan-var (Closure(array<string, mixed>): TEntity)|string|null
|
|
*/
|
|
private readonly Closure|string|null $reconstructor = 'reconstruct',
|
|
/**
|
|
* Extract method name or closure to extract data from an object.
|
|
* Used by extract().
|
|
*
|
|
* @phpstan-var (Closure(TEntity, bool, bool): array<string, mixed>)|string|null
|
|
*/
|
|
private readonly Closure|string|null $extractor = null,
|
|
) {
|
|
$this->dataCaster = new DataCaster($castHandlers, $types, $this->helper);
|
|
}
|
|
|
|
/**
|
|
* Converts data from DataSource to PHP array with specified type values.
|
|
*
|
|
* @param array<string, mixed> $data DataSource data
|
|
*
|
|
* @internal
|
|
*/
|
|
public function fromDataSource(array $data): array
|
|
{
|
|
foreach (array_keys($this->types) as $field) {
|
|
if (array_key_exists($field, $data)) {
|
|
$data[$field] = $this->dataCaster->castAs($data[$field], $field, 'get');
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Converts PHP array to data for DataSource field types.
|
|
*
|
|
* @param array<string, mixed> $phpData PHP data
|
|
*
|
|
* @internal
|
|
*/
|
|
public function toDataSource(array $phpData): array
|
|
{
|
|
foreach (array_keys($this->types) as $field) {
|
|
if (array_key_exists($field, $phpData)) {
|
|
$phpData[$field] = $this->dataCaster->castAs($phpData[$field], $field, 'set');
|
|
}
|
|
}
|
|
|
|
return $phpData;
|
|
}
|
|
|
|
/**
|
|
* Takes database data array and creates a specified type object.
|
|
*
|
|
* @param class-string $classname
|
|
* @phpstan-param class-string<TEntity> $classname
|
|
* @param array<string, mixed> $row Raw data from database
|
|
*
|
|
* @phpstan-return TEntity
|
|
*
|
|
* @internal
|
|
*/
|
|
public function reconstruct(string $classname, array $row): object
|
|
{
|
|
$phpData = $this->fromDataSource($row);
|
|
|
|
// Use static reconstruct method.
|
|
if (is_string($this->reconstructor) && method_exists($classname, $this->reconstructor)) {
|
|
$method = $this->reconstructor;
|
|
|
|
return $classname::$method($phpData);
|
|
}
|
|
|
|
// Use closure to reconstruct.
|
|
if ($this->reconstructor instanceof Closure) {
|
|
$closure = $this->reconstructor;
|
|
|
|
return $closure($phpData);
|
|
}
|
|
|
|
$classObj = new $classname();
|
|
|
|
if ($classObj instanceof Entity) {
|
|
$classObj->injectRawData($phpData);
|
|
$classObj->syncOriginal();
|
|
|
|
return $classObj;
|
|
}
|
|
|
|
$classSet = Closure::bind(function ($key, $value): void {
|
|
$this->{$key} = $value;
|
|
}, $classObj, $classname);
|
|
|
|
foreach ($phpData as $key => $value) {
|
|
$classSet($key, $value);
|
|
}
|
|
|
|
return $classObj;
|
|
}
|
|
|
|
/**
|
|
* Takes an object and extract properties as an array.
|
|
*
|
|
* @param bool $onlyChanged Only for CodeIgniter's Entity. If true, only returns
|
|
* values that have changed since object creation.
|
|
* @param bool $recursive Only for CodeIgniter's Entity. If true, inner
|
|
* entities will be cast as array as well.
|
|
*
|
|
* @return array<string, mixed>
|
|
*
|
|
* @internal
|
|
*/
|
|
public function extract(object $object, bool $onlyChanged = false, bool $recursive = false): array
|
|
{
|
|
// Use extractor method.
|
|
if (is_string($this->extractor) && method_exists($object, $this->extractor)) {
|
|
$method = $this->extractor;
|
|
$row = $object->{$method}($onlyChanged, $recursive);
|
|
|
|
return $this->toDataSource($row);
|
|
}
|
|
|
|
// Use closure to extract.
|
|
if ($this->extractor instanceof Closure) {
|
|
$closure = $this->extractor;
|
|
$row = $closure($object, $onlyChanged, $recursive);
|
|
|
|
return $this->toDataSource($row);
|
|
}
|
|
|
|
if ($object instanceof Entity) {
|
|
$row = $object->toRawArray($onlyChanged, $recursive);
|
|
|
|
return $this->toDataSource($row);
|
|
}
|
|
|
|
$array = (array) $object;
|
|
|
|
$row = [];
|
|
|
|
foreach ($array as $key => $value) {
|
|
$key = preg_replace('/\000.*\000/', '', $key);
|
|
|
|
$row[$key] = $value;
|
|
}
|
|
|
|
return $this->toDataSource($row);
|
|
}
|
|
}
|