PropertyMetadataFactory.php 7.5 KB
Newer Older
1
2
<?php declare(strict_types=1);
/*
Guillaume Perréal's avatar
Guillaume Perréal committed
3
4
5
 * This file is part of "irstea/ng-model-generator-bundle".
 *
 * "irstea/ng-model-generator-bundle" generates Typescript interfaces for Angular using api-platform metadata.
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 * Copyright (C) 2018 IRSTEA
 *
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License and the GNU
 * Lesser General Public License along with this program. If not, see
 * <https://www.gnu.org/licenses/>.
 */

namespace Irstea\NgModelGeneratorBundle\Metadata;

24
use ApiPlatform\Core\Api\ResourceClassResolverInterface;
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
use ApiPlatform\Core\Exception\ResourceClassNotFoundException;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface;
use ApiPlatform\Core\Metadata\Property\PropertyMetadata as APIPropertyMetadata;
use Irstea\NgModelGeneratorBundle\Models\ClassName;
use Symfony\Component\PropertyInfo\Type;

/**
 * Class PropertyMetadataFactory.
 */
class PropertyMetadataFactory
{
    public const MODE_CREATE = 'CREATE';
    public const MODE_UPDATE = 'UPDATE';
    public const MODE_READ = 'READ';
    public const MODE_OTHER = 'OTHER';

    /** @var PropertyNameCollectionFactoryInterface */
    private $propertyNameCollectionFactory;

    /** @var PropertyMetadataFactoryInterface */
    private $propertyMetadataFactory;

    /** @var ClassName */
    private $resource;

    /** @var string */
    private $mode;

    /** @var array */
    private $groups;

    /** @var array */
    private $properties = [];

60
61
62
    /** @var ResourceClassResolverInterface */
    private $resourceClassResolver;

63
64
65
    /**
     * PropertyMetadataFactory constructor.
     *
66
     * @param ResourceClassResolverInterface         $resourceClassResolver
67
68
69
70
71
72
73
     * @param PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory
     * @param PropertyMetadataFactoryInterface       $propertyMetadataFactory
     * @param ClassName                              $resource
     * @param string                                 $mode
     * @param array                                  $groups
     */
    public function __construct(
74
        ResourceClassResolverInterface $resourceClassResolver,
75
76
77
78
79
80
81
82
83
84
85
        PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory,
        PropertyMetadataFactoryInterface $propertyMetadataFactory,
        ClassName $resource,
        string $mode,
        array $groups
    ) {
        $this->propertyNameCollectionFactory = $propertyNameCollectionFactory;
        $this->propertyMetadataFactory = $propertyMetadataFactory;
        $this->resource = $resource;
        $this->mode = $mode;
        $this->groups = $groups;
86
        $this->resourceClassResolver = $resourceClassResolver;
87
88
89
    }

    /**
Guillaume Perréal's avatar
Guillaume Perréal committed
90
91
92
93
94
     * @param ClassName $class
     * @param string    $propertyName
     *
     * @throws ResourceClassNotFoundException
     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
95
96
97
98
99
100
101
102
103
104
     *
     * @return PropertyMetadata
     */
    public function create(ClassName $class, string $propertyName): PropertyMetadata
    {
        $propertyMeta = $this->getAPIMetadata($class)[$propertyName];

        $typeMeta = $propertyMeta->getType();
        \assert($typeMeta !== null);

105
106
107
        [$link, $embedded] = $this->getLinkStatus($propertyMeta);

        $nullable = $this->mode === self::MODE_UPDATE || $typeMeta->isNullable();
108
109
110
111
112
113

        return new PropertyMetadata(
            $propertyName,
            '',
            $typeMeta,
            $propertyMeta->isIdentifier(),
114
            $nullable,
115
116
            $propertyMeta->isReadable(),
            $propertyMeta->isWritable(),
117
118
119
            (bool) $propertyMeta->isInitializable(),
            $link,
            $embedded
120
121
122
123
        );
    }

    /**
124
     * @param $propertyMeta
125
     *
126
     * @return array
127
     */
128
    private function getLinkStatus(APIPropertyMetadata $propertyMeta): array
129
    {
130
131
132
133
134
135
        $typeMeta = $propertyMeta->getType();
        \assert($typeMeta !== null);

        $leafType = $this->getLeafType($typeMeta);
        if (!$leafType) {
            return [false, false];
136
137
        }

138
139
140
141
142
143
144
145
        $leafClassName = $leafType->getClassName();
        if (!$leafClassName || !$this->resourceClassResolver->isResourceClass($leafClassName)) {
            return [false, false];
        }

        $embedded = $this->mode === self::MODE_READ ? $propertyMeta->isReadableLink() : $propertyMeta->isWritableLink();

        return [true, (bool) $embedded];
146
147
148
    }

    /**
149
     * @param Type $type
150
     *
151
     * @return Type|null
152
     */
153
    private function getLeafType(Type $type): ?Type
154
    {
155
156
        while ($type && $type->isCollection()) {
            $type = $type->getCollectionValueType();
157
158
        }

159
        return $type;
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
    }

    /**
     * @param ClassName $class
     *
     * @throws ResourceClassNotFoundException
     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
     *
     * @return APIPropertyMetadata[]
     */
    public function getAPIMetadata(ClassName $class): array
    {
        $key = $class->getFullName();
        if (!isset($this->properties[$key])) {
            $this->properties[$key] = $this->doGetAPIMetadata($class);
        }

        return $this->properties[$key];
    }

    /**
     * @param ClassName $class
     *
     * @throws \ApiPlatform\Core\Exception\PropertyNotFoundException
     * @throws ResourceClassNotFoundException
     *
     * @return APIPropertyMetadata[]
     */
    private function doGetAPIMetadata(ClassName $class): array
    {
        $properties = [];
        $options = $this->groups ? ['serializer_groups' => $this->groups] : [];
192
        $isResource = $class->getFullName() === $this->resource->getFullName();
193
194
195
196
197
198
199
200
201
202

        foreach ($this->propertyNameCollectionFactory->create($class->getFullName(), $options) as $propertyName) {
            \assert(\is_string($propertyName));

            $propertyMeta = $this->propertyMetadataFactory->create($class->getFullName(), $propertyName);

            if (!$propertyMeta->getType() || $propertyMeta->isChildInherited()) {
                continue;
            }

203
            if (!$this->acceptProperty($isResource, $propertyMeta)) {
204
205
206
207
208
209
210
211
212
213
214
215
                continue;
            }

            $properties[$propertyName] = $propertyMeta;
        }

        return $properties;
    }

    /**
     * @param string              $mode
     * @param APIPropertyMetadata $propertyMeta
216
     * @param mixed               $isResource
217
218
219
     *
     * @return bool
     */
220
    private function acceptProperty(bool $isResource, APIPropertyMetadata $propertyMeta): bool
221
    {
222
223
224
225
226
        if (!$isResource && $propertyMeta->isIdentifier()) {
            return true;
        }

        switch ($this->mode) {
227
228
            case self::MODE_CREATE:
                return $propertyMeta->isWritable() || $propertyMeta->isInitializable();
229

230
231
            case self::MODE_READ:
                return $propertyMeta->isReadable();
232
233
234
235

            case self::MODE_UPDATE:
                return $propertyMeta->isWritable();

236
237
238
239
240
            default:
                return $propertyMeta->isReadable() || $propertyMeta->isWritable();
        }
    }
}