• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 17063252025

19 Aug 2025 07:54AM UTC coverage: 22.209% (-0.03%) from 22.236%
17063252025

push

github

soyuka
Merge 4.1

11 of 157 new or added lines in 17 files covered. (7.01%)

2 existing lines in 2 files now uncovered.

11699 of 52676 relevant lines covered (22.21%)

24.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Laravel/Eloquent/State/LinksHandler.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Laravel\Eloquent\State;
15

16
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
17
use ApiPlatform\Metadata\Exception\RuntimeException;
18
use ApiPlatform\Metadata\GraphQl\Operation;
19
use ApiPlatform\Metadata\GraphQl\Query;
20
use ApiPlatform\Metadata\HttpOperation;
21
use ApiPlatform\Metadata\Link;
22
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
23
use Illuminate\Contracts\Foundation\Application;
24
use Illuminate\Database\Eloquent\Builder;
25
use Illuminate\Database\Eloquent\Model;
26
use Illuminate\Database\Eloquent\Relations\BelongsTo;
27
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
28
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
29
use Illuminate\Database\Eloquent\Relations\MorphTo;
30
use Illuminate\Database\Eloquent\Relations\Relation;
31
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
32

33
/**
34
 * @implements LinksHandlerInterface<Model>
35
 */
36
final class LinksHandler implements LinksHandlerInterface
37
{
38
    public function __construct(
39
        private readonly Application $application,
40
        private readonly ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory,
41
    ) {
42
    }
×
43

44
    public function handleLinks(Builder $builder, array $uriVariables, array $context): Builder
45
    {
46
        $operation = $context['operation'];
×
47

48
        if ($operation instanceof HttpOperation) {
×
49
            foreach (array_reverse($operation->getUriVariables() ?? []) as $uriVariable => $link) {
×
50
                $builder = $this->buildQuery($builder, $link, $uriVariables[$uriVariable]);
×
51
            }
52

53
            return $builder;
×
54
        }
55

56
        if (!($linkClass = $context['linkClass'] ?? false)) {
×
57
            return $builder;
×
58
        }
59

60
        $newLink = null;
×
61
        $linkedOperation = null;
×
62
        $linkProperty = $context['linkProperty'] ?? null;
×
63

64
        try {
65
            $resourceMetadataCollection = $this->resourceMetadataCollectionFactory->create($linkClass);
×
66
            $linkedOperation = $resourceMetadataCollection->getOperation($operation->getName());
×
67
        } catch (OperationNotFoundException) {
×
68
            // Instead, we'll look for the first Query available.
69
            foreach ($resourceMetadataCollection as $resourceMetadata) {
×
70
                foreach ($resourceMetadata->getGraphQlOperations() as $op) {
×
71
                    if ($op instanceof Query) {
×
72
                        $linkedOperation = $op;
×
73
                    }
74
                }
75
            }
76
        }
77

78
        if (!$linkedOperation instanceof Operation) {
×
79
            return $builder;
×
80
        }
81

82
        $resourceClass = $builder->getModel()::class;
×
83
        foreach ($linkedOperation->getLinks() ?? [] as $link) {
×
84
            if ($resourceClass === $link->getToClass() && $linkProperty === $link->getFromProperty()) {
×
85
                $newLink = $link;
×
86
                break;
×
87
            }
88
        }
89

90
        if (!$newLink) {
×
91
            return $builder;
×
92
        }
93

94
        return $this->buildQuery($builder, $newLink, $uriVariables[$newLink->getIdentifiers()[0]]);
×
95
    }
96

97
    /**
98
     * @param Builder<Model> $builder
99
     *
100
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
101
     *
102
     * @return Builder<Model> $builder
103
     */
104
    private function buildQuery(Builder $builder, Link $link, mixed $identifier): Builder
105
    {
106
        if ($to = $link->getToProperty()) {
×
107
            return $builder->where($builder->getModel()->{$to}()->getQualifiedForeignKeyName(), $identifier);
×
108
        }
109

110
        if ($from = $link->getFromProperty()) {
×
111
            /** @var Model $relatedInstance */
112
            $relatedInstance = $this->application->make($link->getFromClass());
×
113

NEW
114
            $identifierField = $link->getIdentifiers()[0];
×
115

NEW
116
            if ($identifierField !== $relatedInstance->getKeyName()) {
×
NEW
117
                $relatedInstance = $relatedInstance
×
NEW
118
                    ->newQuery()
×
NEW
119
                    ->where($identifierField, $identifier)
×
NEW
120
                    ->first();
×
121
            } else {
NEW
122
                $relatedInstance->setAttribute($identifierField, $identifier);
×
NEW
123
                $relatedInstance->exists = true;
×
124
            }
125

NEW
126
            if (!$relatedInstance) {
×
NEW
127
                throw new NotFoundHttpException('Not Found');
×
128
            }
129

130
            /** @var Relation<Model, Model, mixed> $relation */
131
            $relation = $relatedInstance->{$from}();
×
132

133
            if ($relation instanceof MorphTo) {
×
134
                throw new RuntimeException('Cannot query directly from a MorphTo relationship.');
×
135
            }
136

137
            if ($relation instanceof BelongsTo) {
×
138
                return $builder->getModel()
×
139
                    ->join(
×
140
                        $relation->getParent()->getTable(),
×
141
                        $relation->getParent()->getQualifiedKeyName(),
×
142
                        $identifier
×
143
                    );
×
144
            }
145

146
            if ($relation instanceof HasOneOrMany || $relation instanceof BelongsToMany) {
×
147
                return $relation->getQuery();
×
148
            }
149

150
            if (method_exists($relation, 'getQualifiedForeignKeyName')) {
×
151
                return $relation->getQuery()->where(
×
152
                    $relation->getQualifiedForeignKeyName(),
×
153
                    $identifier
×
154
                );
×
155
            }
156

157
            throw new RuntimeException(\sprintf('Unhandled or unknown relationship type: %s for property %s on %s', $relation::class, $from, $relatedInstance::class));
×
158
        }
159

160
        return $builder->where(
×
161
            $builder->getModel()->qualifyColumn($link->getIdentifiers()[0]),
×
162
            $identifier
×
163
        );
×
164
    }
165
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc