Skip to content

Commit f44a496

Browse files
authored
Merge pull request #136 from TomHAnderson/feature/metadata-array-object
Feature/metadata array object
2 parents 6f18717 + 69c77b8 commit f44a496

14 files changed

Lines changed: 252 additions & 32 deletions

src/Attribute/Entity.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ final class Entity
1717
/** @var bool Extract by value: true, or by reference: false */
1818
private bool $byValue;
1919

20+
/**
21+
* When this value is 0 the limit falls back to the global config limit
22+
*
23+
* @var int A hard limit for all queries on this entity
24+
*/
25+
private int $limit;
26+
2027
/** @var string|null Documentation for the entity within GraphQL */
2128
private string|null $description = null;
2229

@@ -37,6 +44,7 @@ final class Entity
3744
public function __construct(
3845
string $group = 'default',
3946
bool $byValue = true,
47+
int $limit = 0,
4048
string|null $description = null,
4149
private string|null $typeName = null,
4250
array $filters = [],
@@ -46,6 +54,7 @@ public function __construct(
4654
) {
4755
$this->group = $group;
4856
$this->byValue = $byValue;
57+
$this->limit = $limit;
4958
$this->description = $description;
5059
$this->filters = $filters;
5160
}
@@ -60,6 +69,11 @@ public function getByValue(): bool
6069
return $this->byValue;
6170
}
6271

72+
public function getLimit(): int
73+
{
74+
return $this->limit;
75+
}
76+
6377
public function getDescription(): string|null
6478
{
6579
return $this->description;

src/Event/BuildMetadata.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace ApiSkeletons\Doctrine\GraphQL\Event;
6+
7+
use ArrayObject;
8+
use League\Event\HasEventName;
9+
10+
class BuildMetadata implements
11+
HasEventName
12+
{
13+
public function __construct(
14+
protected ArrayObject $metadata,
15+
protected string $eventName,
16+
) {
17+
}
18+
19+
public function eventName(): string
20+
{
21+
return $this->eventName;
22+
}
23+
24+
public function getMetadata(): ArrayObject
25+
{
26+
return $this->metadata;
27+
}
28+
}

src/Event/EntityDefinition.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
class EntityDefinition implements
1111
HasEventName
1212
{
13-
/** @param string[] $entityAliasMap */
1413
public function __construct(
1514
protected ArrayObject $definition,
1615
protected string $eventName,

src/Metadata/GlobalEnable.php

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,28 @@
55
namespace ApiSkeletons\Doctrine\GraphQL\Metadata;
66

77
use ApiSkeletons\Doctrine\GraphQL\Config;
8+
use ApiSkeletons\Doctrine\GraphQL\Event\BuildMetadata;
89
use ApiSkeletons\Doctrine\GraphQL\Hydrator\Strategy;
10+
use ArrayObject;
911
use Doctrine\ORM\EntityManager;
12+
use League\Event\EventDispatcher;
1013

1114
use function in_array;
1215

1316
final class GlobalEnable extends AbstractMetadataFactory
1417
{
15-
/** @var mixed[] */
16-
private array $metadata = [];
18+
private ArrayObject $metadata;
1719

1820
public function __construct(
1921
private EntityManager $entityManager,
2022
protected Config $config,
23+
protected EventDispatcher $eventDispatcher,
2124
) {
25+
$this->metadata = new ArrayObject();
2226
}
2327

24-
/**
25-
* @param string[] $entityClasses
26-
*
27-
* @return array<int, mixed>
28-
*/
29-
public function __invoke(array $entityClasses): array
28+
/** @param string[] $entityClasses */
29+
public function __invoke(array $entityClasses): ArrayObject
3030
{
3131
foreach ($entityClasses as $entityClass) {
3232
// Get extract by value or reference
@@ -36,6 +36,7 @@ public function __invoke(array $entityClasses): array
3636
$this->metadata[$entityClass] = [
3737
'entityClass' => $entityClass,
3838
'byValue' => $byValue,
39+
'limit' => 0,
3940
'namingStrategy' => null,
4041
'fields' => [],
4142
'filters' => [],
@@ -48,6 +49,10 @@ public function __invoke(array $entityClasses): array
4849
$this->buildAssociationMetadata($entityClass);
4950
}
5051

52+
$this->eventDispatcher->dispatch(
53+
new BuildMetadata($this->metadata, 'metadata.build'),
54+
);
55+
5156
return $this->metadata;
5257
}
5358

src/Metadata/MetadataFactory.php

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,32 @@
66

77
use ApiSkeletons\Doctrine\GraphQL\Attribute;
88
use ApiSkeletons\Doctrine\GraphQL\Config;
9+
use ApiSkeletons\Doctrine\GraphQL\Event\BuildMetadata;
910
use ApiSkeletons\Doctrine\GraphQL\Hydrator\Strategy;
11+
use ArrayObject;
1012
use Doctrine\ORM\EntityManager;
1113
use Doctrine\ORM\Mapping\ClassMetadata;
1214
use Doctrine\ORM\Mapping\ClassMetadataInfo;
15+
use League\Event\EventDispatcher;
1316
use ReflectionClass;
1417

1518
use function assert;
19+
use function count;
1620

1721
class MetadataFactory extends AbstractMetadataFactory
1822
{
19-
/** @param mixed[] $metadata */
2023
public function __construct(
21-
protected array $metadata,
24+
protected ArrayObject $metadata,
2225
protected EntityManager $entityManager,
2326
protected Config $config,
2427
protected GlobalEnable $globalEnable,
28+
protected EventDispatcher $eventDispatcher,
2529
) {
2630
}
2731

28-
/** @return mixed[]|null */
29-
public function __invoke(): array|null
32+
public function __invoke(): ArrayObject
3033
{
31-
if ($this->metadata) {
34+
if (count($this->metadata)) {
3235
return $this->metadata;
3336
}
3437

@@ -53,6 +56,10 @@ public function __invoke(): array|null
5356
$this->buildMetadataForAssociations($entityClassMetadata, $reflectionClass);
5457
}
5558

59+
$this->eventDispatcher->dispatch(
60+
new BuildMetadata($this->metadata, 'metadata.build'),
61+
);
62+
5663
return $this->metadata;
5764
}
5865

@@ -86,6 +93,7 @@ private function buildMetadataForEntity(ReflectionClass $reflectionClass): void
8693
$this->metadata[$reflectionClass->getName()] = [
8794
'entityClass' => $reflectionClass->getName(),
8895
'byValue' => $this->config->getGlobalByValue() ?? $instance->getByValue(),
96+
'limit' => $instance->getLimit(),
8997
'namingStrategy' => $instance->getNamingStrategy(),
9098
'fields' => [],
9199
'filters' => $instance->getFilters(),

src/Resolve/ResolveCollectionFactory.php

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use ApiSkeletons\Doctrine\GraphQL\Event\FilterCriteria;
1010
use ApiSkeletons\Doctrine\GraphQL\Type\Entity;
1111
use ApiSkeletons\Doctrine\GraphQL\Type\TypeManager;
12+
use ArrayObject;
1213
use Closure;
1314
use Doctrine\Common\Collections\Collection;
1415
use Doctrine\Common\Collections\Criteria;
@@ -25,14 +26,13 @@
2526

2627
class ResolveCollectionFactory
2728
{
28-
/** @param mixed[] $metadata */
2929
public function __construct(
3030
protected EntityManager $entityManager,
3131
protected Config $config,
3232
protected FieldResolver $fieldResolver,
3333
protected TypeManager $typeManager,
3434
protected EventDispatcher $eventDispatcher,
35-
protected array $metadata,
35+
protected ArrayObject $metadata,
3636
) {
3737
}
3838

@@ -61,16 +61,17 @@ public function get(Entity $entity): Closure
6161
$fieldResolver = $this->fieldResolver;
6262
$collection = $fieldResolver($source, $args, $context, $info);
6363

64-
$collectionMetadata = $this->entityManager->getMetadataFactory()
65-
->getMetadataFor(
66-
(string) $this->entityManager->getMetadataFactory()
67-
->getMetadataFor(ClassUtils::getRealClass($source::class))
68-
->getAssociationTargetClass($info->fieldName),
69-
);
70-
7164
$entityClass = ClassUtils::getRealClass($source::class);
7265

66+
$targetClassName = (string) $this->entityManager->getMetadataFactory()
67+
->getMetadataFor($entityClass)
68+
->getAssociationTargetClass($info->fieldName);
69+
70+
$collectionMetadata = $this->entityManager->getMetadataFactory()
71+
->getMetadataFor($targetClassName);
72+
7373
return $this->buildPagination(
74+
$targetClassName,
7475
$args['pagination'] ?? [],
7576
$collection,
7677
$this->buildCriteria($args['filter'] ?? [], $collectionMetadata),
@@ -130,6 +131,7 @@ private function buildCriteria(array $filter, ClassMetadata $collectionMetadata)
130131
* @return mixed[]
131132
*/
132133
private function buildPagination(
134+
string $targetClassName,
133135
array $pagination,
134136
PersistentCollection $collection,
135137
Criteria $criteria,
@@ -161,7 +163,7 @@ private function buildPagination(
161163

162164
$itemCount = count($collection->matching($criteria));
163165

164-
$offsetAndLimit = $this->calculateOffsetAndLimit($paginationFields, $itemCount);
166+
$offsetAndLimit = $this->calculateOffsetAndLimit($targetClassName, $paginationFields, $itemCount);
165167
if ($offsetAndLimit['offset']) {
166168
$criteria->setFirstResult($offsetAndLimit['offset']);
167169
}
@@ -244,11 +246,16 @@ protected function buildEdgesAndCursors(Collection $items, array $offsetAndLimit
244246
*
245247
* @return array<string, int>
246248
*/
247-
protected function calculateOffsetAndLimit(array $paginationFields, int $itemCount): array
249+
protected function calculateOffsetAndLimit(string $targetClassName, array $paginationFields, int $itemCount): array
248250
{
249251
$offset = 0;
250252

251-
$limit = $this->config->getLimit();
253+
$limit = $this->metadata[$targetClassName]['limit'];
254+
255+
if (! $limit) {
256+
$limit = $this->config->getLimit();
257+
}
258+
252259
$adjustedLimit = $paginationFields['first'] ?: $paginationFields['last'] ?: $limit;
253260

254261
if ($adjustedLimit < $limit) {

src/Resolve/ResolveEntityFactory.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use ApiSkeletons\Doctrine\GraphQL\Event\FilterQueryBuilder;
1010
use ApiSkeletons\Doctrine\GraphQL\Type\Entity;
1111
use ApiSkeletons\Doctrine\QueryBuilder\Filter\Applicator;
12+
use ArrayObject;
1213
use Closure;
1314
use Doctrine\ORM\EntityManager;
1415
use Doctrine\ORM\QueryBuilder;
@@ -26,6 +27,7 @@ public function __construct(
2627
protected Config $config,
2728
protected EntityManager $entityManager,
2829
protected EventDispatcher $eventDispatcher,
30+
protected ArrayObject $metadata,
2931
) {
3032
}
3133

@@ -40,6 +42,7 @@ public function get(Entity $entity, string $eventName): Closure
4042
->select('entity');
4143

4244
return $this->buildPagination(
45+
entity: $entity,
4346
queryBuilder: $queryBuilder,
4447
aliasMap: $queryBuilderFilter->getEntityAliasMap(),
4548
eventName: $eventName,
@@ -100,6 +103,7 @@ private function buildFilterArray(array $filterTypes): array
100103
* @return mixed[]
101104
*/
102105
public function buildPagination(
106+
Entity $entity,
103107
QueryBuilder $queryBuilder,
104108
array $aliasMap,
105109
string $eventName,
@@ -128,7 +132,7 @@ public function buildPagination(
128132
}
129133
}
130134

131-
$offsetAndLimit = $this->calculateOffsetAndLimit($paginationFields);
135+
$offsetAndLimit = $this->calculateOffsetAndLimit($entity, $paginationFields);
132136

133137
if ($offsetAndLimit['offset']) {
134138
$queryBuilder->setFirstResult($offsetAndLimit['offset']);
@@ -222,10 +226,15 @@ protected function buildEdgesAndCursors(QueryBuilder $queryBuilder, array $offse
222226
*
223227
* @return array<string, int>
224228
*/
225-
protected function calculateOffsetAndLimit(array $paginationFields): array
229+
protected function calculateOffsetAndLimit(Entity $entity, array $paginationFields): array
226230
{
227231
$offset = 0;
228-
$limit = $this->config->getLimit();
232+
233+
$limit = $this->metadata[$entity->getEntityClass()]['limit'];
234+
235+
if (! $limit) {
236+
$limit = $this->config->getLimit();
237+
}
229238

230239
$adjustedLimit = $paginationFields['first'] ?: $paginationFields['last'] ?: $limit;
231240
if ($adjustedLimit < $limit) {

src/Services.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace ApiSkeletons\Doctrine\GraphQL;
66

77
use ApiSkeletons\Doctrine\GraphQL\Metadata\GlobalEnable;
8+
use ArrayObject;
89
use Doctrine\ORM\EntityManager;
910
use League\Event\EventDispatcher;
1011

@@ -25,6 +26,8 @@ public function __construct(
2526
Config|null $config = null,
2627
array $metadata = [],
2728
) {
29+
$metadata = new ArrayObject($metadata);
30+
2831
$this
2932
// Plain classes
3033
->set(EntityManager::class, $entityManager)
@@ -54,6 +57,7 @@ static function (AbstractContainer $container) use ($metadata) {
5457
$container->get(EntityManager::class),
5558
$container->get(Config::class),
5659
$container->get(GlobalEnable::class),
60+
$container->get(EventDispatcher::class),
5761
))();
5862
},
5963
)
@@ -63,6 +67,7 @@ static function (AbstractContainer $container) {
6367
return new Metadata\GlobalEnable(
6468
$container->get(EntityManager::class),
6569
$container->get(Config::class),
70+
$container->get(EventDispatcher::class),
6671
);
6772
},
6873
)
@@ -95,6 +100,7 @@ static function (AbstractContainer $container) {
95100
$container->get(Config::class),
96101
$container->get(EntityManager::class),
97102
$container->get(EventDispatcher::class),
103+
$container->get('metadata'),
98104
);
99105
},
100106
)

0 commit comments

Comments
 (0)