initial commit
commit
1cec4a45f6
@ -0,0 +1,4 @@
|
||||
.idea
|
||||
var
|
||||
vendor
|
||||
tests/.phpunit.cache
|
||||
@ -0,0 +1,16 @@
|
||||
.PHONY: cc
|
||||
cc: stan test
|
||||
|
||||
.PHONY: stan
|
||||
stan:
|
||||
vendor/bin/phpstan analyse src --level=10
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
vendor/bin/phpunit
|
||||
|
||||
.PHONY: classes
|
||||
classes: var/classes.png
|
||||
|
||||
var/classes.png: $(shell find src)
|
||||
vendor/bin/php-class-diagram src | plantuml -p > var/classes.png
|
||||
@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "kartierung/kartierung",
|
||||
"version": "0.1",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"ext-pdo": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Kartierung\\": [
|
||||
"src/",
|
||||
"tests/"
|
||||
]
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^12.4",
|
||||
"phpstan/phpstan": "^2.1",
|
||||
"smeghead/php-class-diagram": "^1.6",
|
||||
"ext-pdo_sqlite": "*"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
cacheDirectory="tests/.phpunit.cache"
|
||||
executionOrder="depends,defects"
|
||||
requireCoverageMetadata="false"
|
||||
beStrictAboutCoverageMetadata="true"
|
||||
beStrictAboutOutputDuringTests="true"
|
||||
displayDetailsOnPhpunitDeprecations="true"
|
||||
failOnPhpunitDeprecation="true"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true">
|
||||
<testsuites>
|
||||
<testsuite name="default">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<source ignoreIndirectDeprecations="true" restrictNotices="true" restrictWarnings="true">
|
||||
<include>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
use Kartierung\Attribute\Column;
|
||||
use Kartierung\Attribute\Id;
|
||||
use Kartierung\Attribute\Table;
|
||||
use ReflectionClass;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionProperty;
|
||||
|
||||
/**
|
||||
* @template E of object
|
||||
*/
|
||||
readonly class EntityAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param class-string<E> $entity
|
||||
*/
|
||||
public function __construct(
|
||||
private string $entity
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return EntityResult<E>
|
||||
*/
|
||||
public function analyze(): EntityResult
|
||||
{
|
||||
$refClass = new ReflectionClass($this->entity);
|
||||
|
||||
$fields = $this->fields($refClass);
|
||||
|
||||
return new EntityResult(
|
||||
classFqcn: $refClass->getName(),
|
||||
tableName: $this->tableName($refClass),
|
||||
fields: $fields,
|
||||
idField: $this->idField($fields)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<E> $refClass
|
||||
* @return string
|
||||
*/
|
||||
private function tableName(ReflectionClass $refClass): string
|
||||
{
|
||||
$attrs = $refClass->getAttributes(Table::class);
|
||||
|
||||
if (count($attrs) !== 1) {
|
||||
//throw new InvalidEntity('Attribute #[Table] not found on entity ' . $refClass->name);
|
||||
return $refClass->getShortName();
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
return $attrs[0]->getArguments()[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<E> $refClass
|
||||
* @return list<EntityField<E>>
|
||||
*/
|
||||
private function fields(ReflectionClass $refClass): array
|
||||
{
|
||||
$props = $refClass->getProperties();
|
||||
|
||||
return array_map(fn(ReflectionProperty $prop) => $this->fieldFromProperty($prop), $props);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<EntityField<E>> $fields
|
||||
* @return EntityField<E>
|
||||
*/
|
||||
private function idField(array $fields): EntityField
|
||||
{
|
||||
$idFields = array_filter($fields, static fn(EntityField $f) => $f->isIdField);
|
||||
$nrIdFields = count($idFields);
|
||||
|
||||
if ($nrIdFields === 0 || $nrIdFields > 1) {
|
||||
throw new InvalidEntity("Invalid number of id fields found: $nrIdFields, expected: 1");
|
||||
}
|
||||
|
||||
return $idFields[array_key_first($idFields)];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionProperty $prop
|
||||
* @return EntityField<E>
|
||||
*/
|
||||
private function fieldFromProperty(ReflectionProperty $prop): EntityField
|
||||
{
|
||||
$attrs = $prop->getAttributes(Column::class);
|
||||
|
||||
if (count($attrs) === 1) {
|
||||
/** @var string $name */
|
||||
$name = $attrs[0]->getArguments()[0];
|
||||
} else {
|
||||
$name = $prop->getName();
|
||||
}
|
||||
|
||||
$refClass = $prop->getDeclaringClass();
|
||||
$writeAccess = $this->writeAccess($refClass, $prop);
|
||||
$readAccess = $this->readAccess($refClass, $prop);
|
||||
|
||||
return new EntityField(
|
||||
name: $prop->name,
|
||||
fqcn: $this->entity,
|
||||
columnName: $name,
|
||||
isIdField: count($prop->getAttributes(Id::class)) > 0,
|
||||
writeAccess: $writeAccess,
|
||||
readAccess: $readAccess
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<E> $refClass
|
||||
* @param ReflectionProperty $prop
|
||||
* @return FieldAccess
|
||||
*/
|
||||
public function writeAccess(ReflectionClass $refClass, ReflectionProperty $prop): FieldAccess
|
||||
{
|
||||
$wither = FieldAccess::witherName($prop->getName());
|
||||
$setter = FieldAccess::setterName($prop->getName());
|
||||
|
||||
if ($refClass->hasMethod($setter)) {
|
||||
$writeAccess = FieldAccess::GETSET;
|
||||
} elseif ($refClass->hasMethod($wither)) {
|
||||
$returnType = $refClass->getMethod($wither)->getReturnType();
|
||||
if (!$returnType instanceof ReflectionNamedType || $returnType->getName() !== 'self') {
|
||||
throw new InvalidEntity(
|
||||
"Field $refClass->name::$prop->name defines wither writing method with invalid return type, " .
|
||||
"expecting `self`."
|
||||
);
|
||||
}
|
||||
|
||||
$writeAccess = FieldAccess::WITHER;
|
||||
} elseif ($prop->isPublic() && !$prop->isReadOnly()) {
|
||||
$writeAccess = FieldAccess::PUBLIC;
|
||||
} else {
|
||||
throw new InvalidEntity(
|
||||
"Field $refClass->name::$prop->name has no valid writing method. "
|
||||
. "Implement $setter, $wither or make the field public."
|
||||
);
|
||||
}
|
||||
|
||||
return $writeAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<E> $refClass
|
||||
* @param ReflectionProperty $prop
|
||||
* @return FieldAccess
|
||||
*/
|
||||
public function readAccess(ReflectionClass $refClass, ReflectionProperty $prop): FieldAccess
|
||||
{
|
||||
$getter = 'get' . ucfirst($prop->getName());
|
||||
|
||||
if ($refClass->hasMethod($getter)) {
|
||||
$readAccess = FieldAccess::GETSET;
|
||||
} elseif ($prop->isPublic()) {
|
||||
$readAccess = FieldAccess::PUBLIC;
|
||||
} else {
|
||||
throw new InvalidEntity(
|
||||
"Field $refClass->name::$prop->name has no valid reading method. "
|
||||
. "Implement $getter or make the field public."
|
||||
);
|
||||
}
|
||||
|
||||
return $readAccess;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
/**
|
||||
* @template P of object
|
||||
*/
|
||||
readonly class EntityField
|
||||
{
|
||||
/**
|
||||
* @param class-string<P> $fqcn
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $fqcn,
|
||||
public string $columnName,
|
||||
public bool $isIdField,
|
||||
public FieldAccess $writeAccess,
|
||||
public FieldAccess $readAccess,
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
/**
|
||||
* @template E of object
|
||||
*/
|
||||
readonly class EntityResult
|
||||
{
|
||||
/**
|
||||
* @param class-string<E> $classFqcn
|
||||
* @param list<EntityField<object>> $fields
|
||||
* @param EntityField<E> $idField
|
||||
*/
|
||||
public function __construct(
|
||||
public string $classFqcn,
|
||||
public string $tableName,
|
||||
public array $fields,
|
||||
public EntityField $idField
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
enum FieldAccess
|
||||
{
|
||||
case GETSET;
|
||||
case PUBLIC;
|
||||
case WITHER;
|
||||
|
||||
public static function setterName(string $fieldName): string
|
||||
{
|
||||
return 'set' . ucfirst($fieldName);
|
||||
}
|
||||
|
||||
public static function witherName(string $fieldName): string
|
||||
{
|
||||
return 'with' . ucfirst($fieldName);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class InvalidEntity extends RuntimeException
|
||||
{
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
class InvalidRepository extends RuntimeException
|
||||
{
|
||||
public function __construct(string $message = "", ?Throwable $previous = null)
|
||||
{
|
||||
parent::__construct($message, 0, $previous);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
enum ParameterFunction
|
||||
{
|
||||
case STATEMENT_PARAMETER;
|
||||
}
|
||||
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
use Kartierung\Analyze\Validate\ResolutionStrategyFactory;
|
||||
use Kartierung\Attribute\ListResultOf;
|
||||
use Kartierung\Attribute\Query;
|
||||
use ReflectionClass;
|
||||
use ReflectionIntersectionType;
|
||||
use ReflectionMethod;
|
||||
use ReflectionUnionType;
|
||||
|
||||
/**
|
||||
* @template R of object
|
||||
*/
|
||||
readonly class RepositoryAnalyzer
|
||||
{
|
||||
|
||||
public function __construct(
|
||||
private ResolutionStrategyFactory $methodValidator
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param class-string<R> $repositoryClass
|
||||
* @noinspection PhpDocMissingThrowsInspection
|
||||
*/
|
||||
public function analyze(
|
||||
string $repositoryClass
|
||||
): RepositoryResult
|
||||
{
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$refClass = new ReflectionClass($repositoryClass);
|
||||
|
||||
return new RepositoryResult(
|
||||
methods: $this->methods($refClass)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionClass<R> $refClass
|
||||
* @return array<string, RepositoryMethod>
|
||||
*/
|
||||
private function methods(ReflectionClass $refClass): array
|
||||
{
|
||||
$refMethods = $refClass->getMethods();
|
||||
|
||||
$methods = [];
|
||||
foreach ($refMethods as $refMethod) {
|
||||
$query = $this->queryString($refMethod);
|
||||
$returnType = $this->returnType($refMethod);
|
||||
$listType = $this->listType($refMethod);
|
||||
$params = $this->parameters($refMethod);
|
||||
|
||||
$name = $refMethod->name;
|
||||
$resolutionStrategy = $this->methodValidator->forRepositoryMethod(
|
||||
$refClass->name,
|
||||
$name,
|
||||
$query,
|
||||
$returnType,
|
||||
$listType,
|
||||
$params
|
||||
);
|
||||
|
||||
$methods[$refMethod->name] = new RepositoryMethod(
|
||||
query: $query,
|
||||
returnType: $returnType,
|
||||
returnListType: $listType,
|
||||
name: $name,
|
||||
parameters: $params,
|
||||
resolutionStrategy: $resolutionStrategy
|
||||
);
|
||||
}
|
||||
|
||||
return $methods;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $refMethod
|
||||
* @return string|null
|
||||
*/
|
||||
private function queryString(ReflectionMethod $refMethod): ?string
|
||||
{
|
||||
$queryAttr = $refMethod->getAttributes(Query::class);
|
||||
|
||||
$query = null;
|
||||
if (count($queryAttr) === 1) {
|
||||
/** @var Query $queryInst */
|
||||
$queryInst = $queryAttr[0]->newInstance();
|
||||
$query = $queryInst->query;
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $refMethod
|
||||
* @return class-string<object>|string
|
||||
*/
|
||||
private function returnType(ReflectionMethod $refMethod): string
|
||||
{
|
||||
$returnType = $refMethod->getReturnType();
|
||||
|
||||
if ($returnType instanceof ReflectionUnionType
|
||||
|| $returnType instanceof ReflectionIntersectionType
|
||||
|| $returnType === null) {
|
||||
throw new InvalidRepository(
|
||||
"Return type of $refMethod->class::$refMethod->name has an invalid return type. "
|
||||
. 'Simple type expected. Unions, Intersections or no type are not simple.'
|
||||
);
|
||||
}
|
||||
|
||||
/** @var class-string<object>|string */
|
||||
return $returnType->__toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $refMethod
|
||||
* @return class-string|null
|
||||
*/
|
||||
private function listType(ReflectionMethod $refMethod): ?string
|
||||
{
|
||||
$listResultOfAttr = $refMethod->getAttributes(ListResultOf::class);
|
||||
|
||||
$listType = null;
|
||||
if (count($listResultOfAttr) === 1) {
|
||||
/** @var ListResultOf $inst */
|
||||
$inst = $listResultOfAttr[0]->newInstance();
|
||||
$listType = $inst->entitiyFqcn;
|
||||
|
||||
if (!class_exists($listType)) {
|
||||
throw new InvalidRepository(
|
||||
"ListResultOf parameter of $refMethod->class::$refMethod->name does not map to a class. "
|
||||
. 'Fqcn of entity class expected.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $listType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ReflectionMethod $refMethod
|
||||
* @return list<RepositoryMethodParameter>
|
||||
*/
|
||||
private function parameters(ReflectionMethod $refMethod): array
|
||||
{
|
||||
$params = [];
|
||||
foreach ($refMethod->getParameters() as $param) {
|
||||
$type = $param->getType();
|
||||
|
||||
if ($type instanceof ReflectionUnionType
|
||||
|| $type instanceof ReflectionIntersectionType
|
||||
|| $type === null) {
|
||||
throw new InvalidRepository(
|
||||
"Type of $refMethod->class::$refMethod->name::$param->name has an invalid type. "
|
||||
. 'Simple type expected. Unions, Intersections or no type are not simple.'
|
||||
);
|
||||
}
|
||||
|
||||
$params[] = new RepositoryMethodParameter(
|
||||
name: $param->getName(),
|
||||
type: $type->__toString(),
|
||||
function: ParameterFunction::STATEMENT_PARAMETER
|
||||
);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
use Kartierung\Repository\ResolutionStrategy\ResolutionStrategy;
|
||||
|
||||
readonly class RepositoryMethod
|
||||
{
|
||||
/**
|
||||
* @param class-string|string $returnType
|
||||
* @param list<RepositoryMethodParameter> $parameters
|
||||
*/
|
||||
public function __construct(
|
||||
public ?string $query,
|
||||
public string $returnType,
|
||||
public ?string $returnListType,
|
||||
public string $name,
|
||||
public array $parameters,
|
||||
public ResolutionStrategy $resolutionStrategy
|
||||
) {}
|
||||
|
||||
public function returnsNullable(): bool
|
||||
{
|
||||
return str_starts_with($this->returnType, '?');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
readonly class RepositoryMethodParameter
|
||||
{
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $type,
|
||||
public ParameterFunction $function
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
readonly class RepositoryResult
|
||||
{
|
||||
/**
|
||||
* @param array<string, RepositoryMethod> $methods
|
||||
*/
|
||||
public function __construct(
|
||||
public array $methods
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze\Validate;
|
||||
|
||||
use Kartierung\Analyze\InvalidRepository;
|
||||
use Kartierung\Repository\ResolutionStrategy\DeleteEntity;
|
||||
use Kartierung\Repository\ResolutionStrategy\FindAll;
|
||||
use Kartierung\Repository\ResolutionStrategy\FindById;
|
||||
use Kartierung\Repository\ResolutionStrategy\ResolutionStrategy;
|
||||
use Kartierung\Repository\ResolutionStrategy\SaveEntity;
|
||||
use Kartierung\Repository\ResolutionStrategy\UserDefinedQuery;
|
||||
|
||||
class ResolutionStrategyFactory
|
||||
{
|
||||
/**
|
||||
* @param list<mixed> $params
|
||||
* @param class-string|null $listType
|
||||
*/
|
||||
public function forRepositoryMethod(
|
||||
string $repName,
|
||||
string $methodName,
|
||||
?string $query,
|
||||
string $returnType,
|
||||
?string $listType,
|
||||
array $params
|
||||
): ResolutionStrategy
|
||||
{
|
||||
$oneParam = count($params) === 1;
|
||||
$returnsNullable = str_starts_with($returnType, '?');
|
||||
|
||||
return match (true) {
|
||||
$query !== null => new UserDefinedQuery(),
|
||||
|
||||
$methodName === 'save' && $oneParam => new SaveEntity(),
|
||||
$methodName === 'save' => throw new InvalidRepository(
|
||||
"'save' functions expect exactly one parameter. Invalid function found in $repName"
|
||||
),
|
||||
|
||||
$methodName === 'delete' && $oneParam && $returnType === 'int' => new DeleteEntity(),
|
||||
$methodName === 'delete' => throw new InvalidRepository(
|
||||
"'delete' functions expect exactly one parameter and integer return type. " .
|
||||
"Invalid function found in $repName"
|
||||
),
|
||||
|
||||
$methodName === 'findById' && $oneParam && $returnsNullable => new FindById(),
|
||||
$methodName === 'findById' => throw new InvalidRepository(
|
||||
"fnyById functions expect exactly one parameter and nullable return type." .
|
||||
"Invalid function found in $repName"
|
||||
),
|
||||
|
||||
$methodName === 'findAll' && $returnType === 'array' && $listType !== null => new FindAll(),
|
||||
$methodName === 'findAll' => throw new InvalidRepository(
|
||||
"FindAll"
|
||||
),
|
||||
|
||||
default => throw new InvalidRepository(
|
||||
"No suitable strategy for repository function $repName::$methodName can be found."
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
readonly class Column
|
||||
{
|
||||
public function __construct(
|
||||
public string $name = ''
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)]
|
||||
class Id
|
||||
{
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
readonly class ListResultOf
|
||||
{
|
||||
/**
|
||||
* @param class-string $entitiyFqcn
|
||||
*/
|
||||
public function __construct(
|
||||
public string $entitiyFqcn
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
readonly class Query
|
||||
{
|
||||
public function __construct(
|
||||
public string $query
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS)]
|
||||
readonly class Table
|
||||
{
|
||||
public function __construct(
|
||||
public string $name = ''
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository;
|
||||
|
||||
use Kartierung\Analyze\InvalidRepository;
|
||||
use Kartierung\Attribute\Query;
|
||||
use ReflectionClass;
|
||||
|
||||
class Repository
|
||||
{
|
||||
/**
|
||||
* @template R of object
|
||||
* @param class-string<R> $clazz
|
||||
* @return R
|
||||
* @noinspection PhpDocMissingThrowsInspection
|
||||
*/
|
||||
public function ofType(string $clazz): object
|
||||
{
|
||||
/** @noinspection PhpUnhandledExceptionInspection */
|
||||
$refClass = new ReflectionClass($clazz);
|
||||
|
||||
/**
|
||||
* @var R of object
|
||||
* @phpstan-ignore varTag.nativeType
|
||||
*/
|
||||
return new class($refClass) {
|
||||
/**
|
||||
* @param ReflectionClass<R> $refClass
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly ReflectionClass $refClass
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param list<mixed> $arguments
|
||||
* @return mixed
|
||||
*/
|
||||
public function __call(string $name, array $arguments): mixed
|
||||
{
|
||||
$method = $this->refClass->getMethod($name);
|
||||
$queryAttr = $method->getAttributes(Query::class);
|
||||
|
||||
if (count($queryAttr) !== 1) {
|
||||
throw new InvalidRepository(
|
||||
"Called method $name has an invalid number of Query attributes. Expected: 1"
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use Kartierung\Sql\LazyQuery;
|
||||
use PDO;
|
||||
|
||||
class DeleteEntity implements ResolutionStrategy
|
||||
{
|
||||
public function execute(RepositoryMethod $method, array $parameters): LazyQuery
|
||||
{
|
||||
return new LazyQuery(
|
||||
function (PDO $pdo) {
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\EntityAnalyzer;
|
||||
use Kartierung\Analyze\EntityField;
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use Kartierung\Sql\LazyQuery;
|
||||
use Kartierung\Sql\DataToEntityMapper;
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
|
||||
readonly class FindAll implements ResolutionStrategy
|
||||
{
|
||||
public function execute(RepositoryMethod $method, array $parameters): LazyQuery
|
||||
{
|
||||
return new LazyQuery(function (PDO $pdo) use ($method): array {
|
||||
/** @var class-string $entityFqcn */
|
||||
$entityFqcn = $method->returnListType;
|
||||
$entityResult = (new EntityAnalyzer($entityFqcn))->analyze();
|
||||
$mapper = new DataToEntityMapper($entityResult);
|
||||
|
||||
$fields = implode(
|
||||
', ',
|
||||
array_map(
|
||||
static fn(EntityField $e) => $e->columnName, $entityResult->fields
|
||||
)
|
||||
);
|
||||
$sql = "
|
||||
SELECT $fields
|
||||
FROM {$entityResult->tableName}
|
||||
";
|
||||
|
||||
/** @var PDOStatement $stmt */
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute();
|
||||
|
||||
$statementResult = [];
|
||||
|
||||
while ($row = $stmt->fetch(
|
||||
mode: PDO::FETCH_ASSOC
|
||||
)) {
|
||||
/** @var array<string, mixed> $row */
|
||||
$entity = $mapper->toEntity($row);
|
||||
$statementResult[] = $entity;
|
||||
}
|
||||
|
||||
return $statementResult;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use Kartierung\Sql\LazyQuery;
|
||||
use PDO;
|
||||
|
||||
class FindById implements ResolutionStrategy
|
||||
{
|
||||
public function execute(RepositoryMethod $method, array $parameters): LazyQuery
|
||||
{
|
||||
return new LazyQuery(
|
||||
function (PDO $pdo) {
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use Kartierung\Sql\LazyQuery;
|
||||
|
||||
interface ResolutionStrategy
|
||||
{
|
||||
/**
|
||||
* @param RepositoryMethod $method
|
||||
* @param array<string, mixed> $parameters
|
||||
* @return LazyQuery
|
||||
*/
|
||||
public function execute(RepositoryMethod $method, array $parameters): LazyQuery;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use Kartierung\Sql\LazyQuery;
|
||||
use PDO;
|
||||
|
||||
class SaveEntity implements ResolutionStrategy
|
||||
{
|
||||
public function execute(RepositoryMethod $method, array $parameters): LazyQuery
|
||||
{
|
||||
return new LazyQuery(
|
||||
function (PDO $pdo) {
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use Kartierung\Sql\LazyQuery;
|
||||
use PDO;
|
||||
|
||||
class UserDefinedQuery implements ResolutionStrategy
|
||||
{
|
||||
public function execute(RepositoryMethod $method, array $parameters): LazyQuery
|
||||
{
|
||||
return new LazyQuery(
|
||||
function (PDO $pdo) {
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Sql;
|
||||
|
||||
use Kartierung\Analyze\EntityField;
|
||||
use Kartierung\Analyze\EntityResult;
|
||||
use Kartierung\Analyze\FieldAccess;
|
||||
|
||||
/**
|
||||
* @template E of object
|
||||
*/
|
||||
readonly class DataToEntityMapper
|
||||
{
|
||||
/**
|
||||
* @param EntityResult<E> $entityResult
|
||||
*/
|
||||
public function __construct(
|
||||
private EntityResult $entityResult
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return E
|
||||
*/
|
||||
public function toEntity(array $row): object
|
||||
{
|
||||
/** @var E $entity */
|
||||
$entity = new $this->entityResult->classFqcn();
|
||||
|
||||
foreach ($this->entityResult->fields as $field) {
|
||||
/** @var object $dbValue */
|
||||
$dbValue = $row[$field->columnName];
|
||||
|
||||
match ($field->writeAccess) {
|
||||
FieldAccess::GETSET => $this->getsetWrite($entity, $field, $dbValue),
|
||||
FieldAccess::PUBLIC => $this->publicWrite($entity, $field, $dbValue),
|
||||
FieldAccess::WITHER => $entity = $this->witherWrite($entity, $field, $dbValue),
|
||||
};
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param E $entity
|
||||
* @param EntityField<object> $field
|
||||
* @param object $value
|
||||
*/
|
||||
private function getsetWrite(object $entity, EntityField $field, mixed $value): void
|
||||
{
|
||||
$setter = FieldAccess::setterName($field->name);
|
||||
$entity->$setter($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param E $entity
|
||||
* @param EntityField<object> $field
|
||||
* @param object $value
|
||||
*/
|
||||
private function publicWrite(object $entity, EntityField $field, mixed $value): void
|
||||
{
|
||||
$entity->{$field->name} = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param E $entity
|
||||
* @param EntityField<object> $field
|
||||
* @param object $value
|
||||
* @return E
|
||||
*/
|
||||
private function witherWrite(object $entity, EntityField $field, mixed $value): object
|
||||
{
|
||||
$wither = FieldAccess::witherName($field->name);
|
||||
/** @var E */
|
||||
return $entity->$wither($value);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Sql;
|
||||
|
||||
use Closure;
|
||||
use PDO;
|
||||
|
||||
readonly class LazyQuery
|
||||
{
|
||||
/**
|
||||
* @param (Closure(PDO): mixed) $execute
|
||||
*/
|
||||
public function __construct(
|
||||
public Closure $execute
|
||||
) {}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Analyze;
|
||||
|
||||
use Kartierung\EntityWithoutId;
|
||||
use Kartierung\EntityWithTwoIds;
|
||||
use Kartierung\FullEntity;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EntityAnalyzerTest extends TestCase
|
||||
{
|
||||
public function testTableNameIsAnalyzed(): void
|
||||
{
|
||||
// Given
|
||||
$entity = FullEntity::class;
|
||||
|
||||
// When
|
||||
$result = (new EntityAnalyzer(FullEntity::class))->analyze();
|
||||
|
||||
// Then
|
||||
$this->assertEquals('simple-table', $result->tableName);
|
||||
$this->assertEquals(FullEntity::class, $result->classFqcn);
|
||||
}
|
||||
|
||||
public function testFieldsAreAnalyzed(): void
|
||||
{
|
||||
// Given
|
||||
$entity = FullEntity::class;
|
||||
|
||||
// When
|
||||
$result = (new EntityAnalyzer($entity))->analyze();
|
||||
|
||||
// Then
|
||||
$this->assertContainsEquals(
|
||||
new EntityField(
|
||||
name: 'stringField',
|
||||
fqcn: $entity,
|
||||
columnName: 'stringField',
|
||||
isIdField: false,
|
||||
writeAccess: FieldAccess::WITHER,
|
||||
readAccess: FieldAccess::PUBLIC
|
||||
),
|
||||
$result->fields
|
||||
);
|
||||
$this->assertContainsEquals(
|
||||
new EntityField(
|
||||
name: 'intField',
|
||||
fqcn: $entity,
|
||||
columnName: 'int-field',
|
||||
isIdField: false,
|
||||
writeAccess: FieldAccess::PUBLIC,
|
||||
readAccess: FieldAccess::GETSET
|
||||
),
|
||||
$result->fields
|
||||
);
|
||||
$this->assertContainsEquals(
|
||||
new EntityField(
|
||||
name: 'idField',
|
||||
fqcn: $entity,
|
||||
columnName: 'renamed-id-field',
|
||||
isIdField: true,
|
||||
writeAccess: FieldAccess::GETSET,
|
||||
readAccess: FieldAccess::PUBLIC
|
||||
),
|
||||
$result->fields
|
||||
);
|
||||
}
|
||||
|
||||
public function testIdFieldIsFound(): void
|
||||
{
|
||||
// Given
|
||||
$entity = FullEntity::class;
|
||||
|
||||
// When
|
||||
$result = (new EntityAnalyzer($entity))->analyze();
|
||||
|
||||
// Then
|
||||
$this->assertEquals(
|
||||
new EntityField(
|
||||
name: 'idField',
|
||||
fqcn: $entity,
|
||||
columnName: 'renamed-id-field',
|
||||
isIdField: true,
|
||||
writeAccess: FieldAccess::GETSET,
|
||||
readAccess: FieldAccess::PUBLIC
|
||||
),
|
||||
$result->idField
|
||||
);
|
||||
}
|
||||
|
||||
public function testNoIdIsInvalid(): void
|
||||
{
|
||||
// Given
|
||||
$entity = EntityWithoutId::class;
|
||||
|
||||
// Then
|
||||
$this->expectException(InvalidEntity::class);
|
||||
|
||||
// When
|
||||
(new EntityAnalyzer($entity))->analyze();
|
||||
}
|
||||
|
||||
public function testMultipleIdsIsInvalid(): void
|
||||
{
|
||||
// Given
|
||||
$entity = EntityWithTwoIds::class;
|
||||
|
||||
// Then
|
||||
$this->expectException(InvalidEntity::class);
|
||||
|
||||
// When
|
||||
(new EntityAnalyzer($entity))->analyze();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Analyze;
|
||||
|
||||
use Kartierung\Analyze\InvalidRepository;
|
||||
use Kartierung\Analyze\RepositoryAnalyzer;
|
||||
use Kartierung\Analyze\RepositoryMethodParameter;
|
||||
use Kartierung\Analyze\Validate\ResolutionStrategyFactory;
|
||||
use Kartierung\FullEntity;
|
||||
use Kartierung\InvalidListResultOfRepository;
|
||||
use Kartierung\Repository;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RepositoryAnalyzerTest extends TestCase
|
||||
{
|
||||
public function testQueryAttributeIsRead(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$this->assertEquals('SELECT something', $result->methods['fetchWithQuery']->query);
|
||||
}
|
||||
|
||||
public function testEntityReturnTypeIsAnalyzed(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$this->assertEquals(FullEntity::class, $result->methods['fetchWithQuery']->returnType);
|
||||
}
|
||||
|
||||
public function testArrayReturnTypeIsRead(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$this->assertEquals('array', $result->methods['fetchArray']->returnType);
|
||||
}
|
||||
|
||||
public function testVoidReturnTypeIsRead(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$this->assertEquals('void', $result->methods['insert']->returnType);
|
||||
}
|
||||
|
||||
public function testListResultOfIsRead(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$this->assertEquals(FullEntity::class, $result->methods['fetchArray']->returnListType);
|
||||
}
|
||||
|
||||
public function testInvalidListResultOfIsReported(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// Then
|
||||
$this->expectException(InvalidRepository::class);
|
||||
|
||||
// When
|
||||
$analyzer->analyze(InvalidListResultOfRepository::class);
|
||||
}
|
||||
|
||||
public function testUnionReturnTypeIsReported(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// Then
|
||||
$this->expectException(InvalidRepository::class);
|
||||
|
||||
// When
|
||||
$analyzer->analyze(InvalidRepository::class);
|
||||
}
|
||||
|
||||
public function testParametersAreAnalyzed(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$method = $result->methods['doSomethingWithParameters'];
|
||||
/** @var array<RepositoryMethodParameter> $parameters */
|
||||
$parameters = $method->parameters;
|
||||
|
||||
$this->assertCount(2, $parameters);
|
||||
$this->assertEquals('stringParam', $parameters[0]->name);
|
||||
$this->assertEquals('string', $parameters[0]->type);
|
||||
|
||||
$this->assertEquals('intParam', $parameters[1]->name);
|
||||
$this->assertEquals('int', $parameters[1]->type);
|
||||
}
|
||||
|
||||
public function testNullableReturnType(): void
|
||||
{
|
||||
// Given
|
||||
$analyzer = new RepositoryAnalyzer(new ResolutionStrategyFactory());
|
||||
|
||||
// When
|
||||
$result = $analyzer->analyze(Repository::class);
|
||||
|
||||
// Then
|
||||
$method = $result->methods['findById'];
|
||||
$this->assertEquals('?Kartierung\FullEntity', $method->returnType);
|
||||
$this->assertTrue($method->returnsNullable());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Analyze\Validate;
|
||||
|
||||
use Kartierung\Analyze\Validate\ResolutionStrategyFactory;
|
||||
use Kartierung\FullEntity;
|
||||
use Kartierung\Repository\ResolutionStrategy\DeleteEntity;
|
||||
use Kartierung\Repository\ResolutionStrategy\FindAll;
|
||||
use Kartierung\Repository\ResolutionStrategy\FindById;
|
||||
use Kartierung\Repository\ResolutionStrategy\SaveEntity;
|
||||
use Kartierung\Repository\ResolutionStrategy\UserDefinedQuery;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class RepositoryValidatorTest extends TestCase
|
||||
{
|
||||
public static function validFunctionsProvider(): array
|
||||
{
|
||||
return [
|
||||
['R', 'save', null, '', null, [1], SaveEntity::class],
|
||||
['R', 'delete', null, 'int', null, [1], DeleteEntity::class],
|
||||
['R', 'findById', null, '?int', null, [1], FindById::class],
|
||||
['R', 'findAll', null, 'array', FullEntity::class, [], FindAll::class],
|
||||
['R', 'something', 'SELECT', 'int', null, [], UserDefinedQuery::class]
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('validFunctionsProvider')]
|
||||
public function testValidFunctions(
|
||||
string $repName,
|
||||
string $name,
|
||||
?string $query,
|
||||
string $returnType,
|
||||
?string $listType,
|
||||
array $params,
|
||||
string $strategyClass
|
||||
): void
|
||||
{
|
||||
// When
|
||||
$function = (new ResolutionStrategyFactory())->forRepositoryMethod(
|
||||
$repName,
|
||||
$name,
|
||||
$query,
|
||||
$returnType,
|
||||
$listType,
|
||||
$params
|
||||
);
|
||||
|
||||
// Then
|
||||
$this->assertInstanceOf($strategyClass, $function);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung;
|
||||
|
||||
use Kartierung\Attribute\Column;
|
||||
use Kartierung\Attribute\Id;
|
||||
use Kartierung\Attribute\Table;
|
||||
|
||||
class EntityWithTwoIds
|
||||
{
|
||||
#[Id]
|
||||
public string $idOne;
|
||||
|
||||
#[Id]
|
||||
public string $idTwo;
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung;
|
||||
|
||||
use Kartierung\Attribute\Column;
|
||||
use Kartierung\Attribute\Id;
|
||||
use Kartierung\Attribute\Table;
|
||||
|
||||
class EntityWithoutId
|
||||
{
|
||||
public string $stringField;
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung;
|
||||
|
||||
use Kartierung\Attribute\Column;
|
||||
use Kartierung\Attribute\Id;
|
||||
use Kartierung\Attribute\Table;
|
||||
|
||||
#[Table("simple-table")]
|
||||
class FullEntity
|
||||
{
|
||||
public string $stringField;
|
||||
#[Column('int-field')]
|
||||
public int $intField;
|
||||
#[Id]
|
||||
#[Column('renamed-id-field')]
|
||||
public int $idField;
|
||||
|
||||
public function setIdField(int $idField): void
|
||||
{
|
||||
$this->idField = $idField;
|
||||
}
|
||||
|
||||
public function getIntField(): int
|
||||
{
|
||||
return $this->intField;
|
||||
}
|
||||
|
||||
public function withStringField(string $stringField): self
|
||||
{
|
||||
$this->stringField = $stringField;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung;
|
||||
|
||||
use Kartierung\Attribute\ListResultOf;
|
||||
|
||||
interface InvalidListResultOfRepository
|
||||
{
|
||||
#[ListResultOf('void')]
|
||||
public function invalidListResultOf(): array;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung;
|
||||
|
||||
use Kartierung\Attribute\ListResultOf;
|
||||
use Kartierung\Attribute\Query;
|
||||
|
||||
interface Repository
|
||||
{
|
||||
#[Query("SELECT something")]
|
||||
public function fetchWithQuery(): FullEntity;
|
||||
|
||||
#[Query("SELECT something")]
|
||||
#[ListResultOf(FullEntity::class)]
|
||||
public function fetchArray(): array;
|
||||
|
||||
#[Query("SELECT something")]
|
||||
public function insert(): void;
|
||||
|
||||
#[Query("SELECT something")]
|
||||
public function doSomethingWithParameters(
|
||||
string $stringParam,
|
||||
int $intParam
|
||||
): void;
|
||||
|
||||
public function findById(int $id): ?FullEntity;
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Analyze\RepositoryMethod;
|
||||
use PHPUnit\Framework\Attributes\Group;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FindAllTest extends TestCase
|
||||
{
|
||||
|
||||
private \PDO $pdo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$tmpfile = tempnam(sys_get_temp_dir(), 'kartierung.sqlite');
|
||||
$this->pdo = new \PDO("sqlite:/$tmpfile");
|
||||
|
||||
$this->pdo->exec(
|
||||
"
|
||||
CREATE TABLE SimpleEntity (
|
||||
stringcol VARCHAR(250),
|
||||
intcol INT
|
||||
);
|
||||
INSERT INTO SimpleEntity(stringcol, intcol)
|
||||
VALUES ('dings', 1),
|
||||
('bumms', 2);
|
||||
"
|
||||
);
|
||||
}
|
||||
|
||||
#[Group('integration')]
|
||||
public function testSelectsData(): void
|
||||
{
|
||||
// Given
|
||||
$query = (new FindAll())->execute(
|
||||
method: new RepositoryMethod(
|
||||
query: '',
|
||||
returnType: '',
|
||||
returnListType: SimpleEntity::class,
|
||||
name: '',
|
||||
parameters: [],
|
||||
resolutionStrategy: new FindAll()
|
||||
),
|
||||
parameters: []
|
||||
);
|
||||
|
||||
// When
|
||||
$result = $query->execute->__invoke($this->pdo);
|
||||
|
||||
// Then
|
||||
$this->assertNotEmpty($result);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Repository\ResolutionStrategy;
|
||||
|
||||
use Kartierung\Attribute\Id;
|
||||
|
||||
class SimpleEntity
|
||||
{
|
||||
public string $stringcol;
|
||||
|
||||
#[Id]
|
||||
public int $intcol;
|
||||
|
||||
public function withStringCol(string $value): self
|
||||
{
|
||||
$new = new self();
|
||||
$new->stringcol = $value;
|
||||
return $new;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Sql;
|
||||
|
||||
use Kartierung\Analyze\EntityField;
|
||||
use Kartierung\Analyze\EntityResult;
|
||||
use Kartierung\Analyze\FieldAccess;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DateToEntityMapperTest extends TestCase
|
||||
{
|
||||
public function testMapsSetter(): void
|
||||
{
|
||||
// Given
|
||||
$mapper = new DataToEntityMapper(
|
||||
entityResult: new EntityResult(
|
||||
classFqcn: EntityWithThreeAccessMethods::class,
|
||||
tableName: '',
|
||||
fields: [
|
||||
new EntityField(
|
||||
name: 'witherVal',
|
||||
fqcn: 'int',
|
||||
columnName: 'witherVal',
|
||||
isIdField: false,
|
||||
writeAccess: FieldAccess::WITHER,
|
||||
readAccess: FieldAccess::WITHER
|
||||
),
|
||||
new EntityField(
|
||||
name: 'publicVal',
|
||||
fqcn: 'int',
|
||||
columnName: 'publicVal',
|
||||
isIdField: false,
|
||||
writeAccess: FieldAccess::PUBLIC,
|
||||
readAccess: FieldAccess::PUBLIC
|
||||
),
|
||||
new EntityField(
|
||||
name: 'setterVal',
|
||||
fqcn: 'int',
|
||||
columnName: 'setterVal',
|
||||
isIdField: false,
|
||||
writeAccess: FieldAccess::GETSET,
|
||||
readAccess: FieldAccess::GETSET
|
||||
)
|
||||
],
|
||||
idField: new EntityField(
|
||||
name: '',
|
||||
fqcn: '',
|
||||
columnName: '',
|
||||
isIdField: false,
|
||||
writeAccess: FieldAccess::GETSET,
|
||||
readAccess: FieldAccess::GETSET
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$row = [
|
||||
'witherVal' => 1,
|
||||
'publicVal' => 2,
|
||||
'setterVal' => 3
|
||||
];
|
||||
|
||||
// When
|
||||
$entity = $mapper->toEntity($row);
|
||||
|
||||
// Then
|
||||
$this->assertInstanceOf(EntityWithThreeAccessMethods::class, $entity);
|
||||
$this->assertEquals(1, $entity->getWitherVal());
|
||||
$this->assertEquals(2, $entity->publicVal);
|
||||
$this->assertEquals(3, $entity->getSetterVal());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung\Sql;
|
||||
|
||||
class EntityWithThreeAccessMethods
|
||||
{
|
||||
public int $publicVal;
|
||||
|
||||
private int $setterVal;
|
||||
|
||||
private int $witherVal;
|
||||
|
||||
public function getSetterVal(): int
|
||||
{
|
||||
return $this->setterVal;
|
||||
}
|
||||
|
||||
public function setSetterVal(int $value): void
|
||||
{
|
||||
$this->setterVal = $value;
|
||||
}
|
||||
|
||||
public function withWitherVal(int $value): self
|
||||
{
|
||||
$this->witherVal = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWitherVal(): int
|
||||
{
|
||||
return $this->witherVal;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Kartierung;
|
||||
|
||||
interface UnionReturnTypeRepository
|
||||
{
|
||||
public function returnsUnion(): int|string;
|
||||
}
|
||||
Loading…
Reference in New Issue