1
|
|
|
<?php namespace Tarsana\Functional; |
|
|
|
|
2
|
|
|
/** |
3
|
|
|
* This file contains some useful String functions. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Curried version of `explode()`. |
8
|
|
|
* ```php |
9
|
|
|
* $words = split(' '); |
10
|
|
|
* $words('Hello World'); // ['Hello', 'World'] |
11
|
|
|
* ``` |
12
|
|
|
* |
13
|
|
|
* @signature String -> String -> [String] |
14
|
|
|
* @param string $delimiter |
|
|
|
|
15
|
|
|
* @param string $string |
|
|
|
|
16
|
|
|
* @return array |
17
|
|
|
*/ |
18
|
|
|
function split() { |
19
|
|
|
return apply(curry('explode'), func_get_args()); |
20
|
|
|
} |
21
|
|
|
|
22
|
|
|
/** |
23
|
|
|
* Curried version of `implode()`. |
24
|
|
|
* ```php |
25
|
|
|
* $sentence = join(' '); |
26
|
|
|
* $sentence(['Hello', 'World']); // 'Hello World' |
27
|
|
|
* ``` |
28
|
|
|
* |
29
|
|
|
* @signature String -> [String] -> String |
30
|
|
|
* @param string $glue |
|
|
|
|
31
|
|
|
* @param array $pieces |
|
|
|
|
32
|
|
|
* @return string |
33
|
|
|
*/ |
34
|
|
|
function join() { |
35
|
|
|
return apply(curry(function($glue, $pieces){ |
36
|
|
|
return implode($glue, $pieces); |
37
|
|
|
}), func_get_args()); |
38
|
|
|
} |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* Curried version of `str_replace()`. |
42
|
|
|
* ```php |
43
|
|
|
* $string = 'a b c d e f'; |
44
|
|
|
* $noSpace = replace(' ', ''); |
45
|
|
|
* $noSpace($string); // 'abcdef' |
46
|
|
|
* replace(['a', 'b', ' '], '', $string) // 'cdef' |
47
|
|
|
* replace(['a', 'e', ' '], ['x', 'y', ''], $string); // 'xbcdyf' |
48
|
|
|
* ``` |
49
|
|
|
* |
50
|
|
|
* @signature String|[String] -> String -> String|[String] -> String |
51
|
|
|
* @param string $search |
|
|
|
|
52
|
|
|
* @param string $replacement |
|
|
|
|
53
|
|
|
* @param string $string |
|
|
|
|
54
|
|
|
* @return string |
55
|
|
|
*/ |
56
|
|
|
function replace() { |
57
|
|
|
return apply(curry('str_replace'), func_get_args()); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Curried version of `preg_replace()`. |
62
|
|
|
* ```php |
63
|
|
|
* $string = 'A12;b_{F}|d'; |
64
|
|
|
* $aplha = regReplace('/[^a-z]+/i', ''); |
65
|
|
|
* $alpha($string); // 'AbFd' |
66
|
|
|
* ``` |
67
|
|
|
* |
68
|
|
|
* @signature String -> String -> String -> String |
69
|
|
|
* @param string $pattern |
|
|
|
|
70
|
|
|
* @param string $replacement |
|
|
|
|
71
|
|
|
* @param string $string |
|
|
|
|
72
|
|
|
* @return string |
73
|
|
|
*/ |
74
|
|
|
function regReplace() { |
75
|
|
|
return apply(curry('preg_replace'), func_get_args()); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* Alias of `strtoupper`. |
80
|
|
|
* ```php |
81
|
|
|
* upperCase('hello') // 'HELLO' |
82
|
|
|
* ``` |
83
|
|
|
* |
84
|
|
|
* @signature String -> String |
85
|
|
|
* @param string $string |
86
|
|
|
* @return string |
87
|
|
|
*/ |
88
|
|
|
function upperCase($string) { |
89
|
|
|
return strtoupper($string); |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Alias of `strtolower`. |
94
|
|
|
* ```php |
95
|
|
|
* lowerCase('HELLO') // 'hello' |
96
|
|
|
* ``` |
97
|
|
|
* |
98
|
|
|
* @signature String -> String |
99
|
|
|
* @param string $string |
100
|
|
|
* @return string |
101
|
|
|
*/ |
102
|
|
|
function lowerCase($string) { |
103
|
|
|
return strtolower($string); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Gets the camlCase version of a string. |
108
|
|
|
* ```php |
109
|
|
|
* camelCase('Yes, we can! 123') // 'yesWeCan123' |
110
|
|
|
* ``` |
111
|
|
|
* |
112
|
|
|
* @signature String -> String |
113
|
|
|
* @param string $string |
114
|
|
|
* @return string |
115
|
|
|
*/ |
116
|
|
|
function camelCase($string) { |
117
|
|
|
return apply(pipe( |
118
|
|
|
regReplace('/[^a-z0-9]+/i', ' '), |
119
|
|
|
'trim', |
120
|
|
|
'ucwords', |
121
|
|
|
replace(' ', ''), |
122
|
|
|
'lcfirst' |
123
|
|
|
), [$string]); |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
/** |
127
|
|
|
* Gets the snake-case of the string using `$delimiter` as separator. |
128
|
|
|
* ``` |
129
|
|
|
* $underscoreCase = snakeCase('_'); |
130
|
|
|
* $underscoreCase('IAm-Happy'); // i_am_happy |
131
|
|
|
* ``` |
132
|
|
|
* |
133
|
|
|
* @signature String -> String -> String |
134
|
|
|
* @param string $delimiter |
|
|
|
|
135
|
|
|
* @param string $string |
|
|
|
|
136
|
|
|
* @return string |
137
|
|
|
*/ |
138
|
|
|
function snakeCase() { |
139
|
|
|
$snackCase = function($delimiter, $string) { |
140
|
|
|
return apply(pipe( |
141
|
|
|
regReplace('/([A-Z])/', ' \\1'), |
142
|
|
|
regReplace('/([0-9]+)/', ' \\1'), |
143
|
|
|
regReplace('/[^a-z0-9]+/i', ' '), |
144
|
|
|
'trim', |
145
|
|
|
'strtolower', |
146
|
|
|
replace(' ', $delimiter) |
147
|
|
|
), [$string]); |
148
|
|
|
}; |
149
|
|
|
return apply(curry($snackCase), func_get_args()); |
150
|
|
|
}; |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Checks if `$string` starts with `$token`. |
154
|
|
|
* ```php |
155
|
|
|
* $http = startsWith('http://'); |
156
|
|
|
* $http('http://gitbub.com'); // true |
157
|
|
|
* $http('gitbub.com'); // false |
158
|
|
|
* ``` |
159
|
|
|
* |
160
|
|
|
* @signature String -> String -> Boolean |
161
|
|
|
* @param string $token |
|
|
|
|
162
|
|
|
* @param string $string |
|
|
|
|
163
|
|
|
* @return bool |
164
|
|
|
*/ |
165
|
|
View Code Duplication |
function startsWith() { |
|
|
|
|
166
|
|
|
$startsWith = function($token, $string) { |
167
|
|
|
return ( |
168
|
|
|
strlen($token) <= strlen($string) && |
169
|
|
|
substr($string, 0, strlen($token)) === $token |
170
|
|
|
); |
171
|
|
|
}; |
172
|
|
|
return apply(curry($startsWith), func_get_args()); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
/** |
176
|
|
|
* Checks if `$string` ends with `$token`. |
177
|
|
|
* ```php |
178
|
|
|
* $dotCom = endsWith('.com'); |
179
|
|
|
* $dotCom('http://gitbub.com'); // true |
180
|
|
|
* $dotCom('php.net'); // false |
181
|
|
|
* ``` |
182
|
|
|
* |
183
|
|
|
* @signature String -> String -> Boolean |
184
|
|
|
* @param string $token |
|
|
|
|
185
|
|
|
* @param string $string |
|
|
|
|
186
|
|
|
* @return bool |
187
|
|
|
*/ |
188
|
|
View Code Duplication |
function endsWith() { |
|
|
|
|
189
|
|
|
$endsWith = function($token, $string) { |
190
|
|
|
return ( |
191
|
|
|
strlen($token) <= strlen($string) && |
192
|
|
|
substr($string, - strlen($token)) === $token |
193
|
|
|
); |
194
|
|
|
}; |
195
|
|
|
return apply(curry($endsWith), func_get_args()); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Checks if a string matches a regular expression. |
200
|
|
|
* ```php |
201
|
|
|
* $numeric = test('/^[0-9.]+$/'); |
202
|
|
|
* $numeric('123.43'); // true |
203
|
|
|
* $numeric('12a3.43'); // false |
204
|
|
|
* ``` |
205
|
|
|
* |
206
|
|
|
* @signature String -> String -> Boolean |
207
|
|
|
* @param string $pattern |
|
|
|
|
208
|
|
|
* @param string $string |
|
|
|
|
209
|
|
|
* @return bool |
210
|
|
|
*/ |
211
|
|
|
function test() { |
212
|
|
|
$test = function($pattern, $string) { |
213
|
|
|
return 1 === preg_match($pattern, $string); |
214
|
|
|
}; |
215
|
|
|
return apply(curry($test), func_get_args()); |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Performs a global regular expression match |
220
|
|
|
* and returns array of results. |
221
|
|
|
* ```php |
222
|
|
|
* $numbers = match('/[0-9.]+/'); |
223
|
|
|
* $numbers('Hello World'); // [] |
224
|
|
|
* $numbers('12 is 4 times 3'); // ['12', '4', '3'] |
225
|
|
|
* ``` |
226
|
|
|
* |
227
|
|
|
* @signature String -> String -> [String] |
228
|
|
|
* @param string $pattern |
|
|
|
|
229
|
|
|
* @param string $string |
|
|
|
|
230
|
|
|
* @return array |
231
|
|
|
*/ |
232
|
|
|
function match() { |
233
|
|
|
$match = function($pattern, $string) { |
234
|
|
|
$results = []; |
235
|
|
|
preg_match_all($pattern, $string, $results); |
236
|
|
|
return $results[0]; |
237
|
|
|
}; |
238
|
|
|
return apply(curry($match), func_get_args()); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* Curried version of `substr_count` with changed order of parameters, |
243
|
|
|
* ```php |
244
|
|
|
* $spaces = occurences(' '); |
245
|
|
|
* $spaces('Hello') // 0 |
246
|
|
|
* $spaces('12 is 4 times 3'); // 4 |
247
|
|
|
* ``` |
248
|
|
|
* |
249
|
|
|
* @signature String -> String -> Number |
250
|
|
|
* @param string $token |
|
|
|
|
251
|
|
|
* @param string $text |
|
|
|
|
252
|
|
|
* @return int |
253
|
|
|
*/ |
254
|
|
|
function occurences() { |
255
|
|
|
$occurences = function($token, $text) { |
256
|
|
|
return substr_count($text, $token); |
257
|
|
|
}; |
258
|
|
|
return apply(curry($occurences), func_get_args()); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* Splits a string into chunks without spliting any group surrounded with some |
263
|
|
|
* specified characters. `$surrounders` is a string where each pair of characters |
264
|
|
|
* specifies the starting and ending characters of a group that should not be split. |
265
|
|
|
* ```php |
266
|
|
|
* $groups = chunks('(){}', ','); |
267
|
|
|
* $groups('1,2,(3,4,5),{6,(7,8)},9'); // ['1', '2', '(3,4,5)', '{6,(7,8)}', '9'] |
268
|
|
|
* |
269
|
|
|
* $names = chunks('()""', ' '); |
270
|
|
|
* $names('Foo "Bar Baz" (Some other name)'); // ['Foo', 'Bar Baz', 'Some other name'] |
271
|
|
|
* ``` |
272
|
|
|
* |
273
|
|
|
* @signature String -> String -> String -> [String] |
274
|
|
|
* @param string $surrounders |
|
|
|
|
275
|
|
|
* @param string $separator |
|
|
|
|
276
|
|
|
* @param sring $text |
|
|
|
|
277
|
|
|
* @return array |
278
|
|
|
*/ |
279
|
|
|
function chunks() { |
280
|
|
|
$chunks = function($surrounders, $separator, $text) { |
281
|
|
|
// Let's assume some values to understand how this function works |
282
|
|
|
// surrounders = '""{}()' |
283
|
|
|
// separator = ' ' |
284
|
|
|
// $text = 'foo ("bar baz" alpha) beta' |
285
|
|
|
|
286
|
|
|
$surrounders = map(slices(1), slices(2, $surrounders)); // [['"'. '"'], ['{'. '}'], ['(', ')']] |
|
|
|
|
287
|
|
|
$openings = map(get(0), $surrounders); // ['"', '{', '('] |
|
|
|
|
288
|
|
|
$closings = map(get(1), $surrounders); // ['"', '}', ')'] |
|
|
|
|
289
|
|
|
$numOfSurrounders = length($surrounders); // 3 |
290
|
|
|
$indexes = keys($surrounders); // [0, 1, 2] |
|
|
|
|
291
|
|
|
|
292
|
|
|
$items = split($separator, $text); // ['foo', '("bar', 'baz"', 'alpha)', 'beta'] |
|
|
|
|
293
|
|
|
|
294
|
|
|
// The initial state |
295
|
|
|
$state = (object) [ |
296
|
|
|
'chunks' => [], //: the resulting chunks |
297
|
|
|
'counts' => array_fill(0, $numOfSurrounders, 0), // [0, 0, 0] : count of openings not closed yet |
298
|
|
|
'total' => 0 //: total of not closed openings |
299
|
|
|
]; |
300
|
|
|
// We will iterate over $items and update the $state while adding them |
301
|
|
|
// For each item we need to update counts and chunks |
302
|
|
|
|
303
|
|
|
// Updates count for a single surrender (the surrender at $index) |
304
|
|
|
// $item : the item we are adding |
305
|
|
|
// $counts : the previous counts |
306
|
|
|
$updateCountAt = curry(function($item, $counts, $index) use($openings, $closings) { |
307
|
|
|
$count = occurences(__(), $item); |
308
|
|
|
return ($openings[$index] == $closings[$index]) ? |
309
|
|
|
($counts[$index] + $count($openings[$index])) % 2 : |
310
|
|
|
$counts[$index] + $count($openings[$index]) - $count($closings[$index]); |
311
|
|
|
}); |
312
|
|
|
// Updates counts for all surrenders |
313
|
|
|
$updateCounts = curry(function($item, $counts) use($indexes, $updateCountAt) { |
314
|
|
|
return map($updateCountAt($item, $counts), $indexes); |
315
|
|
|
}); |
316
|
|
|
// Adds an item to the state and returns a new state |
317
|
|
|
$addItem = function($state, $item) use ($separator, $updateCounts){ |
318
|
|
|
$counts = $updateCounts($item, get('counts', $state)); |
319
|
|
|
$newChunks = (0 == $state->total) // if all openings are closed |
320
|
|
|
? append($item, $state->chunks) // then add a new chunk |
321
|
|
|
// else append the item to the last chunk using the separator as glue |
322
|
|
|
: append(last($state->chunks) . $separator . $item, init($state->chunks)); |
323
|
|
|
return (object) [ |
324
|
|
|
'chunks' => $newChunks, |
325
|
|
|
'counts' => $counts, |
326
|
|
|
'total' => sum($counts) |
327
|
|
|
]; |
328
|
|
|
}; |
329
|
|
|
// Returns the chunks of the resulting state after adding all items |
330
|
|
|
return get('chunks', reduce($addItem, $state, $items)); |
331
|
|
|
}; |
332
|
|
|
return apply(curry($chunks), func_get_args()); |
333
|
|
|
} |
334
|
|
|
|
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.