1<?php
2
3/**
4 * @see       https://github.com/laminas/laminas-stdlib for the canonical source repository
5 * @copyright https://github.com/laminas/laminas-stdlib/blob/master/COPYRIGHT.md
6 * @license   https://github.com/laminas/laminas-stdlib/blob/master/LICENSE.md New BSD License
7 */
8
9namespace Laminas\Stdlib;
10
11use Traversable;
12
13use function array_shift;
14use function is_array;
15use function is_callable;
16use function method_exists;
17use function preg_replace_callback;
18use function sprintf;
19use function str_replace;
20use function strtolower;
21use function ucwords;
22
23abstract class AbstractOptions implements ParameterObjectInterface
24{
25    // @codingStandardsIgnoreStart
26    /**
27     * We use the __ prefix to avoid collisions with properties in
28     * user-implementations.
29     *
30     * @var bool
31     */
32    protected $__strictMode__ = true;
33    // @codingStandardsIgnoreEnd
34
35    /**
36     * Constructor
37     *
38     * @param  array|Traversable|null $options
39     */
40    public function __construct($options = null)
41    {
42        if (null !== $options) {
43            $this->setFromArray($options);
44        }
45    }
46
47    /**
48     * Set one or more configuration properties
49     *
50     * @param  array|Traversable|AbstractOptions $options
51     * @throws Exception\InvalidArgumentException
52     * @return AbstractOptions Provides fluent interface
53     */
54    public function setFromArray($options)
55    {
56        if ($options instanceof self) {
57            $options = $options->toArray();
58        }
59
60        if (! is_array($options) && ! $options instanceof Traversable) {
61            throw new Exception\InvalidArgumentException(
62                sprintf(
63                    'Parameter provided to %s must be an %s, %s or %s',
64                    __METHOD__,
65                    'array',
66                    'Traversable',
67                    'Laminas\Stdlib\AbstractOptions'
68                )
69            );
70        }
71
72        foreach ($options as $key => $value) {
73            $this->__set($key, $value);
74        }
75
76        return $this;
77    }
78
79    /**
80     * Cast to array
81     *
82     * @return array
83     */
84    public function toArray()
85    {
86        $array = [];
87        $transform = function ($letters) {
88            $letter = array_shift($letters);
89            return '_' . strtolower($letter);
90        };
91        foreach ($this as $key => $value) {
92            if ($key === '__strictMode__') {
93                continue;
94            }
95            $normalizedKey = preg_replace_callback('/([A-Z])/', $transform, $key);
96            $array[$normalizedKey] = $value;
97        }
98        return $array;
99    }
100
101    /**
102     * Set a configuration property
103     *
104     * @see ParameterObject::__set()
105     * @param string $key
106     * @param mixed $value
107     * @throws Exception\BadMethodCallException
108     * @return void
109     */
110    public function __set($key, $value)
111    {
112        $setter = 'set' . str_replace('_', '', $key);
113
114        if (is_callable([$this, $setter])) {
115            $this->{$setter}($value);
116
117            return;
118        }
119
120        if ($this->__strictMode__) {
121            throw new Exception\BadMethodCallException(sprintf(
122                'The option "%s" does not have a callable "%s" ("%s") setter method which must be defined',
123                $key,
124                'set' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))),
125                $setter
126            ));
127        }
128    }
129
130    /**
131     * Get a configuration property
132     *
133     * @see ParameterObject::__get()
134     * @param string $key
135     * @throws Exception\BadMethodCallException
136     * @return mixed
137     */
138    public function __get($key)
139    {
140        $getter = 'get' . str_replace('_', '', $key);
141
142        if (is_callable([$this, $getter])) {
143            return $this->{$getter}();
144        }
145
146        throw new Exception\BadMethodCallException(sprintf(
147            'The option "%s" does not have a callable "%s" getter method which must be defined',
148            $key,
149            'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)))
150        ));
151    }
152
153    /**
154     * Test if a configuration property is null
155     * @see ParameterObject::__isset()
156     * @param string $key
157     * @return bool
158     */
159    public function __isset($key)
160    {
161        $getter = 'get' . str_replace('_', '', $key);
162
163        return method_exists($this, $getter) && null !== $this->__get($key);
164    }
165
166    /**
167     * Set a configuration property to NULL
168     *
169     * @see ParameterObject::__unset()
170     * @param string $key
171     * @throws Exception\InvalidArgumentException
172     * @return void
173     */
174    public function __unset($key)
175    {
176        try {
177            $this->__set($key, null);
178        } catch (Exception\BadMethodCallException $e) {
179            throw new Exception\InvalidArgumentException(
180                'The class property $' . $key . ' cannot be unset as'
181                . ' NULL is an invalid value for it',
182                0,
183                $e
184            );
185        }
186    }
187}
188