1<?php
2
3/**
4 * @see       https://github.com/laminas/laminas-code for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-code/blob/master/COPYRIGHT.md
6 * @license   https://github.com/laminas/laminas-code/blob/master/LICENSE.md New BSD License
7 */
8
9namespace Laminas\Code\Generator;
10
11use Laminas\Code\Reflection\PropertyReflection;
12
13use function sprintf;
14use function str_replace;
15use function strtolower;
16
17class PropertyGenerator extends AbstractMemberGenerator
18{
19    const FLAG_CONSTANT = 0x08;
20
21    /**
22     * @var bool
23     */
24    protected $isConst;
25
26    /**
27     * @var PropertyValueGenerator
28     */
29    protected $defaultValue;
30
31    /**
32     * @var bool
33     */
34    private $omitDefaultValue = false;
35
36    /**
37     * @param  PropertyReflection $reflectionProperty
38     * @return PropertyGenerator
39     */
40    public static function fromReflection(PropertyReflection $reflectionProperty)
41    {
42        $property = new static();
43
44        $property->setName($reflectionProperty->getName());
45
46        $allDefaultProperties = $reflectionProperty->getDeclaringClass()->getDefaultProperties();
47
48        $defaultValue = $allDefaultProperties[$reflectionProperty->getName()];
49        $property->setDefaultValue($defaultValue);
50        if ($defaultValue === null) {
51            $property->omitDefaultValue = true;
52        }
53
54        if ($reflectionProperty->getDocComment() != '') {
55            $property->setDocBlock(DocBlockGenerator::fromReflection($reflectionProperty->getDocBlock()));
56        }
57
58        if ($reflectionProperty->isStatic()) {
59            $property->setStatic(true);
60        }
61
62        if ($reflectionProperty->isPrivate()) {
63            $property->setVisibility(self::VISIBILITY_PRIVATE);
64        } elseif ($reflectionProperty->isProtected()) {
65            $property->setVisibility(self::VISIBILITY_PROTECTED);
66        } else {
67            $property->setVisibility(self::VISIBILITY_PUBLIC);
68        }
69
70        $property->setSourceDirty(false);
71
72        return $property;
73    }
74
75    /**
76     * Generate from array
77     *
78     * @configkey name               string                                          [required] Class Name
79     * @configkey const              bool
80     * @configkey defaultvalue       null|bool|string|int|float|array|ValueGenerator
81     * @configkey flags              int
82     * @configkey abstract           bool
83     * @configkey final              bool
84     * @configkey static             bool
85     * @configkey visibility         string
86     * @configkey omitdefaultvalue   bool
87     *
88     * @throws Exception\InvalidArgumentException
89     * @param  array $array
90     * @return PropertyGenerator
91     */
92    public static function fromArray(array $array)
93    {
94        if (! isset($array['name'])) {
95            throw new Exception\InvalidArgumentException(
96                'Property generator requires that a name is provided for this object'
97            );
98        }
99
100        $property = new static($array['name']);
101        foreach ($array as $name => $value) {
102            // normalize key
103            switch (strtolower(str_replace(['.', '-', '_'], '', $name))) {
104                case 'const':
105                    $property->setConst($value);
106                    break;
107                case 'defaultvalue':
108                    $property->setDefaultValue($value);
109                    break;
110                case 'docblock':
111                    $docBlock = $value instanceof DocBlockGenerator ? $value : DocBlockGenerator::fromArray($value);
112                    $property->setDocBlock($docBlock);
113                    break;
114                case 'flags':
115                    $property->setFlags($value);
116                    break;
117                case 'abstract':
118                    $property->setAbstract($value);
119                    break;
120                case 'final':
121                    $property->setFinal($value);
122                    break;
123                case 'static':
124                    $property->setStatic($value);
125                    break;
126                case 'visibility':
127                    $property->setVisibility($value);
128                    break;
129                case 'omitdefaultvalue':
130                    $property->omitDefaultValue($value);
131                    break;
132            }
133        }
134
135        return $property;
136    }
137
138    /**
139     * @param string $name
140     * @param PropertyValueGenerator|string|array $defaultValue
141     * @param int $flags
142     */
143    public function __construct($name = null, $defaultValue = null, $flags = self::FLAG_PUBLIC)
144    {
145        if (null !== $name) {
146            $this->setName($name);
147        }
148        if (null !== $defaultValue) {
149            $this->setDefaultValue($defaultValue);
150        }
151        if ($flags !== self::FLAG_PUBLIC) {
152            $this->setFlags($flags);
153        }
154    }
155
156    /**
157     * @param  bool $const
158     * @return PropertyGenerator
159     */
160    public function setConst($const)
161    {
162        if ($const) {
163            $this->setFlags(self::FLAG_CONSTANT);
164        } else {
165            $this->removeFlag(self::FLAG_CONSTANT);
166        }
167
168        return $this;
169    }
170
171    /**
172     * @return bool
173     */
174    public function isConst()
175    {
176        return (bool) ($this->flags & self::FLAG_CONSTANT);
177    }
178
179    /**
180     * @param PropertyValueGenerator|mixed $defaultValue
181     * @param string                       $defaultValueType
182     * @param string                       $defaultValueOutputMode
183     *
184     * @return PropertyGenerator
185     */
186    public function setDefaultValue(
187        $defaultValue,
188        $defaultValueType = PropertyValueGenerator::TYPE_AUTO,
189        $defaultValueOutputMode = PropertyValueGenerator::OUTPUT_MULTIPLE_LINE
190    ) {
191        if (! $defaultValue instanceof PropertyValueGenerator) {
192            $defaultValue = new PropertyValueGenerator($defaultValue, $defaultValueType, $defaultValueOutputMode);
193        }
194
195        $this->defaultValue = $defaultValue;
196
197        return $this;
198    }
199
200    /**
201     * @return PropertyValueGenerator
202     */
203    public function getDefaultValue()
204    {
205        return $this->defaultValue;
206    }
207
208    /**
209     * @throws Exception\RuntimeException
210     * @return string
211     */
212    public function generate()
213    {
214        $name         = $this->getName();
215        $defaultValue = $this->getDefaultValue();
216
217        $output = '';
218
219        if (($docBlock = $this->getDocBlock()) !== null) {
220            $docBlock->setIndentation('    ');
221            $output .= $docBlock->generate();
222        }
223
224        if ($this->isConst()) {
225            if ($defaultValue !== null && ! $defaultValue->isValidConstantType()) {
226                throw new Exception\RuntimeException(sprintf(
227                    'The property %s is said to be '
228                    . 'constant but does not have a valid constant value.',
229                    $this->name
230                ));
231            }
232            $output .= $this->indentation . $this->getVisibility() . ' const ' . $name . ' = '
233                . ($defaultValue !== null ? $defaultValue->generate() : 'null;');
234
235            return $output;
236        }
237
238        $output .= $this->indentation . $this->getVisibility() . ($this->isStatic() ? ' static' : '') . ' $' . $name;
239
240        if ($this->omitDefaultValue) {
241            return $output . ';';
242        }
243
244        return $output . ' = ' . ($defaultValue !== null ? $defaultValue->generate() : 'null;');
245    }
246
247    /**
248     * @param bool $omit
249     * @return PropertyGenerator
250     */
251    public function omitDefaultValue(bool $omit = true)
252    {
253        $this->omitDefaultValue = $omit;
254
255        return $this;
256    }
257}
258