Passed
Pull Request — master (#125)
by David
04:07
created

ObjectBeanPropertyDescriptor::isGetterProtected()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace TheCodingMachine\TDBM\Utils;
5
6
use function array_map;
7
use Doctrine\DBAL\Schema\Column;
8
use Doctrine\DBAL\Schema\Table;
9
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
10
use TheCodingMachine\TDBM\TDBMException;
11
use TheCodingMachine\TDBM\Utils\Annotation\AnnotationParser;
12
use TheCodingMachine\TDBM\Utils\Annotation\Annotations;
13
use Zend\Code\Generator\AbstractMemberGenerator;
14
use Zend\Code\Generator\MethodGenerator;
15
use Zend\Code\Generator\ParameterGenerator;
16
17
/**
18
 * This class represent a property in a bean that points to another table.
19
 */
20
class ObjectBeanPropertyDescriptor extends AbstractBeanPropertyDescriptor
21
{
22
    /**
23
     * @var ForeignKeyConstraint
24
     */
25
    private $foreignKey;
26
    /**
27
     * @var string
28
     */
29
    private $beanNamespace;
30
31
    /**
32
     * @var Annotations[]
33
     */
34
    private $annotations;
35
    /**
36
     * @var Column[]
37
     */
38
    private $localColumns;
39
    /**
40
     * @var AnnotationParser
41
     */
42
    private $annotationParser;
43
44
    /**
45
     * ObjectBeanPropertyDescriptor constructor.
46
     * @param Table $table
47
     * @param ForeignKeyConstraint $foreignKey
48
     * @param NamingStrategyInterface $namingStrategy
49
     */
50
    public function __construct(Table $table, ForeignKeyConstraint $foreignKey, NamingStrategyInterface $namingStrategy, string $beanNamespace, AnnotationParser $annotationParser)
51
    {
52
        parent::__construct($table, $namingStrategy);
53
        $this->foreignKey = $foreignKey;
54
        $this->beanNamespace = $beanNamespace;
55
        $this->annotationParser = $annotationParser;
56
    }
57
58
    /**
59
     * Returns the foreignkey the column is part of, if any. null otherwise.
60
     *
61
     * @return ForeignKeyConstraint
62
     */
63
    public function getForeignKey(): ForeignKeyConstraint
64
    {
65
        return $this->foreignKey;
66
    }
67
68
    /**
69
     * Returns the name of the class linked to this property or null if this is not a foreign key.
70
     *
71
     * @return string
72
     */
73
    public function getClassName(): string
74
    {
75
        return $this->namingStrategy->getBeanClassName($this->foreignKey->getForeignTableName());
76
    }
77
78
    /**
79
     * Returns the PHP type for the property (it can be a scalar like int, bool, or class names, like \DateTimeInterface, App\Bean\User....)
80
     *
81
     * @return string
82
     */
83
    public function getPhpType(): string
84
    {
85
        return '\\'.$this->beanNamespace.'\\'.$this->getClassName();
86
    }
87
88
    /**
89
     * Returns true if the property is compulsory (and therefore should be fetched in the constructor).
90
     *
91
     * @return bool
92
     */
93
    public function isCompulsory(): bool
94
    {
95
        // Are all columns nullable?
96
        foreach ($this->getLocalColumns() as $column) {
97
            if ($column->getNotnull()) {
98
                return true;
99
            }
100
        }
101
102
        return false;
103
    }
104
105
    /**
106
     * @return Column[]
107
     */
108
    private function getLocalColumns(): array
109
    {
110
        if ($this->localColumns === null) {
111
            $localColumnNames = $this->foreignKey->getUnquotedLocalColumns();
112
113
            $this->localColumns = array_map([$this->table, 'getColumn'], $localColumnNames);
114
        }
115
        return $this->localColumns;
116
    }
117
118
    /**
119
     * Returns true if the property has a default value.
120
     *
121
     * @return bool
122
     */
123
    public function hasDefault(): bool
124
    {
125
        return false;
126
    }
127
128
    /**
129
     * Returns the code that assigns a value to its default value.
130
     *
131
     * @return string
132
     *
133
     * @throws TDBMException
134
     */
135
    public function assignToDefaultCode(): string
136
    {
137
        throw new TDBMException('Foreign key based properties cannot be assigned a default value.');
138
    }
139
140
    /**
141
     * Returns true if the property is the primary key.
142
     *
143
     * @return bool
144
     */
145
    public function isPrimaryKey(): bool
146
    {
147
        $fkColumns = $this->foreignKey->getUnquotedLocalColumns();
148
        sort($fkColumns);
149
150
        $pkColumns = TDBMDaoGenerator::getPrimaryKeyColumnsOrFail($this->table);
151
        sort($pkColumns);
152
153
        return $fkColumns == $pkColumns;
154
    }
155
156
    /**
157
     * Returns the PHP code for getters and setters.
158
     *
159
     * @return MethodGenerator[]
160
     */
161
    public function getGetterSetterCode(): array
162
    {
163
        $tableName = $this->table->getName();
164
        $getterName = $this->getGetterName();
165
        $setterName = $this->getSetterName();
166
        $isNullable = !$this->isCompulsory();
167
168
        $referencedBeanName = $this->namingStrategy->getBeanClassName($this->foreignKey->getForeignTableName());
169
170
        $getter = new MethodGenerator($getterName);
171
        $getter->setDocBlock('Returns the '.$referencedBeanName.' object bound to this object via the '.implode(' and ', $this->foreignKey->getUnquotedLocalColumns()).' column.');
172
173
        /*$types = [ $referencedBeanName ];
174
        if ($isNullable) {
175
            $types[] = 'null';
176
        }
177
        $getter->getDocBlock()->setTag(new ReturnTag($types));*/
178
179
        $getter->setReturnType(($isNullable?'?':'').$this->beanNamespace.'\\'.$referencedBeanName);
180
181
        $getter->setBody('return $this->getRef('.var_export($this->foreignKey->getName(), true).', '.var_export($tableName, true).');');
182
183
        if ($this->isGetterProtected()) {
184
            $getter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
185
        }
186
187
        $setter = new MethodGenerator($setterName);
188
        $setter->setDocBlock('The setter for the '.$referencedBeanName.' object bound to this object via the '.implode(' and ', $this->foreignKey->getUnquotedLocalColumns()).' column.');
189
190
        $setter->setParameter(new ParameterGenerator('object', ($isNullable?'?':'').$this->beanNamespace.'\\'.$referencedBeanName));
191
192
        $setter->setReturnType('void');
193
194
        $setter->setBody('$this->setRef('.var_export($this->foreignKey->getName(), true).', $object, '.var_export($tableName, true).');');
195
196
        if ($this->isSetterProtected()) {
197
            $setter->setVisibility(AbstractMemberGenerator::VISIBILITY_PROTECTED);
198
        }
199
200
        return [$getter, $setter];
201
    }
202
203
    /**
204
     * Returns the part of code useful when doing json serialization.
205
     *
206
     * @return string
207
     */
208
    public function getJsonSerializeCode(): string
209
    {
210
        if ($this->isGetterProtected()) {
211
            return '';
212
        }
213
214
        if (!$this->isCompulsory()) {
215
            return 'if (!$stopRecursion) {
216
    $object = $this->'.$this->getGetterName().'();
217
    $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = $object ? $object->jsonSerialize(true) : null;
218
}
219
';
220
        } else {
221
            return 'if (!$stopRecursion) {
222
    $array['.var_export($this->namingStrategy->getJsonProperty($this), true).'] = $this->'.$this->getGetterName().'()->jsonSerialize(true);
223
}
224
';
225
        }
226
    }
227
228
    /**
229
     * The code to past in the __clone method.
230
     * @return null|string
231
     */
232
    public function getCloneRule(): ?string
233
    {
234
        return null;
235
    }
236
237
    /**
238
     * Tells if this property is a type-hintable in PHP (resource isn't for example)
239
     *
240
     * @return bool
241
     */
242
    public function isTypeHintable() : bool
243
    {
244
        return true;
245
    }
246
247
    private function isGetterProtected(): bool
248
    {
249
        foreach ($this->getAnnotations() as $annotations) {
250
            if ($annotations->findAnnotation(Annotation\ProtectedGetter::class)) {
251
                return true;
252
            }
253
        }
254
        return false;
255
    }
256
257
    private function isSetterProtected(): bool
258
    {
259
        foreach ($this->getAnnotations() as $annotations) {
260
            if ($annotations->findAnnotation(Annotation\ProtectedSetter::class)) {
261
                return true;
262
            }
263
        }
264
        return false;
265
    }
266
267
    /**
268
     * @return Annotations[]
269
     */
270
    private function getAnnotations(): array
271
    {
272
        if ($this->annotations === null) {
273
            $this->annotations = [];
274
275
            // Are all columns nullable?
276
            foreach ($this->getLocalColumns() as $column) {
277
                $this->annotations[] = $this->annotationParser->getColumnAnnotations($column, $this->table);
278
            }
279
        }
280
        return $this->annotations;
281
    }
282
}
283