Passed
Pull Request — master (#132)
by Dorian
04:52
created

ObjectBeanPropertyDescriptor   A

Complexity

Total Complexity 36

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 36
eloc 80
dl 0
loc 261
rs 9.52
c 0
b 0
f 0

16 Methods

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