Issues (64)

src/Configurator/Items/Regexp.php (1 issue)

1
<?php
2
3
/**
4
* @package   s9e\TextFormatter
5
* @copyright Copyright (c) The s9e authors
6
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7
*/
8
namespace s9e\TextFormatter\Configurator\Items;
9
10
use InvalidArgumentException;
11
use s9e\TextFormatter\Configurator\ConfigProvider;
12
use s9e\TextFormatter\Configurator\FilterableConfigValue;
13
use s9e\TextFormatter\Configurator\Helpers\RegexpParser;
14
use s9e\TextFormatter\Configurator\JavaScript\Code;
15
use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
16
17
class Regexp implements ConfigProvider, FilterableConfigValue
18
{
19
	/**
20
	* @var bool Whether this regexp should have the global flag set in JavaScript
21
	*/
22
	protected $isGlobal;
23
24
	/**
25
	* @var string JavaScript regexp, with delimiters and modifiers, e.g. "/foo/i"
26
	*/
27
	protected $jsRegexp;
28
29
	/**
30
	* @var string PCRE regexp, with delimiters and modifiers, e.g. "/foo/i"
31
	*/
32
	protected $regexp;
33
34
	/**
35
	* Constructor
36
	*
37
	* @param string $regexp   PCRE regexp, with delimiters and modifiers, e.g. "/foo/i"
38
	* @param bool   $isGlobal Whether this regexp should have the global flag set in JavaScript
39
	*/
40 13
	public function __construct($regexp, $isGlobal = false)
41
	{
42 13
		if (@preg_match($regexp, '') === false)
43
		{
44 1
			throw new InvalidArgumentException('Invalid regular expression ' . var_export($regexp, true));
45
		}
46
47 12
		$this->regexp   = $regexp;
48 12
		$this->isGlobal = $isGlobal;
49
	}
50
51
	/**
52
	* Return this regexp as a string
53
	*
54
	* @return string
55
	*/
56 2
	public function __toString()
57
	{
58 2
		return $this->regexp;
59
	}
60
61
	/**
62
	* {@inheritdoc}
63
	*/
64 1
	public function asConfig()
65
	{
66 1
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type s9e\TextFormatter\Configurator\Items\Regexp which is incompatible with the return type mandated by s9e\TextFormatter\Config...figProvider::asConfig() of array|null|s9e\TextForma...r\JavaScript\Dictionary.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
67
	}
68
69
	/**
70
	* {@inheritdoc}
71
	*/
72 2
	public function filterConfig($target)
73
	{
74 2
		return ($target === 'JS') ? new Code($this->getJS()) : (string) $this;
75
	}
76
77
	/**
78
	* Return the name of each capture in this regexp
79
	*
80
	* @return string[]
81
	*/
82 1
	public function getCaptureNames()
83
	{
84 1
		return RegexpParser::getCaptureNames($this->regexp);
85
	}
86
87
	/**
88
	* Return this regexp's JavaScript representation
89
	*
90
	* @return string
91
	*/
92 4
	public function getJS()
93
	{
94 4
		if (!isset($this->jsRegexp))
95
		{
96 3
			$this->jsRegexp = RegexpConvertor::toJS($this->regexp, $this->isGlobal);
97
		}
98
99 4
		return $this->jsRegexp;
100
	}
101
102
	/**
103
	* Return all the named captures with a standalone regexp that matches them
104
	*
105
	* @return array Array of [capture name => regexp]
106
	*/
107 3
	public function getNamedCaptures()
108
	{
109 3
		$captures   = [];
110 3
		$regexpInfo = RegexpParser::parse($this->regexp);
111
112
		// Prepare the start/end of the regexp and ensure that we use the D modifier
113 3
		$start = $regexpInfo['delimiter'] . '^';
114 3
		$end   = '$' . $regexpInfo['delimiter'] . $regexpInfo['modifiers'];
115 3
		if (strpos($regexpInfo['modifiers'], 'D') === false)
116
		{
117 2
			$end .= 'D';
118
		}
119
120 3
		foreach ($this->getNamedCapturesExpressions($regexpInfo['tokens']) as $name => $expr)
121
		{
122 3
			$captures[$name] = $start . $expr . $end;
123
		}
124
125 3
		return $captures;
126
	}
127
128
	/**
129
	* Return the expression used in each named capture
130
	*
131
	* @param  array[] $tokens
132
	* @return array
133
	*/
134 3
	protected function getNamedCapturesExpressions(array $tokens)
135
	{
136 3
		$exprs = [];
137 3
		foreach ($tokens as $token)
138
		{
139 3
			if ($token['type'] !== 'capturingSubpatternStart' || !isset($token['name']))
140
			{
141 3
				continue;
142
			}
143
144 3
			$expr = $token['content'];
145 3
			if (strpos($expr, '|') !== false)
146
			{
147 1
				$expr = '(?:' . $expr . ')';
148
			}
149
150 3
			$exprs[$token['name']] = $expr;
151
		}
152
153 3
		return $exprs;
154
	}
155
156
	/**
157
	* Set this regexp's JavaScript representation
158
	*
159
	* @param  string $jsRegexp
160
	* @return void
161
	*/
162 1
	public function setJS($jsRegexp)
163
	{
164 1
		$this->jsRegexp = $jsRegexp;
165
	}
166
}