Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
1 / 1
PropertyGenerator
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
13 / 13
31
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 setType
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setValue
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasValue
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setAsReadonly
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 isReadonly
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 suppressReadonlyKeyword
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setAsStatic
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 render
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
15
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2declare(strict_types=1);
3/**
4 * Pop PHP Framework (https://www.popphp.org/)
5 *
6 * @link       https://github.com/popphp/popphp-framework
7 * @author     Nick Sagona, III <dev@noladev.com>
8 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
9 * @license    https://www.popphp.org/license     New BSD License
10 */
11
12/**
13 * @namespace
14 */
15namespace Pop\Code\Generator;
16
17use Pop\Code\Generator\Support\ValueFormatter;
18
19/**
20 * Property generator class
21 *
22 * @category   Pop
23 * @package    Pop\Code
24 * @author     Nick Sagona, III <dev@noladev.com>
25 * @copyright  Copyright (c) 2009-2027 NOLA Interactive, LLC.
26 * @license    https://www.popphp.org/license     New BSD License
27 * @version    6.0.0
28 */
29class PropertyGenerator extends AbstractClassElementGenerator
30{
31
32    /**
33     * Property type
34     * @var ?string
35     */
36    protected ?string $type = null;
37
38    /**
39     * Property value
40     * @var mixed
41     */
42    protected mixed $value = null;
43
44    /**
45     * Readonly flag
46     * @var bool
47     */
48    protected bool $readonly = false;
49
50    /**
51     * Flag to suppress printing the redundant 'readonly' keyword when the enclosing
52     * class is itself declared readonly (the property remains readonly regardless)
53     * @var bool
54     */
55    protected bool $suppressReadonlyKeyword = false;
56
57    /**
58     * Constructor
59     *
60     * Instantiate the property generator object
61     *
62     * @param  string $name
63     * @param  ?string $type
64     * @param  mixed $value
65     * @param  string $visibility
66     * @param  bool $static
67     * @throws Exception
68     */
69    public function __construct(
70        string $name, ?string $type = null, mixed $value = null, string $visibility = 'public', bool $static = false
71    )
72    {
73        $this->setName($name);
74        if ($type !== null) {
75            $this->setType($type);
76        }
77        if ($value !== null) {
78            $this->setValue($value);
79        }
80        $this->setVisibility($visibility);
81        $this->setAsStatic($static);
82    }
83
84    /**
85     * Set the property type
86     *
87     * @param  string $type
88     * @return PropertyGenerator
89     */
90    public function setType(string $type): PropertyGenerator
91    {
92        $this->type = $type;
93        return $this;
94    }
95
96    /**
97     * Get the property type
98     *
99     * @return string|null
100     */
101    public function getType(): string|null
102    {
103        return $this->type;
104    }
105
106    /**
107     * Has property type
108     *
109     * @return bool
110     */
111    public function hasType(): bool
112    {
113        return ($this->type !== null);
114    }
115
116    /**
117     * Set the property value
118     *
119     * @param  mixed $value
120     * @return PropertyGenerator
121     */
122    public function setValue(mixed $value = null): PropertyGenerator
123    {
124        $this->value = $value;
125        return $this;
126    }
127
128    /**
129     * Get the property value
130     *
131     * @return mixed
132     */
133    public function getValue(): mixed
134    {
135        return $this->value;
136    }
137
138    /**
139     * Has property value
140     *
141     * @return bool
142     */
143    public function hasValue(): bool
144    {
145        return ($this->value !== null);
146    }
147
148    /**
149     * Set the readonly flag
150     *
151     * @param  bool $readonly
152     * @return PropertyGenerator
153     */
154    public function setAsReadonly(bool $readonly = true): PropertyGenerator
155    {
156        $this->readonly = $readonly;
157        if ($this->readonly) {
158            $this->setAsStatic(false);
159        }
160        return $this;
161    }
162
163    /**
164     * Get the readonly flag
165     *
166     * @return bool
167     */
168    public function isReadonly(): bool
169    {
170        return $this->readonly;
171    }
172
173    /**
174     * Suppress printing the 'readonly' keyword on this property, e.g. because the enclosing
175     * class is itself declared readonly, making a per-property 'readonly' keyword redundant.
176     * The property is still treated as readonly for type/value rendering purposes.
177     *
178     * @param  bool $suppress
179     * @return PropertyGenerator
180     */
181    public function suppressReadonlyKeyword(bool $suppress = true): PropertyGenerator
182    {
183        $this->suppressReadonlyKeyword = $suppress;
184        return $this;
185    }
186
187    /**
188     * Set the static flag (overridden to enforce mutual exclusion with readonly)
189     *
190     * @param  bool $static
191     * @return PropertyGenerator
192     */
193    public function setAsStatic(bool $static = true): PropertyGenerator
194    {
195        parent::setAsStatic($static);
196        if ($static) {
197            $this->readonly = false;
198        }
199        return $this;
200    }
201
202    /**
203     * Render property
204     *
205     * @throws Exception
206     * @return string
207     */
208    public function render(): string
209    {
210        if ($this->readonly && ($this->type === null)) {
211            throw new Exception('Error: A readonly property must have a type.');
212        }
213
214        if ($this->docblock === null) {
215            $this->docblock = new DocblockGenerator(null, $this->indent);
216        }
217
218        $this->docblock->addTag('var', $this->type);
219        $type = null;
220        if ($this->type !== null) {
221            $type = $this->type;
222            if (!$this->readonly && ($this->value === null) && !str_starts_with($type, '?') && ($type !== 'mixed')
223                && !in_array('null', explode('|', $type), true)) {
224                // An intersection type (`Countable&Traversable`) needs parens before combining
225                // with `|null` -- PHP requires DNF syntax `(A&B)|null`, not the bare `A&B|null`.
226                $type = str_contains($type, '&') ? '(' . $type . ')|null' : $type . '|null';
227            }
228            $type .= ' ';
229        }
230        $this->output  = PHP_EOL . $this->docblock->render();
231        $this->output .= $this->formatAttributes();
232        $this->output .= $this->printIndent() . $this->visibility . (($this->static) ? ' static' : '')
233            . (($this->readonly && !$this->suppressReadonlyKeyword) ? ' readonly' : '') . ' ' . $type . '$' . $this->name;
234
235        if ($this->readonly) {
236            $this->output .= ';';
237        } else {
238            $this->output .= ' = ' . ValueFormatter::format($this->value, $this->type, $this->printIndent()) . ';';
239        }
240
241        return $this->output;
242    }
243
244    /**
245     * Print property
246     *
247     * @return string
248     */
249    public function __toString(): string
250    {
251        return $this->render();
252    }
253
254}