more precise type analysis: add distinction between primitive and class types

master
Josha von Gizycki 3 weeks ago
parent f2047c3917
commit b10dfdf411

@ -14,21 +14,24 @@ return static function (DeptracConfig $config): void {
->paths('./src') ->paths('./src')
->excludeFiles('#.*test.*#') ->excludeFiles('#.*test.*#')
->layers( ->layers(
$analyzePublic = Layer::withName('AnalyzePublic')->collectors( $analyzePublic = Layer::withName('AnalyzePublic')
ClassLikeConfig::create('Kartierung\\Analyze\\Exception\\.+'), ->collectors(
ClassLikeConfig::create('Kartierung\\Analyze\\Result\\.+') ClassLikeConfig::create('Kartierung\\Analyze\\Exception\\.+'),
), ClassLikeConfig::create('Kartierung\\Analyze\\Result\\.+')
$analyze = Layer::withName('Analyze')->collectors( ),
BoolConfig::create( $analyze = Layer::withName('Analyze')
must: [ClassLikeConfig::create('Kartierung\\Analyze\\.+')], ->collectors(
mustNot: [ BoolConfig::create(
LayerConfig::create($analyzePublic) must: [ClassLikeConfig::create('Kartierung\\Analyze\\.+')],
] mustNot: [
) LayerConfig::create($analyzePublic)
), ]
$attribute = Layer::withName('Attribute')->collectors( )
ClassLikeConfig::create('Kartierung\\Attribute\\.*'), ),
), $attribute = Layer::withName('Attribute')
->collectors(
ClassLikeConfig::create('Kartierung\\Attribute\\.*'),
),
) )
->rulesets( ->rulesets(
Ruleset::forLayer($analyze) Ruleset::forLayer($analyze)

@ -5,14 +5,20 @@ declare(strict_types=1);
namespace Kartierung\Analyze; namespace Kartierung\Analyze;
use Kartierung\Analyze\Exception\InvalidEntity; use Kartierung\Analyze\Exception\InvalidEntity;
use Kartierung\Analyze\Exception\InvalidRepository;
use Kartierung\Analyze\Result\ClassType;
use Kartierung\Analyze\Result\EntityField; use Kartierung\Analyze\Result\EntityField;
use Kartierung\Analyze\Result\EntityResult; use Kartierung\Analyze\Result\EntityResult;
use Kartierung\Analyze\Result\FieldAccess; use Kartierung\Analyze\Result\FieldAccess;
use Kartierung\Analyze\Result\PrimitiveType;
use Kartierung\Attribute\Column; use Kartierung\Attribute\Column;
use Kartierung\Attribute\Id; use Kartierung\Attribute\Id;
use Kartierung\Attribute\Table; use Kartierung\Attribute\Table;
use ReflectionClass; use ReflectionClass;
use ReflectionIntersectionType;
use ReflectionNamedType;
use ReflectionParameter; use ReflectionParameter;
use ReflectionUnionType;
/** /**
* @template E of object * @template E of object
@ -61,7 +67,7 @@ readonly class EntityAnalyzer
/** /**
* @param ReflectionClass<E> $refClass * @param ReflectionClass<E> $refClass
* @return list<EntityField<E>> * @return list<EntityField>
*/ */
private function fields(ReflectionClass $refClass): array private function fields(ReflectionClass $refClass): array
{ {
@ -79,8 +85,8 @@ readonly class EntityAnalyzer
} }
/** /**
* @param list<EntityField<E>> $fields * @param list<EntityField> $fields
* @return EntityField<E> * @return EntityField
*/ */
private function idField(array $fields): EntityField private function idField(array $fields): EntityField
{ {
@ -97,7 +103,7 @@ readonly class EntityAnalyzer
} }
/** /**
* @return EntityField<E> * @return EntityField
*/ */
private function fieldFromParameter(ReflectionParameter $param): EntityField private function fieldFromParameter(ReflectionParameter $param): EntityField
{ {
@ -117,18 +123,47 @@ readonly class EntityAnalyzer
return new EntityField( return new EntityField(
name: $param->name, name: $param->name,
fqcn: $this->entity, type: $this->parameterType($param),
columnName: $name, columnName: $name,
isIdField: count($param->getAttributes(Id::class)) > 0, isIdField: count($param->getAttributes(Id::class)) > 0,
readAccess: $readAccess readAccess: $readAccess
); );
} }
private function parameterType(ReflectionParameter $param): PrimitiveType|ClassType
{
if (!$param->hasType()) {
throw new InvalidEntity(
"Entity parameter $this->entity::$param->name has no type declaration. Add one."
);
}
$type = $param->getType();
if (!$type instanceof ReflectionNamedType) {
throw new InvalidRepository(
"Entity parameter $this->entity::$param->name has an invalid return type. "
. 'Simple type expected. Unions, Intersections or no type are not simple.'
);
}
// workaround for phpstan
/** @var class-string<object> $classType */
$classType = $type->getName();
return match ($type->getName()) {
"string" => PrimitiveType::STRING,
"bool" => PrimitiveType::BOOLEAN,
"int" => PrimitiveType::INTEGER,
"float" => PrimitiveType::FLOAT,
default => new ClassType($classType)
};
}
/** /**
* @param ReflectionClass<E> $refClass * @param ReflectionClass<E> $refClass
* @return FieldAccess * @return FieldAccess
*/ */
public function readAccess(ReflectionClass $refClass, ReflectionParameter $param): FieldAccess private function readAccess(ReflectionClass $refClass, ReflectionParameter $param): FieldAccess
{ {
$getter = 'get' . ucfirst($param->getName()); $getter = 'get' . ucfirst($param->getName());

@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
namespace Kartierung\Analyze\Result;
readonly class ClassType
{
/**
* @param class-string<object> $fqcn
*/
public function __construct(
public string $fqcn
) {}
}

@ -4,17 +4,11 @@ declare(strict_types=1);
namespace Kartierung\Analyze\Result; namespace Kartierung\Analyze\Result;
/**
* @template P of object
*/
readonly class EntityField readonly class EntityField
{ {
/**
* @param class-string<P> $fqcn
*/
public function __construct( public function __construct(
public string $name, public string $name,
public string $fqcn, public PrimitiveType|ClassType $type,
public string $columnName, public string $columnName,
public bool $isIdField, public bool $isIdField,
public FieldAccess $readAccess, public FieldAccess $readAccess,

@ -11,8 +11,9 @@ readonly class EntityResult
{ {
/** /**
* @param class-string<E> $classFqcn * @param class-string<E> $classFqcn
* @param EntityField $fields * @param string $tableName
* @param EntityField<E> $idField * @param list<EntityField> $fields
* @param EntityField $idField
*/ */
public function __construct( public function __construct(
public string $classFqcn, public string $classFqcn,

@ -0,0 +1,13 @@
<?php
declare(strict_types=1);
namespace Kartierung\Analyze\Result;
enum PrimitiveType
{
case INTEGER;
case BOOLEAN;
case FLOAT;
case STRING;
}

@ -7,6 +7,7 @@ namespace Kartierung\Analyze;
use Kartierung\Analyze\Exception\InvalidEntity; use Kartierung\Analyze\Exception\InvalidEntity;
use Kartierung\Analyze\Result\EntityField; use Kartierung\Analyze\Result\EntityField;
use Kartierung\Analyze\Result\FieldAccess; use Kartierung\Analyze\Result\FieldAccess;
use Kartierung\Analyze\Result\PrimitiveType;
use Kartierung\FullEntity; use Kartierung\FullEntity;
use Kartierung\PropertyPropagatedEntity; use Kartierung\PropertyPropagatedEntity;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -38,7 +39,7 @@ class EntityAnalyzerTest extends TestCase
$this->assertContainsEquals( $this->assertContainsEquals(
new EntityField( new EntityField(
name: 'intField', name: 'intField',
fqcn: $entity, type: PrimitiveType::INTEGER,
columnName: 'int-field', columnName: 'int-field',
isIdField: false, isIdField: false,
readAccess: FieldAccess::GETTER readAccess: FieldAccess::GETTER
@ -48,7 +49,7 @@ class EntityAnalyzerTest extends TestCase
$this->assertContainsEquals( $this->assertContainsEquals(
new EntityField( new EntityField(
name: 'idField', name: 'idField',
fqcn: $entity, type: PrimitiveType::INTEGER,
columnName: 'renamed-id-field', columnName: 'renamed-id-field',
isIdField: true, isIdField: true,
readAccess: FieldAccess::PUBLIC readAccess: FieldAccess::PUBLIC
@ -69,7 +70,7 @@ class EntityAnalyzerTest extends TestCase
$this->assertEquals( $this->assertEquals(
new EntityField( new EntityField(
name: 'idField', name: 'idField',
fqcn: $entity, type: PrimitiveType::INTEGER,
columnName: 'renamed-id-field', columnName: 'renamed-id-field',
isIdField: true, isIdField: true,
readAccess: FieldAccess::PUBLIC readAccess: FieldAccess::PUBLIC

@ -10,6 +10,6 @@ class EntityWithoutTableAttribute
{ {
public function __construct( public function __construct(
#[Id] #[Id]
public $id public int $id
) {} ) {}
} }

@ -7,6 +7,7 @@ namespace Kartierung\Sql;
use Kartierung\Analyze\Result\EntityField; use Kartierung\Analyze\Result\EntityField;
use Kartierung\Analyze\Result\EntityResult; use Kartierung\Analyze\Result\EntityResult;
use Kartierung\Analyze\Result\FieldAccess; use Kartierung\Analyze\Result\FieldAccess;
use Kartierung\Analyze\Result\PrimitiveType;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class DataToEntityMapperTest extends TestCase class DataToEntityMapperTest extends TestCase
@ -21,14 +22,14 @@ class DataToEntityMapperTest extends TestCase
fields: [ fields: [
new EntityField( new EntityField(
name: 'publicVal', name: 'publicVal',
fqcn: 'int', type: PrimitiveType::INTEGER,
columnName: 'public_val', columnName: 'public_val',
isIdField: false, isIdField: false,
readAccess: FieldAccess::PUBLIC readAccess: FieldAccess::PUBLIC
), ),
new EntityField( new EntityField(
name: 'setterVal', name: 'setterVal',
fqcn: 'int', type: PrimitiveType::INTEGER,
columnName: 'setter_val', columnName: 'setter_val',
isIdField: false, isIdField: false,
readAccess: FieldAccess::GETTER readAccess: FieldAccess::GETTER
@ -36,7 +37,7 @@ class DataToEntityMapperTest extends TestCase
], ],
idField: new EntityField( idField: new EntityField(
name: '', name: '',
fqcn: '', type: PrimitiveType::INTEGER,
columnName: '', columnName: '',
isIdField: false, isIdField: false,
readAccess: FieldAccess::GETTER readAccess: FieldAccess::GETTER

Loading…
Cancel
Save