Formatter::formats()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
3
namespace Benrowe\Formatter;
4
5
use \ReflectionObject;
6
use \ReflectionMethod;
7
use \Closure;
8
use InvalidArgumentException;
9
10
/**
11
 * Formatter
12
 * Enables values to be formatted
13
 *
14
 * @package Benrowe\Formatter
15
 */
16
class Formatter extends AbstractFormatterProvider
17
{
18
    /**
19
     * If no formatter is specified, this formatter is used by default
20
     *
21
     * @var string
22
     */
23
    protected $defaultFormatter;
24
25
    /**
26
     * The list of available formatter providers.
27
     * The key is the same key that is exposed in the format() method
28
     * The value is either a Closure, a FQC, or an object that implements the
29
     * FormatterProvider interface
30
     *
31
     * @var array list of formatters & providers|closures
32
     */
33
    private $providers = [];
34
35
    /**
36
     * A list of all the available formats. If a formatter is an instance of
37
     * FormatterProvider, it's list is exploded using dot notiation.
38
     *
39
     * @var string[]
40
     */
41
    private $formats = [];
42
43
    private $formatMethodPrefix = 'as';
44
45
    /**
46
     * Constructor
47
     *
48
     * @param array $formatters The formatters to provide, either as instances
49
     *                          of FormatterProvider or closures
50
     */
51 13
    public function __construct(array $formatters = [])
52
    {
53 13
        foreach ($formatters as $formatter => $closure) {
54 13
            $this->addFormatter($formatter, $closure);
55 13
        }
56 13
    }
57
58
    /**
59
     * Set the default formatter to use
60
     *
61
     * @param string $format
62
     */
63 13
    public function setDefaultFormatter($format)
64
    {
65 13
        if (!$this->hasFormat($format)) {
66 1
            throw new InvalidArgumentException(
67 1
                'format "'.$format.'" does not exist'
68 1
            );
69
        }
70 13
        $this->defaultFormatter = $format;
71 13
    }
72
73
    /**
74
     * Get the default formatter
75
     *
76
     * @return string
77
     */
78 1
    public function getDefaultFormatter()
79
    {
80 1
        return $this->defaultFormatter;
81
    }
82
83
    /**
84
     * Add a new or replace a formatter within the stack
85
     *
86
     * @param string $name   The name of the formatter
87
     * @param Closure|FormatterProvider $method the object executes the format
88
     * @throws InvalidArgumentException
89
     */
90 13
    public function addFormatter($name, $method)
91
    {
92 13
        $this->validateProviderName($name);
93 13
        $method = $this->getFormatterObject($method);
94
95 13
        if (!($method instanceof FormatterProvider || $method instanceof Closure)) {
96 1
            throw new InvalidArgumentException('Supplied formatter is not supported');
97
        }
98 13
        $name = strtolower($name);
99 13
        $this->providers[$name] = $method;
100
101
        // generate a list of formats from this method
102 13
        $this->formats = array_merge(
103 13
            $this->formats,
104 13
            $this->getFormatsFromFormatter($method, $name)
105 13
        );
106
107 13
        $this->checkDefaultFormatter();
108 13
    }
109
110
    /**
111
     * Detect and convert the FQN of a formatter provider into an instance
112
     *
113
     * @param  FormatterProvider|Closure|string $formatter
114
     * @return FormatterProvider|Closure
0 ignored issues
show
Documentation introduced by
Should the return type not be string|object?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
115
     */
116 13
    private function getFormatterObject($formatter)
117
    {
118 13
        if (is_string($formatter) && class_exists($formatter)) {
119 4
            $formatter = new $formatter;
120 4
        }
121 13
        return $formatter;
122
    }
123
124
    /**
125
     * Check the default format and set the default if we have at least
126
     * one formatter
127
     *
128
     * @return nil|null
129
     */
130 13
    private function checkDefaultFormatter()
131
    {
132 13
        if (!$this->defaultFormatter) {
133 13
            $format = current($this->formats);
134 13
            if ($format !== false) {
135 13
                $this->setDefaultFormatter($format);
136 13
            }
137 13
        }
138 13
    }
139
140
    /**
141
     * Get a list of available formats from the supplied formatter
142
     *
143
     * @param  Closure|FormatterProvider $formatter
144
     * @param  string $name Base name of the formatter
145
     * @return string[]
146
     */
147 13
    private function getFormatsFromFormatter($formatter, $name)
148
    {
149 13
        if ($formatter instanceof Closure) {
150 9
            return [$name];
151
        }
152 4
        $formats = $formatter->formats();
153
        // prefix each formatter of the object with the name of the formatter
154 4
        return array_map(function ($value) use ($name) {
155 4
            return $name . '.' . $value;
156 4
        }, $formats);
157
    }
158
159
    /**
160
     * Format the provided value based on the requested formatter
161
     *
162
     * @param mixed $value The value to format
163
     * @param string|array The format + format options, if an array is provided the first value is the formatter
164
     *                     and the other values are format params
165
     * @return mixed
166
     * @throws InvalidArgumentException
167
     */
168 6
    public function format($value, $format = null)
169
    {
170 6
        $format = $format ?: $this->defaultFormatter;
171
172 6
        list($format, $params) = $this->extractFormatAndParams($value, $format);
173
174 6
        if (!$this->hasFormat($format)) {
175 1
            throw new InvalidArgumentException(
176 1
                'Unknown format: "' . $format . '"'
177 1
            );
178
        }
179
180 4
        return $this->callFormatter($format, $params);
181
    }
182
183
    /**
184
     * Get the current list of available formats
185
     *
186
     * @return string[]
187
     */
188 1
    public function formats()
189
    {
190 1
        return $this->formats;
191
    }
192
193
    /**
194
     * Allow dynamic calls to be made to the formatter
195
     *
196
     * @todo Is this still needed?
197
     */
198 2
    public function __call($method, $params)
199
    {
200 2
        $format = strtolower(substr($method, strlen($this->formatMethodPrefix)));
201 2
        $value = array_shift($params);
202 2
        array_unshift($params, $format);
203 2
        return $this->format($value, $params);
204
    }
205
206
    /**
207
     * Determine if the format exists within the formatter.
208
     *
209
     * @return boolean
210
     * @throws InvalidArgumentException
211
     */
212 13
    public function hasFormat($format)
213
    {
214 13
        if (!preg_match("/^[A-Za-z_]+(\.[A-Za-z_]+)?$/", $format)) {
215 2
            throw new InvalidArgumentException(
216 2
                'Format "' . $format . '" is not provided in correct format'
217 2
            );
218
        }
219 13
        return in_array(strtolower($format), $this->formats);
220
    }
221
222
    /**
223
     * Validate the provider name
224
     *
225
     * @param  string $name
226
     * @return boolean
227
     */
228 13
    private function validateProviderName($name)
229
    {
230 13
        if (!preg_match("/^[\w]+$/", $name)) {
231 1
            throw new InvalidArgumentException(
232 1
                'Supplied formatter name "'.$name.'" contains invalid characters'
233 1
            );
234
        }
235 13
        return true;
236
    }
237
238
    /**
239
     * Calls the requested formatting method based on it's simple formatting name
240
     * + passes through the requested params
241
     *
242
     * @param  string $format
243
     * @param  array  $params the first is the value
244
     * @return mixed
245
     */
246 4
    private function callFormatter($format, array $params)
247
    {
248
        // is the formatter in a custom defined
249 4
        if (strpos($format, '.') > 0) {
250
            // provider
251 1
            list($provider, $format) = explode('.', $format, 2);
252
253 1
            $callback = $this->providers[$provider];
254 1
            $func = [$callback, 'format'];
255
256
            // extract the value from the params
257 1
            $value = array_shift($params);
258
259
            // push the format to the front of the params
260 1
            array_unshift($params, $format);
261 1
            $params = [$value, $params];
262 1
        } else {
263
            // Closure
264 3
            $callback = $this->providers[$format];
265 3
            $func = $callback->bindTo($this);
266
        }
267
268 4
        return call_user_func_array($func, $params);
269
    }
270
}
271