Completed
Push — master ( 6d356d...1799f4 )
by Ben
02:29
created

Formatter   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 209
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 30
Bugs 3 Features 6
Metric Value
wmc 24
c 30
b 3
f 6
lcom 1
cbo 2
dl 0
loc 209
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
A setDefaultFormatter() 0 9 2
A getDefaultFormatter() 0 4 1
B addFormatter() 0 24 6
A checkDefaultFormatter() 0 9 3
A getFormatsFromFormatter() 0 11 2
B format() 0 27 4
A formats() 0 4 1
A __call() 0 7 1
A hasFormat() 0 9 2
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
    public function __construct(array $formatters = [])
52
    {
53
        foreach ($formatters as $formatter => $closure) {
54
            $this->addFormatter($formatter, $closure);
55
        }
56
    }
57
58
    /**
59
     * Set the default formatter to use
60
     *
61
     * @param string $format
62
     */
63
    public function setDefaultFormatter($format)
64
    {
65
        if (!$this->hasFormat($format)) {
66
            throw new InvalidArgumentException(
67
                'format "'.$format.'" does not exist'
68
            );
69
        }
70
        $this->defaultFormatter = $format;
71
    }
72
73
    /**
74
     * Get the default formatter
75
     *
76
     * @return string
77
     */
78
    public function getDefaultFormatter()
79
    {
80
        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
     */
89
    public function addFormatter($name, $method)
90
    {
91
        if (!preg_match("/^[\w]+$/", $name)) {
92
            throw new InvalidArgumentException(
93
                'Supplied formatter name "'.$name.'" contains invalid characters'
94
            );
95
        }
96
        if (is_string($method) && class_exists($method)) {
97
            $method = new $method;
98
        }
99
        if (!($method instanceof FormatterProvider || $method instanceof Closure)) {
100
            throw new InvalidArgumentException('Supplied formatter is not supported');
101
        }
102
        $name = strtolower($name);
103
        $this->providers[$name] = $method;
104
105
        // generate a list of formats from this method
106
        $this->formats = array_merge(
107
            $this->formats,
108
            $this->getFormatsFromFormatter($method, $name)
109
        );
110
111
        $this->checkDefaultFormatter();
112
    }
113
114
    /**
115
     * Check the default format and set the default if we have at least
116
     * one formatter
117
     *
118
     * @return nil
0 ignored issues
show
Documentation introduced by
Should the return type not be nil|null?

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...
119
     */
120
    private function checkDefaultFormatter()
121
    {
122
        if (!$this->defaultFormatter) {
123
            $format = current($this->formats);
124
            if ($format) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $format of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
125
                $this->setDefaultFormatter($format);
126
            }
127
        }
128
    }
129
130
    /**
131
     * Get a list of available formats from the supplied formatter
132
     *
133
     * @param  Closure|FormatterProvider $formatter
134
     * @param  string $name Base name of the formatter
135
     * @return string[]
136
     */
137
    private function getFormatsFromFormatter($formatter, $name)
138
    {
139
        if ($formatter instanceof Closure) {
140
            return [$name];
141
        }
142
        $formats = $formatter->formats();
143
        // prefix each formatter of the object with the name of the formatter
144
        return array_map(function ($value) use ($name) {
145
            return $name . '.' . $value;
146
        }, $formats);
147
    }
148
149
    /**
150
     * Format the provided value based on the requested formatter
151
     *
152
     * @param mixed $value The value to format
153
     * @param string|array The format + format options, if an array is provided the first value is the formatter
154
     *                     and the other values are format params
155
     * @return mixed
156
     * @throws InvalidArgumentException
157
     */
158
    public function format($value, $format = null)
159
    {
160
        $format = $format ?: $this->defaultFormatter;
161
162
        list($format, $params) = $this->extractFormatAndParams($value, $format);
163
164
        if (!$this->hasFormat($format)) {
165
            throw new InvalidArgumentException(
166
                'Unknown format: "' . $format . '"'
167
            );
168
        }
169
170
        // is the formatter in a custom defined
171
        if (strpos($format, '.') > 0) {
172
            list($provider, $format) = explode($format, '.');
173
            $callback = $this->providers[$provider];
174
            $func = [$callback, 'format'];
175
            array_unshift($params, $format);
176
            $params = [$value, $params];
177
        } else {
178
            // Closure
179
            $callback = $this->providers[$format];
180
            $func = $callback->bindTo($this);
181
        }
182
183
        return call_user_func_array($func, $params);
184
    }
185
186
    /**
187
     * Get the current list of available formats
188
     *
189
     * @return array
190
     */
191
    public function formats()
192
    {
193
        return $this->formats;
194
    }
195
196
    /**
197
     * Allow dynamic calls to be made to the formatter
198
     *
199
     * @todo Is this still needed?
200
     */
201
    public function __call($method, $params)
202
    {
203
        $format = strtolower(substr($method, strlen($this->formatMethodPrefix)));
204
        $value = array_shift($params);
205
        array_unshift($params, $format);
206
        return $this->format($value, $params);
207
    }
208
209
    /**
210
     * Determine if the format exists within the formatter.
211
     *
212
     * @return boolean
213
     * @throws InvalidArgumentException
214
     */
215
    public function hasFormat($format)
216
    {
217
        if (!preg_match("/^[A-Za-z]+(\.[A-Za-z]+)?$/", $format)) {
218
            throw new InvalidArgumentException(
219
                'Format "' . $format . '" is not provided in correct format'
220
            );
221
        }
222
        return in_array(strtolower($format), $this->formats);
223
    }
224
}
225