Passed
Pull Request — master (#45)
by Roman
03:50
created

Stenographer::getRelatedPropertyModelName()   A

Complexity

Conditions 6
Paths 3

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 13
rs 9.2222
c 0
b 0
f 0
ccs 0
cts 12
cp 0
cc 6
nc 3
nop 1
crap 42
1
<?php
2
3
4
namespace Kami\ApiCoreBundle\Stenographer;
5
6
7
use Doctrine\Common\Annotations\Reader;
8
use Doctrine\Common\Inflector\Inflector;
9
use Doctrine\ORM\Mapping\Column;
10
use Doctrine\ORM\Mapping\ManyToMany;
11
use Doctrine\ORM\Mapping\ManyToOne;
12
use Doctrine\ORM\Mapping\OneToMany;
13
use Doctrine\ORM\Mapping\OneToOne;
14
use EXSyst\Component\Swagger\Path;
15
use EXSyst\Component\Swagger\Swagger;
16
use Kami\ApiCoreBundle\Annotation\Access;
17
use Kami\ApiCoreBundle\Annotation\AnonymousAccess;
18
use Kami\ApiCoreBundle\Annotation\AnonymousCreate;
19
use Kami\ApiCoreBundle\Annotation\AnonymousUpdate;
20
use Kami\ApiCoreBundle\Annotation\CanBeCreatedBy;
21
use Kami\ApiCoreBundle\Annotation\CanBeUpdatedBy;
22
use Kami\ApiCoreBundle\Annotation\Relation;
23
use Symfony\Component\Routing\Route;
24
25
class Stenographer
26
{
27
    /**
28
     * @var Reader
29
     */
30
    protected $reader;
31
32
    /**
33
     * Stenographer constructor.
34
     *
35
     * @param Reader $reader
36
     */
37
    public function __construct(Reader $reader)
38
    {
39
        $this->reader = $reader;
40
    }
41
42
    /**
43
     * @param Swagger $api
44
     *
45
     * @param Route $route
46
     */
47
    public function getStenography(Swagger $api, Route $route)
48
    {
49
        $paths = $api->getPaths();
50
51
        foreach (['get', 'post', 'put', 'delete'] as $operation) {
52
            $paths->get($route->getPath())->removeOperation($operation);
53
        }
54
55
        $paths->set(sprintf('/api/%s', $route->getDefault('_resource_name')), new Path([
56
            'get' => [
57
                'summary' => sprintf('Get %s index', $route->getDefault('_resource_name')),
58
                'parameters' => array_merge($this->getEntityFields($route, AnonymousAccess::class, Access::class), $this->getSortFields()),
59
                'responses' => [
60
                    '200' => [
61
                        'description' => 'Successful operation',
62
                    ],
63
                    '403' => [
64
                        'description' => 'Access denied'
65
                    ],
66
                    '401' => [
67
                        'description' => 'Authorization required'
68
                    ]
69
                ],
70
                'tags' => [$route->getDefault('_resource_name')]
71
            ],
72
            'post' => [
73
                'summary' => sprintf('Create %s', $route->getDefault('_resource_name')),
74
                'parameters' => $this->getEntityFields($route, AnonymousCreate::class, CanBeCreatedBy::class),
75
                'tags' => [$route->getDefault('_resource_name')],
76
                'responses' => [
77
                    '200' => [
78
                        'description' => 'Successful operation',
79
                    ],
80
                    '403' => [
81
                        'description' => 'Access denied'
82
                    ],
83
                    '401' => [
84
                        'description' => 'Authorization required'
85
                    ]
86
                ],
87
            ]
88
        ]));
89
        $paths->set(sprintf('/api/%s/{id}', $route->getDefault('_resource_name')), new Path([
90
            'get' => [
91
                'summary' => sprintf('Get single %s', $route->getDefault('_resource_name')),
92
                'parameters' => $this->getEntityFields($route, AnonymousAccess::class, Access::class),
93
                'responses' => [
94
                    '200' => [
95
                        'description' => 'Successful operation'
96
                    ],
97
                    '403' => [
98
                        'description' => 'Access denied'
99
                    ],
100
                    '401' => [
101
                        'description' => 'Authorization required'
102
                    ]
103
                ],
104
                'tags' => [$route->getDefault('_resource_name')]
105
            ],
106
            'put' => [
107
                'summary' => sprintf('Update %s', $route->getDefault('_resource_name')),
108
                'parameters' => $this->getEntityFields($route, AnonymousUpdate::class, CanBeUpdatedBy::class),
109
                'tags' => [$route->getDefault('_resource_name')]
110
            ],
111
            'delete' => [
112
                'summary' => sprintf('Delete %s', $route->getDefault('_resource_name')),
113
                'parameters' => [
114
                    [
115
                        'name' => 'id',
116
                        'description' => 'Resource identifier',
117
                        'in' => 'integer'
118
                    ]
119
                ],
120
                'responses' => [
121
                    '204' => [
122
                        'description' => 'Successful operation'
123
                    ],
124
                    '403' => [
125
                        'description' => 'Access denied'
126
                    ],
127
                    '401' => [
128
                        'description' => 'Authorization required'
129
                    ]
130
                ],
131
                'tags' => [$route->getDefault('_resource_name')]
132
            ]
133
        ]));
134
        $paths->set(sprintf('/api/%s/filter', $route->getDefault('_resource_name')), new Path([
135
            'get' => [
136
                'summary' => 'Filter operation',
137
                'parameters' => array_merge($this->getEntityFields($route, AnonymousAccess::class, Access::class), $this->getSortFields()),
138
                'responses' => [
139
                    '200' => [
140
                        'description' => 'Successful operation',
141
                    ],
142
                    '403' => [
143
                        'description' => 'Access denied'
144
                    ],
145
                    '401' => [
146
                        'description' => 'Authorization required'
147
                    ]
148
                ],
149
                'tags' => [$route->getDefault('_resource_name')]
150
            ],
151
        ]));
152
    }
153
154
155
    private function getSortFields()
156
    {
157
        return [
158
            [
159
                'name' => 'page',
160
                'description' => 'Page to return',
161
                'in' => 'integer'
162
            ],
163
            [
164
                'name' => 'sort',
165
                'description' => 'If you need to sort by related model, you should set this field with "." like: '.
166
            '<pre>property_name_in_your_model.property_name_in_related_model</pre>',
167
                'in' => 'string'
168
            ],
169
            [
170
                'name' => 'direction',
171
                'description' => 'Sort direction',
172
                'in' => 'string (asc|desc)'
173
            ]
174
        ];
175
    }
176
177
    private function getEntityFields($route, $anonymousClass, $canAccessClass)
178
    {
179
        $reflection = $this->getEntityReflection($route->getDefault('_entity'));
180
        $availableParams = [];
181
182
        foreach ($reflection->getProperties() as $property) {
183
            $anonymous = $this->reader->getPropertyAnnotation($property, $anonymousClass);
184
            $access = $this->reader->getPropertyAnnotation($property, $canAccessClass);
185
            $relation = $this->reader->getPropertyAnnotation($property, Relation::class);
186
            if ($anonymous || $access) {
187
                $column = $this->reader->getPropertyAnnotation($property, Column::class);
188
                $param = ['in' => $relation ? 'Related model' : ($column ? $column->type : 'Unknown')];
189
190
                $param['name'] = sprintf('%s[%s]',
191
                    Inflector::tableize($reflection->getShortName()),
192
                    Inflector::tableize($property->getName())
193
                );
194
                $availableAccess = $anonymous ? ['Any'] : [];
195
                $availableAccess = $access ? $access->roles : $availableAccess;
196
                $availableAccess = implode(', ', $availableAccess);
197
198
                $param['description'] = $relation ?
199
                    '<a href="#operations-tag-'.$this->getRelatedPropertyModelName($property).'">'.
200
                    'Related model description</a><pre>Access: ' . $availableAccess . '</pre>' :
201
                    '<pre>Access: ' . $availableAccess . '</pre>';
202
203
                $availableParams[] = $param;
204
            }
205
        }
206
207
        return $availableParams;
208
    }
209
210
    private function getRelatedPropertyModelName($property)
211
    {
212
        $annotations = $this->reader->getPropertyAnnotations($property);
213
        foreach ($annotations as $annotation){
214
            if(
215
                $annotation instanceof OneToOne ||
216
                $annotation instanceof OneToMany ||
217
                $annotation instanceof ManyToOne ||
218
                $annotation instanceof ManyToMany
219
            ) $targetEntity = $annotation->targetEntity;
220
        }
221
222
        return str_replace('_', '-', Inflector::tableize(substr($targetEntity, strripos($targetEntity, '\\') + 1)));
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $targetEntity does not seem to be defined for all execution paths leading up to this point.
Loading history...
223
    }
224
225
    /**
226
     * @param string $entity
227
     * @return \ReflectionClass
228
     */
229
    protected function getEntityReflection($entity)
230
    {
231
        try {
232
            $reflection = new \ReflectionClass($entity);
233
        } catch (\ReflectionException $e) {
234
            throw new \InvalidArgumentException('Route default entity can not be found');
235
        }
236
        return $reflection;
237
    }
238
}
239