Skip to content

Instantly share code, notes, and snippets.

@il-m-yamagishi
Last active September 23, 2022 07:49
Show Gist options
  • Save il-m-yamagishi/b845406086fdef32a5f794ff02eba30a to your computer and use it in GitHub Desktop.
Save il-m-yamagishi/b845406086fdef32a5f794ff02eba30a to your computer and use it in GitHub Desktop.
PHP Conference Japan 2022 LT 資料
<?php
/**
* @copyright 2022 Masaru Yamagishi
* @license CC-BY-4.0
* @link https://fortee.jp/phpcon-2022/proposal/51f66d0e-5bae-4902-bb63-89700407204a
* @link https://docs.google.com/presentation/d/11NCgOhSF3ZB1Kea8VD4TW9qghQjxSZwm4-IOIP2x-aQ/edit#slide=id.p
*/
declare(strict_types=1);
// [{"id":1,"name":"PHP","level":1,"jobType":1}]
// ↓
class Job
{
public function __construct(
public readonly int $id,
public readonly string $name,
public readonly int $level,
public readonly JobType $jobType,
) {
}
}
// ---
interface MappingInterface
{
/**
* @template T of object
* @param string $className
* @psalm-param class-string<T> $className
* @param int $id
* @return object
* @psalm-return T
*/
public function instantiate(string $className, int $id): object;
}
$job = $mapping->instantiate(Job::class, 1);
assert($job instanceof Job);
// ---
class JsonMapping implements MappingInterface
{
/** {@inheritDoc} */
public function instantiate(string $className, int $id): object
{
$records = $this->load($className);
$record = $this->filter($records, $id);
return $this->map($className, $record);
}
/**
* json ファイルから二次元配列でデータ読み込み
* @param string $className
* @return array
*/
private function load(string $className): array { /* ... */ }
/**
* 指定した ID のデータを取得
* @param array $records
* @param int $id
* @return array
*/
private function filter(array $records, int $id): array { /* ... */ }
/**
* array のデータをクラスに変換
* @template T of object
* @param string $className
* @psalm-param class-string<T> $className
* @param array $record
* @return object
* @psalm-return T
*/
private function map(string $className, array $record): object
{
$refClass = new \ReflectionClass($className);
$results = [];
foreach ($refClass->getConstructor()->getParameters() as $param) {
$name = $param->getName();
$type = $param->getType();
$rawValue = $record[$name];
if ($type->allowsNull() && is_null($rawValue)) {
$results[$name] = null;
continue;
}
$results[$name] = $this->mapValue($type->getName(), $rawValue);
}
return $refClass->newInstanceArgs($results);
}
/**
* 型名から値を変換
* @param string $typeName
* @param mixed $rawValue
* @return mixed
*/
private function mapValue(string $typeName, mixed $rawValue): mixed
{
return match ($typeName) {
'int' => \is_int($rawValue) ? \intval($rawValue) : throw new \LogicException(),
'string' => \is_string($rawValue) ? \strval($rawValue) : throw new \LogicException(),
'float' => \is_float($rawValue) ? \floatval($rawValue) : throw new \LogicException(),
'bool' => \is_bool($rawValue) ? \boolval($rawValue) : throw new \LogicException(),
// ...
default => match (true) {
\is_subclass_of($typeName, \BackedEnum::class, true) => $typeName::from($rawValue),
// ...
},
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment