Passed
Pull Request — master (#132)
by Dorian
05:13
created

ObjectBeanPropertyDescriptor::getCloneRule()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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