6 |
7 | # QueryList
8 | `QueryList` is a simple, elegant, extensible PHP Web Scraper (crawler/spider) ,based on phpQuery.
9 |
10 | [API Documentation](https://github.com/jae-jae/QueryList/wiki)
11 |
12 | [中文文档](README-ZH.md)
13 |
14 | ## Features
15 | - Have the same CSS3 DOM selector as jQuery
16 | - Have the same DOM manipulation API as jQuery
17 | - Have a generic list crawling program
18 | - Have a strong HTTP request suite, easy to achieve such as: simulated landing, forged browser, HTTP proxy and other complex network requests
19 | - Have a messy code solution
20 | - Have powerful content filtering, you can use the jQuey selector to filter content
21 | - Has a high degree of modular design, scalability and strong
22 | - Have an expressive API
23 | - Has a wealth of plug-ins
24 |
25 | Through plug-ins you can easily implement things like:
26 | - Multithreaded crawl
27 | - Crawl JavaScript dynamic rendering page (PhantomJS/headless WebKit)
28 | - Image downloads to local
29 | - Simulate browser behavior such as submitting Form forms
30 | - Web crawler
31 | - .....
32 |
33 | ## Requirements
34 | - PHP >= 8.1
35 |
36 | ## Installation
37 | By Composer installation:
38 | ```
39 | composer require jaeger/querylist
40 | ```
41 |
42 | ## Usage
43 |
44 | #### DOM Traversal and Manipulation
45 | - Crawl「GitHub」all picture links
46 |
47 | ```php
48 | QueryList::get('https://github.com')->find('img')->attrs('src');
49 | ```
50 | - Crawl Google search results
51 |
52 | ```php
53 | $ql = QueryList::get('https://www.google.co.jp/search?q=QueryList');
54 |
55 | $ql->find('title')->text(); //The page title
56 | $ql->find('meta[name=keywords]')->content; //The page keywords
57 |
58 | $ql->find('h3>a')->texts(); //Get a list of search results titles
59 | $ql->find('h3>a')->attrs('href'); //Get a list of search results links
60 |
61 | $ql->find('img')->src; //Gets the link address of the first image
62 | $ql->find('img:eq(1)')->src; //Gets the link address of the second image
63 | $ql->find('img')->eq(2)->src; //Gets the link address of the third image
64 | // Loop all the images
65 | $ql->find('img')->map(function($img){
66 | echo $img->alt; //Print the alt attribute of the image
67 | });
68 | ```
69 | - More usage
70 |
71 | ```php
72 | $ql->find('#head')->append('
Append content
')->find('div')->htmls();
73 | $ql->find('.two')->children('img')->attrs('alt'); // Get the class is the "two" element under all img child nodes
74 | // Loop class is the "two" element under all child nodes
75 | $data = $ql->find('.two')->children()->map(function ($item){
76 | // Use "is" to determine the node type
77 | if($item->is('a')){
78 | return $item->text();
79 | }elseif($item->is('img'))
80 | {
81 | return $item->alt;
82 | }
83 | });
84 |
85 | $ql->find('a')->attr('href', 'newVal')->removeClass('className')->html('newHtml')->...
86 | $ql->find('div > p')->add('div > ul')->filter(':has(a)')->find('p:first')->nextAll()->andSelf()->...
87 | $ql->find('div.old')->replaceWith( $ql->find('div.new')->clone())->appendTo('.trash')->prepend('Deleted')->...
88 | ```
89 | #### List crawl
90 | Crawl the title and link of the Google search results list:
91 | ```php
92 | $data = QueryList::get('https://www.google.co.jp/search?q=QueryList')
93 | // Set the crawl rules
94 | ->rules([
95 | 'title'=>array('h3','text'),
96 | 'link'=>array('h3>a','href')
97 | ])
98 | ->query()->getData();
99 |
100 | print_r($data->all());
101 | ```
102 | Results:
103 | ```
104 | Array
105 | (
106 | [0] => Array
107 | (
108 | [title] => Angular - QueryList
109 | [link] => https://angular.io/api/core/QueryList
110 | )
111 | [1] => Array
112 | (
113 | [title] => QueryList | @angular/core - Angularリファレンス - Web Creative Park
114 | [link] => http://www.webcreativepark.net/angular/querylist/
115 | )
116 | [2] => Array
117 | (
118 | [title] => QueryListにQueryを追加したり、追加されたことを感知する | TIPS ...
119 | [link] => http://www.webcreativepark.net/angular/querylist_query_add_subscribe/
120 | )
121 | //...
122 | )
123 | ```
124 | #### Encode convert
125 | ```php
126 | // Out charset :UTF-8
127 | // In charset :GB2312
128 | QueryList::get('https://top.etao.com')->encoding('UTF-8','GB2312')->find('a')->texts();
129 |
130 | // Out charset:UTF-8
131 | // In charset:Automatic Identification
132 | QueryList::get('https://top.etao.com')->encoding('UTF-8')->find('a')->texts();
133 | ```
134 |
135 | #### HTTP Client (GuzzleHttp)
136 | - Carry cookie login GitHub
137 | ```php
138 | //Crawl GitHub content
139 | $ql = QueryList::get('https://github.com','param1=testvalue & params2=somevalue',[
140 | 'headers' => [
141 | // Fill in the cookie from the browser
142 | 'Cookie' => 'SINAGLOBAL=546064; wb_cmtLike_2112031=1; wvr=6;....'
143 | ]
144 | ]);
145 | //echo $ql->getHtml();
146 | $userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text();
147 | echo $userName;
148 | ```
149 | - Use the Http proxy
150 | ```php
151 | $urlParams = ['param1' => 'testvalue','params2' => 'somevalue'];
152 | $opts = [
153 | // Set the http proxy
154 | 'proxy' => 'http://222.141.11.17:8118',
155 | //Set the timeout time in seconds
156 | 'timeout' => 30,
157 | // Fake HTTP headers
158 | 'headers' => [
159 | 'Referer' => 'https://querylist.cc/',
160 | 'User-Agent' => 'testing/1.0',
161 | 'Accept' => 'application/json',
162 | 'X-Foo' => ['Bar', 'Baz'],
163 | 'Cookie' => 'abc=111;xxx=222'
164 | ]
165 | ];
166 | $ql->get('http://httpbin.org/get',$urlParams,$opts);
167 | // echo $ql->getHtml();
168 | ```
169 |
170 | - Analog login
171 | ```php
172 | // Post login
173 | $ql = QueryList::post('http://xxxx.com/login',[
174 | 'username' => 'admin',
175 | 'password' => '123456'
176 | ])->get('http://xxx.com/admin');
177 | // Crawl pages that need to be logged in to access
178 | $ql->get('http://xxx.com/admin/page');
179 | //echo $ql->getHtml();
180 | ```
181 |
182 | #### Submit forms
183 | Login GitHub
184 | ```php
185 | // Get the QueryList instance
186 | $ql = QueryList::getInstance();
187 | // Get the login form
188 | $form = $ql->get('https://github.com/login')->find('form');
189 |
190 | // Fill in the GitHub username and password
191 | $form->find('input[name=login]')->val('your github username or email');
192 | $form->find('input[name=password]')->val('your github password');
193 |
194 | // Serialize the form data
195 | $fromData = $form->serializeArray();
196 | $postData = [];
197 | foreach ($fromData as $item) {
198 | $postData[$item['name']] = $item['value'];
199 | }
200 |
201 | // Submit the login form
202 | $actionUrl = 'https://github.com'.$form->attr('action');
203 | $ql->post($actionUrl,$postData);
204 | // To determine whether the login is successful
205 | // echo $ql->getHtml();
206 | $userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text();
207 | if($userName)
208 | {
209 | echo 'Login successful ! Welcome:'.$userName;
210 | }else{
211 | echo 'Login failed !';
212 | }
213 | ```
214 | #### Bind function extension
215 | Customize the extension of a `myHttp` method:
216 | ```php
217 | $ql = QueryList::getInstance();
218 |
219 | //Bind a `myHttp` method to the QueryList object
220 | $ql->bind('myHttp',function ($url){
221 | // $this is the current QueryList object
222 | $html = file_get_contents($url);
223 | $this->setHtml($html);
224 | return $this;
225 | });
226 |
227 | // And then you can call by the name of the binding
228 | $data = $ql->myHttp('https://toutiao.io')->find('h3 a')->texts();
229 | print_r($data->all());
230 | ```
231 | Or package to class, and then bind:
232 | ```php
233 | $ql->bind('myHttp',function ($url){
234 | return new MyHttp($this,$url);
235 | });
236 | ```
237 |
238 | #### Plugin used
239 | - Use the PhantomJS plugin to crawl JavaScript dynamically rendered pages:
240 |
241 | ```php
242 | // Set the PhantomJS binary file path during installation
243 | $ql = QueryList::use(PhantomJs::class,'/usr/local/bin/phantomjs');
244 |
245 | // Crawl「500px」all picture links
246 | $data = $ql->browser('https://500px.com/editors')->find('img')->attrs('src');
247 | print_r($data->all());
248 |
249 | // Use the HTTP proxy
250 | $ql->browser('https://500px.com/editors',false,[
251 | '--proxy' => '192.168.1.42:8080',
252 | '--proxy-type' => 'http'
253 | ])
254 | ```
255 |
256 | - Using the CURL multithreading plug-in, multi-threaded crawling GitHub trending :
257 |
258 | ```php
259 | $ql = QueryList::use(CurlMulti::class);
260 | $ql->curlMulti([
261 | 'https://github.com/trending/php',
262 | 'https://github.com/trending/go',
263 | //.....more urls
264 | ])
265 | // Called if task is success
266 | ->success(function (QueryList $ql,CurlMulti $curl,$r){
267 | echo "Current url:{$r['info']['url']} \r\n";
268 | $data = $ql->find('h3 a')->texts();
269 | print_r($data->all());
270 | })
271 | // Task fail callback
272 | ->error(function ($errorInfo,CurlMulti $curl){
273 | echo "Current url:{$errorInfo['info']['url']} \r\n";
274 | print_r($errorInfo['error']);
275 | })
276 | ->start([
277 | // Maximum number of threads
278 | 'maxThread' => 10,
279 | // Number of error retries
280 | 'maxTry' => 3,
281 | ]);
282 |
283 | ```
284 |
285 | ## Plugins
286 | - [jae-jae/QueryList-PhantomJS](https://github.com/jae-jae/QueryList-PhantomJS):Use PhantomJS to crawl Javascript dynamically rendered page.
287 | - [jae-jae/QueryList-CurlMulti](https://github.com/jae-jae/QueryList-CurlMulti) : Curl multi threading.
288 | - [jae-jae/QueryList-AbsoluteUrl](https://github.com/jae-jae/QueryList-AbsoluteUrl) : Converting relative urls to absolute.
289 | - [jae-jae/QueryList-Rule-Google](https://github.com/jae-jae/QueryList-Rule-Google) : Google searcher.
290 | - [jae-jae/QueryList-Rule-Baidu](https://github.com/jae-jae/QueryList-Rule-Baidu) : Baidu searcher.
291 |
292 |
293 | View more QueryList plugins and QueryList-based products: [QueryList Community](https://github.com/jae-jae/QueryList-Community)
294 |
295 | ## Contributing
296 | Welcome to contribute code for the QueryList。About Contributing Plugins can be viewed:[QueryList Plugin Contributing Guide](https://github.com/jae-jae/QueryList-Community/blob/master/CONTRIBUTING.md)
297 |
298 | ## Author
299 | Jaeger
300 |
301 | If this library is useful for you, say thanks [buying me a beer :beer:](https://www.paypal.me/jaepay)!
302 |
303 | ## Lisence
304 | QueryList is licensed under the license of MIT. See the LICENSE for more details.
305 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jaeger/querylist",
3 | "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。",
4 | "keywords":["QueryList","phpQuery","spider"],
5 | "homepage": "http://querylist.cc",
6 | "require": {
7 | "PHP":">=8.1",
8 | "jaeger/phpquery-single": "^1",
9 | "ext-dom": "*",
10 | "symfony/var-dumper": ">3.4",
11 | "jaeger/g-http": "^2.0"
12 | },
13 | "suggest":{
14 |
15 | },
16 | "license": "MIT",
17 | "authors": [
18 | {
19 | "name": "Jaeger",
20 | "email": "JaegerCode@gmail.com"
21 | }
22 | ],
23 | "autoload":{
24 | "psr-4":{
25 | "QL\\":"src"
26 | },
27 | "files": [
28 | "src/Collect/Support/helpers.php",
29 | "src/Collect/Support/alias.php"
30 | ]
31 | },
32 | "autoload-dev": {
33 | "files": [
34 | "src/Collect/Support/helpers.php",
35 | "src/Collect/Support/alias.php"
36 | ],
37 | "psr-4": {
38 | "Tests\\": "tests/"
39 | }
40 | },
41 | "require-dev": {
42 | "phpunit/phpunit": "^8.5"
43 | },
44 | "scripts": {
45 | "test": "./vendor/bin/phpunit"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/Collect/Conditionable/HigherOrderWhenProxy.php:
--------------------------------------------------------------------------------
1 | target = $target;
44 | }
45 |
46 | /**
47 | * Set the condition on the proxy.
48 | *
49 | * @param bool $condition
50 | * @return $this
51 | */
52 | public function condition($condition)
53 | {
54 | [$this->condition, $this->hasCondition] = [$condition, true];
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Indicate that the condition should be negated.
61 | *
62 | * @return $this
63 | */
64 | public function negateConditionOnCapture()
65 | {
66 | $this->negateConditionOnCapture = true;
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * Proxy accessing an attribute onto the target.
73 | *
74 | * @param string $key
75 | * @return mixed
76 | */
77 | public function __get($key)
78 | {
79 | if (! $this->hasCondition) {
80 | $condition = $this->target->{$key};
81 |
82 | return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition);
83 | }
84 |
85 | return $this->condition
86 | ? $this->target->{$key}
87 | : $this->target;
88 | }
89 |
90 | /**
91 | * Proxy a method call on the target.
92 | *
93 | * @param string $method
94 | * @param array $parameters
95 | * @return mixed
96 | */
97 | public function __call($method, $parameters)
98 | {
99 | if (! $this->hasCondition) {
100 | $condition = $this->target->{$method}(...$parameters);
101 |
102 | return $this->condition($this->negateConditionOnCapture ? ! $condition : $condition);
103 | }
104 |
105 | return $this->condition
106 | ? $this->target->{$method}(...$parameters)
107 | : $this->target;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/Collect/Contracts/Support/Arrayable.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | public function toArray();
17 | }
18 |
--------------------------------------------------------------------------------
/src/Collect/Contracts/Support/CanBeEscapedWhenCastToString.php:
--------------------------------------------------------------------------------
1 | all();
55 | } elseif (! is_array($values)) {
56 | continue;
57 | }
58 |
59 | $results[] = $values;
60 | }
61 |
62 | return array_merge([], ...$results);
63 | }
64 |
65 | /**
66 | * Cross join the given arrays, returning all possible permutations.
67 | *
68 | * @param iterable ...$arrays
69 | * @return array
70 | */
71 | public static function crossJoin(...$arrays)
72 | {
73 | $results = [[]];
74 |
75 | foreach ($arrays as $index => $array) {
76 | $append = [];
77 |
78 | foreach ($results as $product) {
79 | foreach ($array as $item) {
80 | $product[$index] = $item;
81 |
82 | $append[] = $product;
83 | }
84 | }
85 |
86 | $results = $append;
87 | }
88 |
89 | return $results;
90 | }
91 |
92 | /**
93 | * Divide an array into two arrays. One with keys and the other with values.
94 | *
95 | * @param array $array
96 | * @return array
97 | */
98 | public static function divide($array)
99 | {
100 | return [array_keys($array), array_values($array)];
101 | }
102 |
103 | /**
104 | * Flatten a multi-dimensional associative array with dots.
105 | *
106 | * @param iterable $array
107 | * @param string $prepend
108 | * @return array
109 | */
110 | public static function dot($array, $prepend = '')
111 | {
112 | $results = [];
113 |
114 | foreach ($array as $key => $value) {
115 | if (is_array($value) && ! empty($value)) {
116 | $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
117 | } else {
118 | $results[$prepend.$key] = $value;
119 | }
120 | }
121 |
122 | return $results;
123 | }
124 |
125 | /**
126 | * Convert a flatten "dot" notation array into an expanded array.
127 | *
128 | * @param iterable $array
129 | * @return array
130 | */
131 | public static function undot($array)
132 | {
133 | $results = [];
134 |
135 | foreach ($array as $key => $value) {
136 | static::set($results, $key, $value);
137 | }
138 |
139 | return $results;
140 | }
141 |
142 | /**
143 | * Get all of the given array except for a specified array of keys.
144 | *
145 | * @param array $array
146 | * @param array|string|int|float $keys
147 | * @return array
148 | */
149 | public static function except($array, $keys)
150 | {
151 | static::forget($array, $keys);
152 |
153 | return $array;
154 | }
155 |
156 | /**
157 | * Determine if the given key exists in the provided array.
158 | *
159 | * @param \ArrayAccess|array $array
160 | * @param string|int $key
161 | * @return bool
162 | */
163 | public static function exists($array, $key)
164 | {
165 | if ($array instanceof Enumerable) {
166 | return $array->has($key);
167 | }
168 |
169 | if ($array instanceof ArrayAccess) {
170 | return $array->offsetExists($key);
171 | }
172 |
173 | if (is_float($key)) {
174 | $key = (string) $key;
175 | }
176 |
177 | return array_key_exists($key, $array);
178 | }
179 |
180 | /**
181 | * Return the first element in an array passing a given truth test.
182 | *
183 | * @param iterable $array
184 | * @param callable|null $callback
185 | * @param mixed $default
186 | * @return mixed
187 | */
188 | public static function first($array, callable $callback = null, $default = null)
189 | {
190 | if (is_null($callback)) {
191 | if (empty($array)) {
192 | return value($default);
193 | }
194 |
195 | foreach ($array as $item) {
196 | return $item;
197 | }
198 | }
199 |
200 | foreach ($array as $key => $value) {
201 | if ($callback($value, $key)) {
202 | return $value;
203 | }
204 | }
205 |
206 | return value($default);
207 | }
208 |
209 | /**
210 | * Return the last element in an array passing a given truth test.
211 | *
212 | * @param array $array
213 | * @param callable|null $callback
214 | * @param mixed $default
215 | * @return mixed
216 | */
217 | public static function last($array, callable $callback = null, $default = null)
218 | {
219 | if (is_null($callback)) {
220 | return empty($array) ? value($default) : end($array);
221 | }
222 |
223 | return static::first(array_reverse($array, true), $callback, $default);
224 | }
225 |
226 | /**
227 | * Flatten a multi-dimensional array into a single level.
228 | *
229 | * @param iterable $array
230 | * @param int $depth
231 | * @return array
232 | */
233 | public static function flatten($array, $depth = INF)
234 | {
235 | $result = [];
236 |
237 | foreach ($array as $item) {
238 | $item = $item instanceof Collection ? $item->all() : $item;
239 |
240 | if (! is_array($item)) {
241 | $result[] = $item;
242 | } else {
243 | $values = $depth === 1
244 | ? array_values($item)
245 | : static::flatten($item, $depth - 1);
246 |
247 | foreach ($values as $value) {
248 | $result[] = $value;
249 | }
250 | }
251 | }
252 |
253 | return $result;
254 | }
255 |
256 | /**
257 | * Remove one or many array items from a given array using "dot" notation.
258 | *
259 | * @param array $array
260 | * @param array|string|int|float $keys
261 | * @return void
262 | */
263 | public static function forget(&$array, $keys)
264 | {
265 | $original = &$array;
266 |
267 | $keys = (array) $keys;
268 |
269 | if (count($keys) === 0) {
270 | return;
271 | }
272 |
273 | foreach ($keys as $key) {
274 | // if the exact key exists in the top-level, remove it
275 | if (static::exists($array, $key)) {
276 | unset($array[$key]);
277 |
278 | continue;
279 | }
280 |
281 | $parts = explode('.', $key);
282 |
283 | // clean up before each pass
284 | $array = &$original;
285 |
286 | while (count($parts) > 1) {
287 | $part = array_shift($parts);
288 |
289 | if (isset($array[$part]) && static::accessible($array[$part])) {
290 | $array = &$array[$part];
291 | } else {
292 | continue 2;
293 | }
294 | }
295 |
296 | unset($array[array_shift($parts)]);
297 | }
298 | }
299 |
300 | /**
301 | * Get an item from an array using "dot" notation.
302 | *
303 | * @param \ArrayAccess|array $array
304 | * @param string|int|null $key
305 | * @param mixed $default
306 | * @return mixed
307 | */
308 | public static function get($array, $key, $default = null)
309 | {
310 | if (! static::accessible($array)) {
311 | return value($default);
312 | }
313 |
314 | if (is_null($key)) {
315 | return $array;
316 | }
317 |
318 | if (static::exists($array, $key)) {
319 | return $array[$key];
320 | }
321 |
322 | if (! str_contains($key, '.')) {
323 | return $array[$key] ?? value($default);
324 | }
325 |
326 | foreach (explode('.', $key) as $segment) {
327 | if (static::accessible($array) && static::exists($array, $segment)) {
328 | $array = $array[$segment];
329 | } else {
330 | return value($default);
331 | }
332 | }
333 |
334 | return $array;
335 | }
336 |
337 | /**
338 | * Check if an item or items exist in an array using "dot" notation.
339 | *
340 | * @param \ArrayAccess|array $array
341 | * @param string|array $keys
342 | * @return bool
343 | */
344 | public static function has($array, $keys)
345 | {
346 | $keys = (array) $keys;
347 |
348 | if (! $array || $keys === []) {
349 | return false;
350 | }
351 |
352 | foreach ($keys as $key) {
353 | $subKeyArray = $array;
354 |
355 | if (static::exists($array, $key)) {
356 | continue;
357 | }
358 |
359 | foreach (explode('.', $key) as $segment) {
360 | if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
361 | $subKeyArray = $subKeyArray[$segment];
362 | } else {
363 | return false;
364 | }
365 | }
366 | }
367 |
368 | return true;
369 | }
370 |
371 | /**
372 | * Determine if any of the keys exist in an array using "dot" notation.
373 | *
374 | * @param \ArrayAccess|array $array
375 | * @param string|array $keys
376 | * @return bool
377 | */
378 | public static function hasAny($array, $keys)
379 | {
380 | if (is_null($keys)) {
381 | return false;
382 | }
383 |
384 | $keys = (array) $keys;
385 |
386 | if (! $array) {
387 | return false;
388 | }
389 |
390 | if ($keys === []) {
391 | return false;
392 | }
393 |
394 | foreach ($keys as $key) {
395 | if (static::has($array, $key)) {
396 | return true;
397 | }
398 | }
399 |
400 | return false;
401 | }
402 |
403 | /**
404 | * Determines if an array is associative.
405 | *
406 | * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
407 | *
408 | * @param array $array
409 | * @return bool
410 | */
411 | public static function isAssoc(array $array)
412 | {
413 | $keys = array_keys($array);
414 |
415 | return array_keys($keys) !== $keys;
416 | }
417 |
418 | /**
419 | * Determines if an array is a list.
420 | *
421 | * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between.
422 | *
423 | * @param array $array
424 | * @return bool
425 | */
426 | public static function isList($array)
427 | {
428 | return ! self::isAssoc($array);
429 | }
430 |
431 | /**
432 | * Join all items using a string. The final items can use a separate glue string.
433 | *
434 | * @param array $array
435 | * @param string $glue
436 | * @param string $finalGlue
437 | * @return string
438 | */
439 | public static function join($array, $glue, $finalGlue = '')
440 | {
441 | if ($finalGlue === '') {
442 | return implode($glue, $array);
443 | }
444 |
445 | if (count($array) === 0) {
446 | return '';
447 | }
448 |
449 | if (count($array) === 1) {
450 | return end($array);
451 | }
452 |
453 | $finalItem = array_pop($array);
454 |
455 | return implode($glue, $array).$finalGlue.$finalItem;
456 | }
457 |
458 | /**
459 | * Key an associative array by a field or using a callback.
460 | *
461 | * @param array $array
462 | * @param callable|array|string $keyBy
463 | * @return array
464 | */
465 | public static function keyBy($array, $keyBy)
466 | {
467 | return Collection::make($array)->keyBy($keyBy)->all();
468 | }
469 |
470 | /**
471 | * Prepend the key names of an associative array.
472 | *
473 | * @param array $array
474 | * @param string $prependWith
475 | * @return array
476 | */
477 | public static function prependKeysWith($array, $prependWith)
478 | {
479 | return Collection::make($array)->mapWithKeys(function ($item, $key) use ($prependWith) {
480 | return [$prependWith.$key => $item];
481 | })->all();
482 | }
483 |
484 | /**
485 | * Get a subset of the items from the given array.
486 | *
487 | * @param array $array
488 | * @param array|string $keys
489 | * @return array
490 | */
491 | public static function only($array, $keys)
492 | {
493 | return array_intersect_key($array, array_flip((array) $keys));
494 | }
495 |
496 | /**
497 | * Pluck an array of values from an array.
498 | *
499 | * @param iterable $array
500 | * @param string|array|int|null $value
501 | * @param string|array|null $key
502 | * @return array
503 | */
504 | public static function pluck($array, $value, $key = null)
505 | {
506 | $results = [];
507 |
508 | [$value, $key] = static::explodePluckParameters($value, $key);
509 |
510 | foreach ($array as $item) {
511 | $itemValue = data_get($item, $value);
512 |
513 | // If the key is "null", we will just append the value to the array and keep
514 | // looping. Otherwise we will key the array using the value of the key we
515 | // received from the developer. Then we'll return the final array form.
516 | if (is_null($key)) {
517 | $results[] = $itemValue;
518 | } else {
519 | $itemKey = data_get($item, $key);
520 |
521 | if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
522 | $itemKey = (string) $itemKey;
523 | }
524 |
525 | $results[$itemKey] = $itemValue;
526 | }
527 | }
528 |
529 | return $results;
530 | }
531 |
532 | /**
533 | * Explode the "value" and "key" arguments passed to "pluck".
534 | *
535 | * @param string|array $value
536 | * @param string|array|null $key
537 | * @return array
538 | */
539 | protected static function explodePluckParameters($value, $key)
540 | {
541 | $value = is_string($value) ? explode('.', $value) : $value;
542 |
543 | $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
544 |
545 | return [$value, $key];
546 | }
547 |
548 | /**
549 | * Run a map over each of the items in the array.
550 | *
551 | * @param array $array
552 | * @param callable $callback
553 | * @return array
554 | */
555 | public static function map(array $array, callable $callback)
556 | {
557 | $keys = array_keys($array);
558 |
559 | try {
560 | $items = array_map($callback, $array, $keys);
561 | } catch (ArgumentCountError) {
562 | $items = array_map($callback, $array);
563 | }
564 |
565 | return array_combine($keys, $items);
566 | }
567 |
568 | /**
569 | * Push an item onto the beginning of an array.
570 | *
571 | * @param array $array
572 | * @param mixed $value
573 | * @param mixed $key
574 | * @return array
575 | */
576 | public static function prepend($array, $value, $key = null)
577 | {
578 | if (func_num_args() == 2) {
579 | array_unshift($array, $value);
580 | } else {
581 | $array = [$key => $value] + $array;
582 | }
583 |
584 | return $array;
585 | }
586 |
587 | /**
588 | * Get a value from the array, and remove it.
589 | *
590 | * @param array $array
591 | * @param string|int $key
592 | * @param mixed $default
593 | * @return mixed
594 | */
595 | public static function pull(&$array, $key, $default = null)
596 | {
597 | $value = static::get($array, $key, $default);
598 |
599 | static::forget($array, $key);
600 |
601 | return $value;
602 | }
603 |
604 | /**
605 | * Convert the array into a query string.
606 | *
607 | * @param array $array
608 | * @return string
609 | */
610 | public static function query($array)
611 | {
612 | return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
613 | }
614 |
615 | /**
616 | * Get one or a specified number of random values from an array.
617 | *
618 | * @param array $array
619 | * @param int|null $number
620 | * @param bool $preserveKeys
621 | * @return mixed
622 | *
623 | * @throws \InvalidArgumentException
624 | */
625 | public static function random($array, $number = null, $preserveKeys = false)
626 | {
627 | $requested = is_null($number) ? 1 : $number;
628 |
629 | $count = count($array);
630 |
631 | if ($requested > $count) {
632 | throw new InvalidArgumentException(
633 | "You requested {$requested} items, but there are only {$count} items available."
634 | );
635 | }
636 |
637 | if (is_null($number)) {
638 | return $array[array_rand($array)];
639 | }
640 |
641 | if ((int) $number === 0) {
642 | return [];
643 | }
644 |
645 | $keys = array_rand($array, $number);
646 |
647 | $results = [];
648 |
649 | if ($preserveKeys) {
650 | foreach ((array) $keys as $key) {
651 | $results[$key] = $array[$key];
652 | }
653 | } else {
654 | foreach ((array) $keys as $key) {
655 | $results[] = $array[$key];
656 | }
657 | }
658 |
659 | return $results;
660 | }
661 |
662 | /**
663 | * Set an array item to a given value using "dot" notation.
664 | *
665 | * If no key is given to the method, the entire array will be replaced.
666 | *
667 | * @param array $array
668 | * @param string|int|null $key
669 | * @param mixed $value
670 | * @return array
671 | */
672 | public static function set(&$array, $key, $value)
673 | {
674 | if (is_null($key)) {
675 | return $array = $value;
676 | }
677 |
678 | $keys = explode('.', $key);
679 |
680 | foreach ($keys as $i => $key) {
681 | if (count($keys) === 1) {
682 | break;
683 | }
684 |
685 | unset($keys[$i]);
686 |
687 | // If the key doesn't exist at this depth, we will just create an empty array
688 | // to hold the next value, allowing us to create the arrays to hold final
689 | // values at the correct depth. Then we'll keep digging into the array.
690 | if (! isset($array[$key]) || ! is_array($array[$key])) {
691 | $array[$key] = [];
692 | }
693 |
694 | $array = &$array[$key];
695 | }
696 |
697 | $array[array_shift($keys)] = $value;
698 |
699 | return $array;
700 | }
701 |
702 | /**
703 | * Shuffle the given array and return the result.
704 | *
705 | * @param array $array
706 | * @param int|null $seed
707 | * @return array
708 | */
709 | public static function shuffle($array, $seed = null)
710 | {
711 | if (is_null($seed)) {
712 | shuffle($array);
713 | } else {
714 | mt_srand($seed);
715 | shuffle($array);
716 | mt_srand();
717 | }
718 |
719 | return $array;
720 | }
721 |
722 | /**
723 | * Sort the array using the given callback or "dot" notation.
724 | *
725 | * @param array $array
726 | * @param callable|array|string|null $callback
727 | * @return array
728 | */
729 | public static function sort($array, $callback = null)
730 | {
731 | return Collection::make($array)->sortBy($callback)->all();
732 | }
733 |
734 | /**
735 | * Sort the array in descending order using the given callback or "dot" notation.
736 | *
737 | * @param array $array
738 | * @param callable|array|string|null $callback
739 | * @return array
740 | */
741 | public static function sortDesc($array, $callback = null)
742 | {
743 | return Collection::make($array)->sortByDesc($callback)->all();
744 | }
745 |
746 | /**
747 | * Recursively sort an array by keys and values.
748 | *
749 | * @param array $array
750 | * @param int $options
751 | * @param bool $descending
752 | * @return array
753 | */
754 | public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false)
755 | {
756 | foreach ($array as &$value) {
757 | if (is_array($value)) {
758 | $value = static::sortRecursive($value, $options, $descending);
759 | }
760 | }
761 |
762 | if (static::isAssoc($array)) {
763 | $descending
764 | ? krsort($array, $options)
765 | : ksort($array, $options);
766 | } else {
767 | $descending
768 | ? rsort($array, $options)
769 | : sort($array, $options);
770 | }
771 |
772 | return $array;
773 | }
774 |
775 | /**
776 | * Conditionally compile classes from an array into a CSS class list.
777 | *
778 | * @param array $array
779 | * @return string
780 | */
781 | public static function toCssClasses($array)
782 | {
783 | $classList = static::wrap($array);
784 |
785 | $classes = [];
786 |
787 | foreach ($classList as $class => $constraint) {
788 | if (is_numeric($class)) {
789 | $classes[] = $constraint;
790 | } elseif ($constraint) {
791 | $classes[] = $class;
792 | }
793 | }
794 |
795 | return implode(' ', $classes);
796 | }
797 |
798 | /**
799 | * Conditionally compile styles from an array into a style list.
800 | *
801 | * @param array $array
802 | * @return string
803 | */
804 | public static function toCssStyles($array)
805 | {
806 | $styleList = static::wrap($array);
807 |
808 | $styles = [];
809 |
810 | foreach ($styleList as $class => $constraint) {
811 | if (is_numeric($class)) {
812 | $styles[] = Str::finish($constraint, ';');
813 | } elseif ($constraint) {
814 | $styles[] = Str::finish($class, ';');
815 | }
816 | }
817 |
818 | return implode(' ', $styles);
819 | }
820 |
821 | /**
822 | * Filter the array using the given callback.
823 | *
824 | * @param array $array
825 | * @param callable $callback
826 | * @return array
827 | */
828 | public static function where($array, callable $callback)
829 | {
830 | return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
831 | }
832 |
833 | /**
834 | * Filter items where the value is not null.
835 | *
836 | * @param array $array
837 | * @return array
838 | */
839 | public static function whereNotNull($array)
840 | {
841 | return static::where($array, fn ($value) => ! is_null($value));
842 | }
843 |
844 | /**
845 | * If the given value is not an array and not null, wrap it in one.
846 | *
847 | * @param mixed $value
848 | * @return array
849 | */
850 | public static function wrap($value)
851 | {
852 | if (is_null($value)) {
853 | return [];
854 | }
855 |
856 | return is_array($value) ? $value : [$value];
857 | }
858 | }
859 |
--------------------------------------------------------------------------------
/src/Collect/Support/Enumerable.php:
--------------------------------------------------------------------------------
1 |
18 | * @extends \IteratorAggregate
19 | */
20 | interface Enumerable extends Arrayable, Countable, IteratorAggregate, Jsonable, JsonSerializable
21 | {
22 | /**
23 | * Create a new collection instance if the value isn't one already.
24 | *
25 | * @template TMakeKey of array-key
26 | * @template TMakeValue
27 | *
28 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable|null $items
29 | * @return static
30 | */
31 | public static function make($items = []);
32 |
33 | /**
34 | * Create a new instance by invoking the callback a given amount of times.
35 | *
36 | * @param int $number
37 | * @param callable|null $callback
38 | * @return static
39 | */
40 | public static function times($number, callable $callback = null);
41 |
42 | /**
43 | * Create a collection with the given range.
44 | *
45 | * @param int $from
46 | * @param int $to
47 | * @return static
48 | */
49 | public static function range($from, $to);
50 |
51 | /**
52 | * Wrap the given value in a collection if applicable.
53 | *
54 | * @template TWrapValue
55 | *
56 | * @param iterable|TWrapValue $value
57 | * @return static
58 | */
59 | public static function wrap($value);
60 |
61 | /**
62 | * Get the underlying items from the given collection if applicable.
63 | *
64 | * @template TUnwrapKey of array-key
65 | * @template TUnwrapValue
66 | *
67 | * @param array|static $value
68 | * @return array
69 | */
70 | public static function unwrap($value);
71 |
72 | /**
73 | * Create a new instance with no items.
74 | *
75 | * @return static
76 | */
77 | public static function empty();
78 |
79 | /**
80 | * Get all items in the enumerable.
81 | *
82 | * @return array
83 | */
84 | public function all();
85 |
86 | /**
87 | * Alias for the "avg" method.
88 | *
89 | * @param (callable(TValue): float|int)|string|null $callback
90 | * @return float|int|null
91 | */
92 | public function average($callback = null);
93 |
94 | /**
95 | * Get the median of a given key.
96 | *
97 | * @param string|array|null $key
98 | * @return float|int|null
99 | */
100 | public function median($key = null);
101 |
102 | /**
103 | * Get the mode of a given key.
104 | *
105 | * @param string|array|null $key
106 | * @return array|null
107 | */
108 | public function mode($key = null);
109 |
110 | /**
111 | * Collapse the items into a single enumerable.
112 | *
113 | * @return static
114 | */
115 | public function collapse();
116 |
117 | /**
118 | * Alias for the "contains" method.
119 | *
120 | * @param (callable(TValue, TKey): bool)|TValue|string $key
121 | * @param mixed $operator
122 | * @param mixed $value
123 | * @return bool
124 | */
125 | public function some($key, $operator = null, $value = null);
126 |
127 | /**
128 | * Determine if an item exists, using strict comparison.
129 | *
130 | * @param (callable(TValue): bool)|TValue|array-key $key
131 | * @param TValue|null $value
132 | * @return bool
133 | */
134 | public function containsStrict($key, $value = null);
135 |
136 | /**
137 | * Get the average value of a given key.
138 | *
139 | * @param (callable(TValue): float|int)|string|null $callback
140 | * @return float|int|null
141 | */
142 | public function avg($callback = null);
143 |
144 | /**
145 | * Determine if an item exists in the enumerable.
146 | *
147 | * @param (callable(TValue, TKey): bool)|TValue|string $key
148 | * @param mixed $operator
149 | * @param mixed $value
150 | * @return bool
151 | */
152 | public function contains($key, $operator = null, $value = null);
153 |
154 | /**
155 | * Determine if an item is not contained in the collection.
156 | *
157 | * @param mixed $key
158 | * @param mixed $operator
159 | * @param mixed $value
160 | * @return bool
161 | */
162 | public function doesntContain($key, $operator = null, $value = null);
163 |
164 | /**
165 | * Cross join with the given lists, returning all possible permutations.
166 | *
167 | * @template TCrossJoinKey
168 | * @template TCrossJoinValue
169 | *
170 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable ...$lists
171 | * @return static>
172 | */
173 | public function crossJoin(...$lists);
174 |
175 | /**
176 | * Dump the collection and end the script.
177 | *
178 | * @param mixed ...$args
179 | * @return never
180 | */
181 | public function dd(...$args);
182 |
183 | /**
184 | * Dump the collection.
185 | *
186 | * @return $this
187 | */
188 | public function dump();
189 |
190 | /**
191 | * Get the items that are not present in the given items.
192 | *
193 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
194 | * @return static
195 | */
196 | public function diff($items);
197 |
198 | /**
199 | * Get the items that are not present in the given items, using the callback.
200 | *
201 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
202 | * @param callable(TValue, TValue): int $callback
203 | * @return static
204 | */
205 | public function diffUsing($items, callable $callback);
206 |
207 | /**
208 | * Get the items whose keys and values are not present in the given items.
209 | *
210 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
211 | * @return static
212 | */
213 | public function diffAssoc($items);
214 |
215 | /**
216 | * Get the items whose keys and values are not present in the given items, using the callback.
217 | *
218 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
219 | * @param callable(TKey, TKey): int $callback
220 | * @return static
221 | */
222 | public function diffAssocUsing($items, callable $callback);
223 |
224 | /**
225 | * Get the items whose keys are not present in the given items.
226 | *
227 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
228 | * @return static
229 | */
230 | public function diffKeys($items);
231 |
232 | /**
233 | * Get the items whose keys are not present in the given items, using the callback.
234 | *
235 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
236 | * @param callable(TKey, TKey): int $callback
237 | * @return static
238 | */
239 | public function diffKeysUsing($items, callable $callback);
240 |
241 | /**
242 | * Retrieve duplicate items.
243 | *
244 | * @param (callable(TValue): bool)|string|null $callback
245 | * @param bool $strict
246 | * @return static
247 | */
248 | public function duplicates($callback = null, $strict = false);
249 |
250 | /**
251 | * Retrieve duplicate items using strict comparison.
252 | *
253 | * @param (callable(TValue): bool)|string|null $callback
254 | * @return static
255 | */
256 | public function duplicatesStrict($callback = null);
257 |
258 | /**
259 | * Execute a callback over each item.
260 | *
261 | * @param callable(TValue, TKey): mixed $callback
262 | * @return $this
263 | */
264 | public function each(callable $callback);
265 |
266 | /**
267 | * Execute a callback over each nested chunk of items.
268 | *
269 | * @param callable $callback
270 | * @return static
271 | */
272 | public function eachSpread(callable $callback);
273 |
274 | /**
275 | * Determine if all items pass the given truth test.
276 | *
277 | * @param (callable(TValue, TKey): bool)|TValue|string $key
278 | * @param mixed $operator
279 | * @param mixed $value
280 | * @return bool
281 | */
282 | public function every($key, $operator = null, $value = null);
283 |
284 | /**
285 | * Get all items except for those with the specified keys.
286 | *
287 | * @param \QL\Collect\Support\Enumerable|array $keys
288 | * @return static
289 | */
290 | public function except($keys);
291 |
292 | /**
293 | * Run a filter over each of the items.
294 | *
295 | * @param (callable(TValue): bool)|null $callback
296 | * @return static
297 | */
298 | public function filter(callable $callback = null);
299 |
300 | /**
301 | * Apply the callback if the given "value" is (or resolves to) truthy.
302 | *
303 | * @template TWhenReturnType as null
304 | *
305 | * @param bool $value
306 | * @param (callable($this): TWhenReturnType)|null $callback
307 | * @param (callable($this): TWhenReturnType)|null $default
308 | * @return $this|TWhenReturnType
309 | */
310 | public function when($value, callable $callback = null, callable $default = null);
311 |
312 | /**
313 | * Apply the callback if the collection is empty.
314 | *
315 | * @template TWhenEmptyReturnType
316 | *
317 | * @param (callable($this): TWhenEmptyReturnType) $callback
318 | * @param (callable($this): TWhenEmptyReturnType)|null $default
319 | * @return $this|TWhenEmptyReturnType
320 | */
321 | public function whenEmpty(callable $callback, callable $default = null);
322 |
323 | /**
324 | * Apply the callback if the collection is not empty.
325 | *
326 | * @template TWhenNotEmptyReturnType
327 | *
328 | * @param callable($this): TWhenNotEmptyReturnType $callback
329 | * @param (callable($this): TWhenNotEmptyReturnType)|null $default
330 | * @return $this|TWhenNotEmptyReturnType
331 | */
332 | public function whenNotEmpty(callable $callback, callable $default = null);
333 |
334 | /**
335 | * Apply the callback if the given "value" is (or resolves to) truthy.
336 | *
337 | * @template TUnlessReturnType
338 | *
339 | * @param bool $value
340 | * @param (callable($this): TUnlessReturnType) $callback
341 | * @param (callable($this): TUnlessReturnType)|null $default
342 | * @return $this|TUnlessReturnType
343 | */
344 | public function unless($value, callable $callback, callable $default = null);
345 |
346 | /**
347 | * Apply the callback unless the collection is empty.
348 | *
349 | * @template TUnlessEmptyReturnType
350 | *
351 | * @param callable($this): TUnlessEmptyReturnType $callback
352 | * @param (callable($this): TUnlessEmptyReturnType)|null $default
353 | * @return $this|TUnlessEmptyReturnType
354 | */
355 | public function unlessEmpty(callable $callback, callable $default = null);
356 |
357 | /**
358 | * Apply the callback unless the collection is not empty.
359 | *
360 | * @template TUnlessNotEmptyReturnType
361 | *
362 | * @param callable($this): TUnlessNotEmptyReturnType $callback
363 | * @param (callable($this): TUnlessNotEmptyReturnType)|null $default
364 | * @return $this|TUnlessNotEmptyReturnType
365 | */
366 | public function unlessNotEmpty(callable $callback, callable $default = null);
367 |
368 | /**
369 | * Filter items by the given key value pair.
370 | *
371 | * @param string $key
372 | * @param mixed $operator
373 | * @param mixed $value
374 | * @return static
375 | */
376 | public function where($key, $operator = null, $value = null);
377 |
378 | /**
379 | * Filter items where the value for the given key is null.
380 | *
381 | * @param string|null $key
382 | * @return static
383 | */
384 | public function whereNull($key = null);
385 |
386 | /**
387 | * Filter items where the value for the given key is not null.
388 | *
389 | * @param string|null $key
390 | * @return static
391 | */
392 | public function whereNotNull($key = null);
393 |
394 | /**
395 | * Filter items by the given key value pair using strict comparison.
396 | *
397 | * @param string $key
398 | * @param mixed $value
399 | * @return static
400 | */
401 | public function whereStrict($key, $value);
402 |
403 | /**
404 | * Filter items by the given key value pair.
405 | *
406 | * @param string $key
407 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
408 | * @param bool $strict
409 | * @return static
410 | */
411 | public function whereIn($key, $values, $strict = false);
412 |
413 | /**
414 | * Filter items by the given key value pair using strict comparison.
415 | *
416 | * @param string $key
417 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
418 | * @return static
419 | */
420 | public function whereInStrict($key, $values);
421 |
422 | /**
423 | * Filter items such that the value of the given key is between the given values.
424 | *
425 | * @param string $key
426 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
427 | * @return static
428 | */
429 | public function whereBetween($key, $values);
430 |
431 | /**
432 | * Filter items such that the value of the given key is not between the given values.
433 | *
434 | * @param string $key
435 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
436 | * @return static
437 | */
438 | public function whereNotBetween($key, $values);
439 |
440 | /**
441 | * Filter items by the given key value pair.
442 | *
443 | * @param string $key
444 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
445 | * @param bool $strict
446 | * @return static
447 | */
448 | public function whereNotIn($key, $values, $strict = false);
449 |
450 | /**
451 | * Filter items by the given key value pair using strict comparison.
452 | *
453 | * @param string $key
454 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
455 | * @return static
456 | */
457 | public function whereNotInStrict($key, $values);
458 |
459 | /**
460 | * Filter the items, removing any items that don't match the given type(s).
461 | *
462 | * @template TWhereInstanceOf
463 | *
464 | * @param class-string|array> $type
465 | * @return static
466 | */
467 | public function whereInstanceOf($type);
468 |
469 | /**
470 | * Get the first item from the enumerable passing the given truth test.
471 | *
472 | * @template TFirstDefault
473 | *
474 | * @param (callable(TValue,TKey): bool)|null $callback
475 | * @param TFirstDefault|(\Closure(): TFirstDefault) $default
476 | * @return TValue|TFirstDefault
477 | */
478 | public function first(callable $callback = null, $default = null);
479 |
480 | /**
481 | * Get the first item by the given key value pair.
482 | *
483 | * @param string $key
484 | * @param mixed $operator
485 | * @param mixed $value
486 | * @return TValue|null
487 | */
488 | public function firstWhere($key, $operator = null, $value = null);
489 |
490 | /**
491 | * Get a flattened array of the items in the collection.
492 | *
493 | * @param int $depth
494 | * @return static
495 | */
496 | public function flatten($depth = INF);
497 |
498 | /**
499 | * Flip the values with their keys.
500 | *
501 | * @return static
502 | */
503 | public function flip();
504 |
505 | /**
506 | * Get an item from the collection by key.
507 | *
508 | * @template TGetDefault
509 | *
510 | * @param TKey $key
511 | * @param TGetDefault|(\Closure(): TGetDefault) $default
512 | * @return TValue|TGetDefault
513 | */
514 | public function get($key, $default = null);
515 |
516 | /**
517 | * Group an associative array by a field or using a callback.
518 | *
519 | * @param (callable(TValue, TKey): array-key)|array|string $groupBy
520 | * @param bool $preserveKeys
521 | * @return static>
522 | */
523 | public function groupBy($groupBy, $preserveKeys = false);
524 |
525 | /**
526 | * Key an associative array by a field or using a callback.
527 | *
528 | * @param (callable(TValue, TKey): array-key)|array|string $keyBy
529 | * @return static
530 | */
531 | public function keyBy($keyBy);
532 |
533 | /**
534 | * Determine if an item exists in the collection by key.
535 | *
536 | * @param TKey|array $key
537 | * @return bool
538 | */
539 | public function has($key);
540 |
541 | /**
542 | * Determine if any of the keys exist in the collection.
543 | *
544 | * @param mixed $key
545 | * @return bool
546 | */
547 | public function hasAny($key);
548 |
549 | /**
550 | * Concatenate values of a given key as a string.
551 | *
552 | * @param string $value
553 | * @param string|null $glue
554 | * @return string
555 | */
556 | public function implode($value, $glue = null);
557 |
558 | /**
559 | * Intersect the collection with the given items.
560 | *
561 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
562 | * @return static
563 | */
564 | public function intersect($items);
565 |
566 | /**
567 | * Intersect the collection with the given items by key.
568 | *
569 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
570 | * @return static
571 | */
572 | public function intersectByKeys($items);
573 |
574 | /**
575 | * Determine if the collection is empty or not.
576 | *
577 | * @return bool
578 | */
579 | public function isEmpty();
580 |
581 | /**
582 | * Determine if the collection is not empty.
583 | *
584 | * @return bool
585 | */
586 | public function isNotEmpty();
587 |
588 | /**
589 | * Determine if the collection contains a single item.
590 | *
591 | * @return bool
592 | */
593 | public function containsOneItem();
594 |
595 | /**
596 | * Join all items from the collection using a string. The final items can use a separate glue string.
597 | *
598 | * @param string $glue
599 | * @param string $finalGlue
600 | * @return string
601 | */
602 | public function join($glue, $finalGlue = '');
603 |
604 | /**
605 | * Get the keys of the collection items.
606 | *
607 | * @return static
608 | */
609 | public function keys();
610 |
611 | /**
612 | * Get the last item from the collection.
613 | *
614 | * @template TLastDefault
615 | *
616 | * @param (callable(TValue, TKey): bool)|null $callback
617 | * @param TLastDefault|(\Closure(): TLastDefault) $default
618 | * @return TValue|TLastDefault
619 | */
620 | public function last(callable $callback = null, $default = null);
621 |
622 | /**
623 | * Run a map over each of the items.
624 | *
625 | * @template TMapValue
626 | *
627 | * @param callable(TValue, TKey): TMapValue $callback
628 | * @return static
629 | */
630 | public function map(callable $callback);
631 |
632 | /**
633 | * Run a map over each nested chunk of items.
634 | *
635 | * @param callable $callback
636 | * @return static
637 | */
638 | public function mapSpread(callable $callback);
639 |
640 | /**
641 | * Run a dictionary map over the items.
642 | *
643 | * The callback should return an associative array with a single key/value pair.
644 | *
645 | * @template TMapToDictionaryKey of array-key
646 | * @template TMapToDictionaryValue
647 | *
648 | * @param callable(TValue, TKey): array $callback
649 | * @return static>
650 | */
651 | public function mapToDictionary(callable $callback);
652 |
653 | /**
654 | * Run a grouping map over the items.
655 | *
656 | * The callback should return an associative array with a single key/value pair.
657 | *
658 | * @template TMapToGroupsKey of array-key
659 | * @template TMapToGroupsValue
660 | *
661 | * @param callable(TValue, TKey): array $callback
662 | * @return static>
663 | */
664 | public function mapToGroups(callable $callback);
665 |
666 | /**
667 | * Run an associative map over each of the items.
668 | *
669 | * The callback should return an associative array with a single key/value pair.
670 | *
671 | * @template TMapWithKeysKey of array-key
672 | * @template TMapWithKeysValue
673 | *
674 | * @param callable(TValue, TKey): array $callback
675 | * @return static
676 | */
677 | public function mapWithKeys(callable $callback);
678 |
679 | /**
680 | * Map a collection and flatten the result by a single level.
681 | *
682 | * @template TFlatMapKey of array-key
683 | * @template TFlatMapValue
684 | *
685 | * @param callable(TValue, TKey): (\QL\Collect\Support\Collection|array) $callback
686 | * @return static
687 | */
688 | public function flatMap(callable $callback);
689 |
690 | /**
691 | * Map the values into a new class.
692 | *
693 | * @template TMapIntoValue
694 | *
695 | * @param class-string $class
696 | * @return static
697 | */
698 | public function mapInto($class);
699 |
700 | /**
701 | * Merge the collection with the given items.
702 | *
703 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
704 | * @return static
705 | */
706 | public function merge($items);
707 |
708 | /**
709 | * Recursively merge the collection with the given items.
710 | *
711 | * @template TMergeRecursiveValue
712 | *
713 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
714 | * @return static
715 | */
716 | public function mergeRecursive($items);
717 |
718 | /**
719 | * Create a collection by using this collection for keys and another for its values.
720 | *
721 | * @template TCombineValue
722 | *
723 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
724 | * @return static
725 | */
726 | public function combine($values);
727 |
728 | /**
729 | * Union the collection with the given items.
730 | *
731 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
732 | * @return static
733 | */
734 | public function union($items);
735 |
736 | /**
737 | * Get the min value of a given key.
738 | *
739 | * @param (callable(TValue):mixed)|string|null $callback
740 | * @return mixed
741 | */
742 | public function min($callback = null);
743 |
744 | /**
745 | * Get the max value of a given key.
746 | *
747 | * @param (callable(TValue):mixed)|string|null $callback
748 | * @return mixed
749 | */
750 | public function max($callback = null);
751 |
752 | /**
753 | * Create a new collection consisting of every n-th element.
754 | *
755 | * @param int $step
756 | * @param int $offset
757 | * @return static
758 | */
759 | public function nth($step, $offset = 0);
760 |
761 | /**
762 | * Get the items with the specified keys.
763 | *
764 | * @param \QL\Collect\Support\Enumerable|array|string $keys
765 | * @return static
766 | */
767 | public function only($keys);
768 |
769 | /**
770 | * "Paginate" the collection by slicing it into a smaller collection.
771 | *
772 | * @param int $page
773 | * @param int $perPage
774 | * @return static
775 | */
776 | public function forPage($page, $perPage);
777 |
778 | /**
779 | * Partition the collection into two arrays using the given callback or key.
780 | *
781 | * @param (callable(TValue, TKey): bool)|TValue|string $key
782 | * @param mixed $operator
783 | * @param mixed $value
784 | * @return static, static>
785 | */
786 | public function partition($key, $operator = null, $value = null);
787 |
788 | /**
789 | * Push all of the given items onto the collection.
790 | *
791 | * @param iterable $source
792 | * @return static
793 | */
794 | public function concat($source);
795 |
796 | /**
797 | * Get one or a specified number of items randomly from the collection.
798 | *
799 | * @param int|null $number
800 | * @return static|TValue
801 | *
802 | * @throws \InvalidArgumentException
803 | */
804 | public function random($number = null);
805 |
806 | /**
807 | * Reduce the collection to a single value.
808 | *
809 | * @template TReduceInitial
810 | * @template TReduceReturnType
811 | *
812 | * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
813 | * @param TReduceInitial $initial
814 | * @return TReduceReturnType
815 | */
816 | public function reduce(callable $callback, $initial = null);
817 |
818 | /**
819 | * Reduce the collection to multiple aggregate values.
820 | *
821 | * @param callable $callback
822 | * @param mixed ...$initial
823 | * @return array
824 | *
825 | * @throws \UnexpectedValueException
826 | */
827 | public function reduceSpread(callable $callback, ...$initial);
828 |
829 | /**
830 | * Replace the collection items with the given items.
831 | *
832 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
833 | * @return static
834 | */
835 | public function replace($items);
836 |
837 | /**
838 | * Recursively replace the collection items with the given items.
839 | *
840 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $items
841 | * @return static
842 | */
843 | public function replaceRecursive($items);
844 |
845 | /**
846 | * Reverse items order.
847 | *
848 | * @return static
849 | */
850 | public function reverse();
851 |
852 | /**
853 | * Search the collection for a given value and return the corresponding key if successful.
854 | *
855 | * @param TValue|callable(TValue,TKey): bool $value
856 | * @param bool $strict
857 | * @return TKey|bool
858 | */
859 | public function search($value, $strict = false);
860 |
861 | /**
862 | * Shuffle the items in the collection.
863 | *
864 | * @param int|null $seed
865 | * @return static
866 | */
867 | public function shuffle($seed = null);
868 |
869 | /**
870 | * Create chunks representing a "sliding window" view of the items in the collection.
871 | *
872 | * @param int $size
873 | * @param int $step
874 | * @return static
875 | */
876 | public function sliding($size = 2, $step = 1);
877 |
878 | /**
879 | * Skip the first {$count} items.
880 | *
881 | * @param int $count
882 | * @return static
883 | */
884 | public function skip($count);
885 |
886 | /**
887 | * Skip items in the collection until the given condition is met.
888 | *
889 | * @param TValue|callable(TValue,TKey): bool $value
890 | * @return static
891 | */
892 | public function skipUntil($value);
893 |
894 | /**
895 | * Skip items in the collection while the given condition is met.
896 | *
897 | * @param TValue|callable(TValue,TKey): bool $value
898 | * @return static
899 | */
900 | public function skipWhile($value);
901 |
902 | /**
903 | * Get a slice of items from the enumerable.
904 | *
905 | * @param int $offset
906 | * @param int|null $length
907 | * @return static
908 | */
909 | public function slice($offset, $length = null);
910 |
911 | /**
912 | * Split a collection into a certain number of groups.
913 | *
914 | * @param int $numberOfGroups
915 | * @return static
916 | */
917 | public function split($numberOfGroups);
918 |
919 | /**
920 | * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
921 | *
922 | * @param (callable(TValue, TKey): bool)|string $key
923 | * @param mixed $operator
924 | * @param mixed $value
925 | * @return TValue
926 | *
927 | * @throws \QL\Collect\Support\ItemNotFoundException
928 | * @throws \QL\Collect\Support\MultipleItemsFoundException
929 | */
930 | public function sole($key = null, $operator = null, $value = null);
931 |
932 | /**
933 | * Get the first item in the collection but throw an exception if no matching items exist.
934 | *
935 | * @param (callable(TValue, TKey): bool)|string $key
936 | * @param mixed $operator
937 | * @param mixed $value
938 | * @return TValue
939 | *
940 | * @throws \QL\Collect\Support\ItemNotFoundException
941 | */
942 | public function firstOrFail($key = null, $operator = null, $value = null);
943 |
944 | /**
945 | * Chunk the collection into chunks of the given size.
946 | *
947 | * @param int $size
948 | * @return static
949 | */
950 | public function chunk($size);
951 |
952 | /**
953 | * Chunk the collection into chunks with a callback.
954 | *
955 | * @param callable(TValue, TKey, static): bool $callback
956 | * @return static>
957 | */
958 | public function chunkWhile(callable $callback);
959 |
960 | /**
961 | * Split a collection into a certain number of groups, and fill the first groups completely.
962 | *
963 | * @param int $numberOfGroups
964 | * @return static
965 | */
966 | public function splitIn($numberOfGroups);
967 |
968 | /**
969 | * Sort through each item with a callback.
970 | *
971 | * @param (callable(TValue, TValue): int)|null|int $callback
972 | * @return static
973 | */
974 | public function sort($callback = null);
975 |
976 | /**
977 | * Sort items in descending order.
978 | *
979 | * @param int $options
980 | * @return static
981 | */
982 | public function sortDesc($options = SORT_REGULAR);
983 |
984 | /**
985 | * Sort the collection using the given callback.
986 | *
987 | * @param array|(callable(TValue, TKey): mixed)|string $callback
988 | * @param int $options
989 | * @param bool $descending
990 | * @return static
991 | */
992 | public function sortBy($callback, $options = SORT_REGULAR, $descending = false);
993 |
994 | /**
995 | * Sort the collection in descending order using the given callback.
996 | *
997 | * @param array|(callable(TValue, TKey): mixed)|string $callback
998 | * @param int $options
999 | * @return static
1000 | */
1001 | public function sortByDesc($callback, $options = SORT_REGULAR);
1002 |
1003 | /**
1004 | * Sort the collection keys.
1005 | *
1006 | * @param int $options
1007 | * @param bool $descending
1008 | * @return static
1009 | */
1010 | public function sortKeys($options = SORT_REGULAR, $descending = false);
1011 |
1012 | /**
1013 | * Sort the collection keys in descending order.
1014 | *
1015 | * @param int $options
1016 | * @return static
1017 | */
1018 | public function sortKeysDesc($options = SORT_REGULAR);
1019 |
1020 | /**
1021 | * Sort the collection keys using a callback.
1022 | *
1023 | * @param callable(TKey, TKey): int $callback
1024 | * @return static
1025 | */
1026 | public function sortKeysUsing(callable $callback);
1027 |
1028 | /**
1029 | * Get the sum of the given values.
1030 | *
1031 | * @param (callable(TValue): mixed)|string|null $callback
1032 | * @return mixed
1033 | */
1034 | public function sum($callback = null);
1035 |
1036 | /**
1037 | * Take the first or last {$limit} items.
1038 | *
1039 | * @param int $limit
1040 | * @return static
1041 | */
1042 | public function take($limit);
1043 |
1044 | /**
1045 | * Take items in the collection until the given condition is met.
1046 | *
1047 | * @param TValue|callable(TValue,TKey): bool $value
1048 | * @return static
1049 | */
1050 | public function takeUntil($value);
1051 |
1052 | /**
1053 | * Take items in the collection while the given condition is met.
1054 | *
1055 | * @param TValue|callable(TValue,TKey): bool $value
1056 | * @return static
1057 | */
1058 | public function takeWhile($value);
1059 |
1060 | /**
1061 | * Pass the collection to the given callback and then return it.
1062 | *
1063 | * @param callable(TValue): mixed $callback
1064 | * @return $this
1065 | */
1066 | public function tap(callable $callback);
1067 |
1068 | /**
1069 | * Pass the enumerable to the given callback and return the result.
1070 | *
1071 | * @template TPipeReturnType
1072 | *
1073 | * @param callable($this): TPipeReturnType $callback
1074 | * @return TPipeReturnType
1075 | */
1076 | public function pipe(callable $callback);
1077 |
1078 | /**
1079 | * Pass the collection into a new class.
1080 | *
1081 | * @param class-string $class
1082 | * @return mixed
1083 | */
1084 | public function pipeInto($class);
1085 |
1086 | /**
1087 | * Pass the collection through a series of callable pipes and return the result.
1088 | *
1089 | * @param array $pipes
1090 | * @return mixed
1091 | */
1092 | public function pipeThrough($pipes);
1093 |
1094 | /**
1095 | * Get the values of a given key.
1096 | *
1097 | * @param string|array $value
1098 | * @param string|null $key
1099 | * @return static
1100 | */
1101 | public function pluck($value, $key = null);
1102 |
1103 | /**
1104 | * Create a collection of all elements that do not pass a given truth test.
1105 | *
1106 | * @param (callable(TValue, TKey): bool)|bool|TValue $callback
1107 | * @return static
1108 | */
1109 | public function reject($callback = true);
1110 |
1111 | /**
1112 | * Convert a flatten "dot" notation array into an expanded array.
1113 | *
1114 | * @return static
1115 | */
1116 | public function undot();
1117 |
1118 | /**
1119 | * Return only unique items from the collection array.
1120 | *
1121 | * @param (callable(TValue, TKey): mixed)|string|null $key
1122 | * @param bool $strict
1123 | * @return static
1124 | */
1125 | public function unique($key = null, $strict = false);
1126 |
1127 | /**
1128 | * Return only unique items from the collection array using strict comparison.
1129 | *
1130 | * @param (callable(TValue, TKey): mixed)|string|null $key
1131 | * @return static
1132 | */
1133 | public function uniqueStrict($key = null);
1134 |
1135 | /**
1136 | * Reset the keys on the underlying array.
1137 | *
1138 | * @return static
1139 | */
1140 | public function values();
1141 |
1142 | /**
1143 | * Pad collection to the specified length with a value.
1144 | *
1145 | * @template TPadValue
1146 | *
1147 | * @param int $size
1148 | * @param TPadValue $value
1149 | * @return static
1150 | */
1151 | public function pad($size, $value);
1152 |
1153 | /**
1154 | * Get the values iterator.
1155 | *
1156 | * @return \Traversable
1157 | */
1158 | public function getIterator(): Traversable;
1159 |
1160 | /**
1161 | * Count the number of items in the collection.
1162 | *
1163 | * @return int
1164 | */
1165 | public function count(): int;
1166 |
1167 | /**
1168 | * Count the number of items in the collection by a field or using a callback.
1169 | *
1170 | * @param (callable(TValue, TKey): array-key)|string|null $countBy
1171 | * @return static
1172 | */
1173 | public function countBy($countBy = null);
1174 |
1175 | /**
1176 | * Zip the collection together with one or more arrays.
1177 | *
1178 | * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
1179 | * => [[1, 4], [2, 5], [3, 6]]
1180 | *
1181 | * @template TZipValue
1182 | *
1183 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable ...$items
1184 | * @return static>
1185 | */
1186 | public function zip($items);
1187 |
1188 | /**
1189 | * Collect the values into a collection.
1190 | *
1191 | * @return \QL\Collect\Support\Collection
1192 | */
1193 | public function collect();
1194 |
1195 | /**
1196 | * Get the collection of items as a plain array.
1197 | *
1198 | * @return array
1199 | */
1200 | public function toArray();
1201 |
1202 | /**
1203 | * Convert the object into something JSON serializable.
1204 | *
1205 | * @return mixed
1206 | */
1207 | public function jsonSerialize(): mixed;
1208 |
1209 | /**
1210 | * Get the collection of items as JSON.
1211 | *
1212 | * @param int $options
1213 | * @return string
1214 | */
1215 | public function toJson($options = 0);
1216 |
1217 | /**
1218 | * Get a CachingIterator instance.
1219 | *
1220 | * @param int $flags
1221 | * @return \CachingIterator
1222 | */
1223 | public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING);
1224 |
1225 | /**
1226 | * Convert the collection to its string representation.
1227 | *
1228 | * @return string
1229 | */
1230 | public function __toString();
1231 |
1232 | /**
1233 | * Indicate that the model's string representation should be escaped when __toString is invoked.
1234 | *
1235 | * @param bool $escape
1236 | * @return $this
1237 | */
1238 | public function escapeWhenCastingToString($escape = true);
1239 |
1240 | /**
1241 | * Add a method to the list of proxied methods.
1242 | *
1243 | * @param string $method
1244 | * @return void
1245 | */
1246 | public static function proxy($method);
1247 |
1248 | /**
1249 | * Dynamically access collection proxies.
1250 | *
1251 | * @param string $key
1252 | * @return mixed
1253 | *
1254 | * @throws \Exception
1255 | */
1256 | public function __get($key);
1257 | }
1258 |
--------------------------------------------------------------------------------
/src/Collect/Support/HigherOrderCollectionProxy.php:
--------------------------------------------------------------------------------
1 | method = $method;
34 | $this->collection = $collection;
35 | }
36 |
37 | /**
38 | * Proxy accessing an attribute onto the collection items.
39 | *
40 | * @param string $key
41 | * @return mixed
42 | */
43 | public function __get($key)
44 | {
45 | return $this->collection->{$this->method}(function ($value) use ($key) {
46 | return is_array($value) ? $value[$key] : $value->{$key};
47 | });
48 | }
49 |
50 | /**
51 | * Proxy a method call onto the collection items.
52 | *
53 | * @param string $method
54 | * @param array $parameters
55 | * @return mixed
56 | */
57 | public function __call($method, $parameters)
58 | {
59 | return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
60 | return $value->{$method}(...$parameters);
61 | });
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Collect/Support/Str.php:
--------------------------------------------------------------------------------
1 | $needles
224 | * @param bool $ignoreCase
225 | * @return bool
226 | */
227 | public static function contains($haystack, $needles, $ignoreCase = false)
228 | {
229 | if ($ignoreCase) {
230 | $haystack = mb_strtolower($haystack);
231 | }
232 |
233 | if (! is_iterable($needles)) {
234 | $needles = (array) $needles;
235 | }
236 |
237 | foreach ($needles as $needle) {
238 | if ($ignoreCase) {
239 | $needle = mb_strtolower($needle);
240 | }
241 |
242 | if ($needle !== '' && str_contains($haystack, $needle)) {
243 | return true;
244 | }
245 | }
246 |
247 | return false;
248 | }
249 |
250 | /**
251 | * Determine if a given string contains all array values.
252 | *
253 | * @param string $haystack
254 | * @param iterable $needles
255 | * @param bool $ignoreCase
256 | * @return bool
257 | */
258 | public static function containsAll($haystack, $needles, $ignoreCase = false)
259 | {
260 | foreach ($needles as $needle) {
261 | if (! static::contains($haystack, $needle, $ignoreCase)) {
262 | return false;
263 | }
264 | }
265 |
266 | return true;
267 | }
268 |
269 | /**
270 | * Determine if a given string ends with a given substring.
271 | *
272 | * @param string $haystack
273 | * @param string|iterable $needles
274 | * @return bool
275 | */
276 | public static function endsWith($haystack, $needles)
277 | {
278 | if (! is_iterable($needles)) {
279 | $needles = (array) $needles;
280 | }
281 |
282 | foreach ($needles as $needle) {
283 | if ((string) $needle !== '' && str_ends_with($haystack, $needle)) {
284 | return true;
285 | }
286 | }
287 |
288 | return false;
289 | }
290 |
291 | /**
292 | * Extracts an excerpt from text that matches the first instance of a phrase.
293 | *
294 | * @param string $text
295 | * @param string $phrase
296 | * @param array $options
297 | * @return string|null
298 | */
299 | public static function excerpt($text, $phrase = '', $options = [])
300 | {
301 | $radius = $options['radius'] ?? 100;
302 | $omission = $options['omission'] ?? '...';
303 |
304 | preg_match('/^(.*?)('.preg_quote((string) $phrase).')(.*)$/iu', (string) $text, $matches);
305 |
306 | if (empty($matches)) {
307 | return null;
308 | }
309 |
310 | $start = ltrim($matches[1]);
311 |
312 | $start = str(mb_substr($start, max(mb_strlen($start, 'UTF-8') - $radius, 0), $radius, 'UTF-8'))->ltrim()->unless(
313 | fn ($startWithRadius) => $startWithRadius->exactly($start),
314 | fn ($startWithRadius) => $startWithRadius->prepend($omission),
315 | );
316 |
317 | $end = rtrim($matches[3]);
318 |
319 | $end = str(mb_substr($end, 0, $radius, 'UTF-8'))->rtrim()->unless(
320 | fn ($endWithRadius) => $endWithRadius->exactly($end),
321 | fn ($endWithRadius) => $endWithRadius->append($omission),
322 | );
323 |
324 | return $start->append($matches[2], $end)->toString();
325 | }
326 |
327 | /**
328 | * Cap a string with a single instance of a given value.
329 | *
330 | * @param string $value
331 | * @param string $cap
332 | * @return string
333 | */
334 | public static function finish($value, $cap)
335 | {
336 | $quoted = preg_quote($cap, '/');
337 |
338 | return preg_replace('/(?:'.$quoted.')+$/u', '', $value).$cap;
339 | }
340 |
341 | /**
342 | * Wrap the string with the given strings.
343 | *
344 | * @param string $value
345 | * @param string $before
346 | * @param string|null $after
347 | * @return string
348 | */
349 | public static function wrap($value, $before, $after = null)
350 | {
351 | return $before.$value.($after ??= $before);
352 | }
353 |
354 | /**
355 | * Determine if a given string matches a given pattern.
356 | *
357 | * @param string|iterable $pattern
358 | * @param string $value
359 | * @return bool
360 | */
361 | public static function is($pattern, $value)
362 | {
363 | $value = (string) $value;
364 |
365 | if (! is_iterable($pattern)) {
366 | $pattern = [$pattern];
367 | }
368 |
369 | foreach ($pattern as $pattern) {
370 | $pattern = (string) $pattern;
371 |
372 | // If the given value is an exact match we can of course return true right
373 | // from the beginning. Otherwise, we will translate asterisks and do an
374 | // actual pattern match against the two strings to see if they match.
375 | if ($pattern === $value) {
376 | return true;
377 | }
378 |
379 | $pattern = preg_quote($pattern, '#');
380 |
381 | // Asterisks are translated into zero-or-more regular expression wildcards
382 | // to make it convenient to check if the strings starts with the given
383 | // pattern such as "library/*", making any string check convenient.
384 | $pattern = str_replace('\*', '.*', $pattern);
385 |
386 | if (preg_match('#^'.$pattern.'\z#u', $value) === 1) {
387 | return true;
388 | }
389 | }
390 |
391 | return false;
392 | }
393 |
394 | /**
395 | * Determine if a given string is 7 bit ASCII.
396 | *
397 | * @param string $value
398 | * @return bool
399 | */
400 | public static function isAscii($value)
401 | {
402 | return ASCII::is_ascii((string) $value);
403 | }
404 |
405 | /**
406 | * Determine if a given string is valid JSON.
407 | *
408 | * @param string $value
409 | * @return bool
410 | */
411 | public static function isJson($value)
412 | {
413 | if (! is_string($value)) {
414 | return false;
415 | }
416 |
417 | try {
418 | json_decode($value, true, 512, JSON_THROW_ON_ERROR);
419 | } catch (JsonException) {
420 | return false;
421 | }
422 |
423 | return true;
424 | }
425 |
426 | /**
427 | * Determine if a given string is a valid UUID.
428 | *
429 | * @param string $value
430 | * @return bool
431 | */
432 | public static function isUuid($value)
433 | {
434 | if (! is_string($value)) {
435 | return false;
436 | }
437 |
438 | return preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0;
439 | }
440 |
441 | /**
442 | * Determine if a given string is a valid ULID.
443 | *
444 | * @param string $value
445 | * @return bool
446 | */
447 | public static function isUlid($value)
448 | {
449 | if (! is_string($value)) {
450 | return false;
451 | }
452 |
453 | return Ulid::isValid($value);
454 | }
455 |
456 | /**
457 | * Convert a string to kebab case.
458 | *
459 | * @param string $value
460 | * @return string
461 | */
462 | public static function kebab($value)
463 | {
464 | return static::snake($value, '-');
465 | }
466 |
467 | /**
468 | * Return the length of the given string.
469 | *
470 | * @param string $value
471 | * @param string|null $encoding
472 | * @return int
473 | */
474 | public static function length($value, $encoding = null)
475 | {
476 | if ($encoding) {
477 | return mb_strlen($value, $encoding);
478 | }
479 |
480 | return mb_strlen($value);
481 | }
482 |
483 | /**
484 | * Limit the number of characters in a string.
485 | *
486 | * @param string $value
487 | * @param int $limit
488 | * @param string $end
489 | * @return string
490 | */
491 | public static function limit($value, $limit = 100, $end = '...')
492 | {
493 | if (mb_strwidth($value, 'UTF-8') <= $limit) {
494 | return $value;
495 | }
496 |
497 | return rtrim(mb_strimwidth($value, 0, $limit, '', 'UTF-8')).$end;
498 | }
499 |
500 | /**
501 | * Convert the given string to lower-case.
502 | *
503 | * @param string $value
504 | * @return string
505 | */
506 | public static function lower($value)
507 | {
508 | return mb_strtolower($value, 'UTF-8');
509 | }
510 |
511 | /**
512 | * Limit the number of words in a string.
513 | *
514 | * @param string $value
515 | * @param int $words
516 | * @param string $end
517 | * @return string
518 | */
519 | public static function words($value, $words = 100, $end = '...')
520 | {
521 | preg_match('/^\s*+(?:\S++\s*+){1,'.$words.'}/u', $value, $matches);
522 |
523 | if (! isset($matches[0]) || static::length($value) === static::length($matches[0])) {
524 | return $value;
525 | }
526 |
527 | return rtrim($matches[0]).$end;
528 | }
529 |
530 | /**
531 | * Converts GitHub flavored Markdown into HTML.
532 | *
533 | * @param string $string
534 | * @param array $options
535 | * @return string
536 | */
537 | public static function markdown($string, array $options = [])
538 | {
539 | $converter = new GithubFlavoredMarkdownConverter($options);
540 |
541 | return (string) $converter->convert($string);
542 | }
543 |
544 | /**
545 | * Converts inline Markdown into HTML.
546 | *
547 | * @param string $string
548 | * @param array $options
549 | * @return string
550 | */
551 | public static function inlineMarkdown($string, array $options = [])
552 | {
553 | $environment = new Environment($options);
554 |
555 | $environment->addExtension(new GithubFlavoredMarkdownExtension());
556 | $environment->addExtension(new InlinesOnlyExtension());
557 |
558 | $converter = new MarkdownConverter($environment);
559 |
560 | return (string) $converter->convert($string);
561 | }
562 |
563 | /**
564 | * Masks a portion of a string with a repeated character.
565 | *
566 | * @param string $string
567 | * @param string $character
568 | * @param int $index
569 | * @param int|null $length
570 | * @param string $encoding
571 | * @return string
572 | */
573 | public static function mask($string, $character, $index, $length = null, $encoding = 'UTF-8')
574 | {
575 | if ($character === '') {
576 | return $string;
577 | }
578 |
579 | $segment = mb_substr($string, $index, $length, $encoding);
580 |
581 | if ($segment === '') {
582 | return $string;
583 | }
584 |
585 | $strlen = mb_strlen($string, $encoding);
586 | $startIndex = $index;
587 |
588 | if ($index < 0) {
589 | $startIndex = $index < -$strlen ? 0 : $strlen + $index;
590 | }
591 |
592 | $start = mb_substr($string, 0, $startIndex, $encoding);
593 | $segmentLen = mb_strlen($segment, $encoding);
594 | $end = mb_substr($string, $startIndex + $segmentLen);
595 |
596 | return $start.str_repeat(mb_substr($character, 0, 1, $encoding), $segmentLen).$end;
597 | }
598 |
599 | /**
600 | * Get the string matching the given pattern.
601 | *
602 | * @param string $pattern
603 | * @param string $subject
604 | * @return string
605 | */
606 | public static function match($pattern, $subject)
607 | {
608 | preg_match($pattern, $subject, $matches);
609 |
610 | if (! $matches) {
611 | return '';
612 | }
613 |
614 | return $matches[1] ?? $matches[0];
615 | }
616 |
617 | /**
618 | * Get the string matching the given pattern.
619 | *
620 | * @param string $pattern
621 | * @param string $subject
622 | * @return \QL\Collect\Support\Collection
623 | */
624 | public static function matchAll($pattern, $subject)
625 | {
626 | preg_match_all($pattern, $subject, $matches);
627 |
628 | if (empty($matches[0])) {
629 | return collect();
630 | }
631 |
632 | return collect($matches[1] ?? $matches[0]);
633 | }
634 |
635 | /**
636 | * Pad both sides of a string with another.
637 | *
638 | * @param string $value
639 | * @param int $length
640 | * @param string $pad
641 | * @return string
642 | */
643 | public static function padBoth($value, $length, $pad = ' ')
644 | {
645 | $short = max(0, $length - mb_strlen($value));
646 | $shortLeft = floor($short / 2);
647 | $shortRight = ceil($short / 2);
648 |
649 | return mb_substr(str_repeat($pad, $shortLeft), 0, $shortLeft).
650 | $value.
651 | mb_substr(str_repeat($pad, $shortRight), 0, $shortRight);
652 | }
653 |
654 | /**
655 | * Pad the left side of a string with another.
656 | *
657 | * @param string $value
658 | * @param int $length
659 | * @param string $pad
660 | * @return string
661 | */
662 | public static function padLeft($value, $length, $pad = ' ')
663 | {
664 | $short = max(0, $length - mb_strlen($value));
665 |
666 | return mb_substr(str_repeat($pad, $short), 0, $short).$value;
667 | }
668 |
669 | /**
670 | * Pad the right side of a string with another.
671 | *
672 | * @param string $value
673 | * @param int $length
674 | * @param string $pad
675 | * @return string
676 | */
677 | public static function padRight($value, $length, $pad = ' ')
678 | {
679 | $short = max(0, $length - mb_strlen($value));
680 |
681 | return $value.mb_substr(str_repeat($pad, $short), 0, $short);
682 | }
683 |
684 | /**
685 | * Parse a Class[@]method style callback into class and method.
686 | *
687 | * @param string $callback
688 | * @param string|null $default
689 | * @return array
690 | */
691 | public static function parseCallback($callback, $default = null)
692 | {
693 | return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
694 | }
695 |
696 | /**
697 | * Get the plural form of an English word.
698 | *
699 | * @param string $value
700 | * @param int|array|\Countable $count
701 | * @return string
702 | */
703 | public static function plural($value, $count = 2)
704 | {
705 | return Pluralizer::plural($value, $count);
706 | }
707 |
708 | /**
709 | * Pluralize the last word of an English, studly caps case string.
710 | *
711 | * @param string $value
712 | * @param int|array|\Countable $count
713 | * @return string
714 | */
715 | public static function pluralStudly($value, $count = 2)
716 | {
717 | $parts = preg_split('/(.)(?=[A-Z])/u', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
718 |
719 | $lastWord = array_pop($parts);
720 |
721 | return implode('', $parts).self::plural($lastWord, $count);
722 | }
723 |
724 | /**
725 | * Generate a more truly "random" alpha-numeric string.
726 | *
727 | * @param int $length
728 | * @return string
729 | */
730 | public static function random($length = 16)
731 | {
732 | return (static::$randomStringFactory ?? function ($length) {
733 | $string = '';
734 |
735 | while (($len = strlen($string)) < $length) {
736 | $size = $length - $len;
737 |
738 | $bytesSize = (int) ceil($size / 3) * 3;
739 |
740 | $bytes = random_bytes($bytesSize);
741 |
742 | $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size);
743 | }
744 |
745 | return $string;
746 | })($length);
747 | }
748 |
749 | /**
750 | * Set the callable that will be used to generate random strings.
751 | *
752 | * @param callable|null $factory
753 | * @return void
754 | */
755 | public static function createRandomStringsUsing(callable $factory = null)
756 | {
757 | static::$randomStringFactory = $factory;
758 | }
759 |
760 | /**
761 | * Set the sequence that will be used to generate random strings.
762 | *
763 | * @param array $sequence
764 | * @param callable|null $whenMissing
765 | * @return void
766 | */
767 | public static function createRandomStringsUsingSequence(array $sequence, $whenMissing = null)
768 | {
769 | $next = 0;
770 |
771 | $whenMissing ??= function ($length) use (&$next) {
772 | $factoryCache = static::$randomStringFactory;
773 |
774 | static::$randomStringFactory = null;
775 |
776 | $randomString = static::random($length);
777 |
778 | static::$randomStringFactory = $factoryCache;
779 |
780 | $next++;
781 |
782 | return $randomString;
783 | };
784 |
785 | static::createRandomStringsUsing(function ($length) use (&$next, $sequence, $whenMissing) {
786 | if (array_key_exists($next, $sequence)) {
787 | return $sequence[$next++];
788 | }
789 |
790 | return $whenMissing($length);
791 | });
792 | }
793 |
794 | /**
795 | * Indicate that random strings should be created normally and not using a custom factory.
796 | *
797 | * @return void
798 | */
799 | public static function createRandomStringsNormally()
800 | {
801 | static::$randomStringFactory = null;
802 | }
803 |
804 | /**
805 | * Repeat the given string.
806 | *
807 | * @param string $string
808 | * @param int $times
809 | * @return string
810 | */
811 | public static function repeat(string $string, int $times)
812 | {
813 | return str_repeat($string, $times);
814 | }
815 |
816 | /**
817 | * Replace a given value in the string sequentially with an array.
818 | *
819 | * @param string $search
820 | * @param iterable $replace
821 | * @param string $subject
822 | * @return string
823 | */
824 | public static function replaceArray($search, $replace, $subject)
825 | {
826 | if ($replace instanceof Traversable) {
827 | $replace = collect($replace)->all();
828 | }
829 |
830 | $segments = explode($search, $subject);
831 |
832 | $result = array_shift($segments);
833 |
834 | foreach ($segments as $segment) {
835 | $result .= (array_shift($replace) ?? $search).$segment;
836 | }
837 |
838 | return $result;
839 | }
840 |
841 | /**
842 | * Replace the given value in the given string.
843 | *
844 | * @param string|iterable $search
845 | * @param string|iterable $replace
846 | * @param string|iterable $subject
847 | * @return string
848 | */
849 | public static function replace($search, $replace, $subject)
850 | {
851 | if ($search instanceof Traversable) {
852 | $search = collect($search)->all();
853 | }
854 |
855 | if ($replace instanceof Traversable) {
856 | $replace = collect($replace)->all();
857 | }
858 |
859 | if ($subject instanceof Traversable) {
860 | $subject = collect($subject)->all();
861 | }
862 |
863 | return str_replace($search, $replace, $subject);
864 | }
865 |
866 | /**
867 | * Replace the first occurrence of a given value in the string.
868 | *
869 | * @param string $search
870 | * @param string $replace
871 | * @param string $subject
872 | * @return string
873 | */
874 | public static function replaceFirst($search, $replace, $subject)
875 | {
876 | $search = (string) $search;
877 |
878 | if ($search === '') {
879 | return $subject;
880 | }
881 |
882 | $position = strpos($subject, $search);
883 |
884 | if ($position !== false) {
885 | return substr_replace($subject, $replace, $position, strlen($search));
886 | }
887 |
888 | return $subject;
889 | }
890 |
891 | /**
892 | * Replace the last occurrence of a given value in the string.
893 | *
894 | * @param string $search
895 | * @param string $replace
896 | * @param string $subject
897 | * @return string
898 | */
899 | public static function replaceLast($search, $replace, $subject)
900 | {
901 | if ($search === '') {
902 | return $subject;
903 | }
904 |
905 | $position = strrpos($subject, $search);
906 |
907 | if ($position !== false) {
908 | return substr_replace($subject, $replace, $position, strlen($search));
909 | }
910 |
911 | return $subject;
912 | }
913 |
914 | /**
915 | * Remove any occurrence of the given string in the subject.
916 | *
917 | * @param string|iterable $search
918 | * @param string $subject
919 | * @param bool $caseSensitive
920 | * @return string
921 | */
922 | public static function remove($search, $subject, $caseSensitive = true)
923 | {
924 | if ($search instanceof Traversable) {
925 | $search = collect($search)->all();
926 | }
927 |
928 | $subject = $caseSensitive
929 | ? str_replace($search, '', $subject)
930 | : str_ireplace($search, '', $subject);
931 |
932 | return $subject;
933 | }
934 |
935 | /**
936 | * Reverse the given string.
937 | *
938 | * @param string $value
939 | * @return string
940 | */
941 | public static function reverse(string $value)
942 | {
943 | return implode(array_reverse(mb_str_split($value)));
944 | }
945 |
946 | /**
947 | * Begin a string with a single instance of a given value.
948 | *
949 | * @param string $value
950 | * @param string $prefix
951 | * @return string
952 | */
953 | public static function start($value, $prefix)
954 | {
955 | $quoted = preg_quote($prefix, '/');
956 |
957 | return $prefix.preg_replace('/^(?:'.$quoted.')+/u', '', $value);
958 | }
959 |
960 | /**
961 | * Convert the given string to upper-case.
962 | *
963 | * @param string $value
964 | * @return string
965 | */
966 | public static function upper($value)
967 | {
968 | return mb_strtoupper($value, 'UTF-8');
969 | }
970 |
971 | /**
972 | * Convert the given string to title case.
973 | *
974 | * @param string $value
975 | * @return string
976 | */
977 | public static function title($value)
978 | {
979 | return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
980 | }
981 |
982 | /**
983 | * Convert the given string to title case for each word.
984 | *
985 | * @param string $value
986 | * @return string
987 | */
988 | public static function headline($value)
989 | {
990 | $parts = explode(' ', $value);
991 |
992 | $parts = count($parts) > 1
993 | ? array_map([static::class, 'title'], $parts)
994 | : array_map([static::class, 'title'], static::ucsplit(implode('_', $parts)));
995 |
996 | $collapsed = static::replace(['-', '_', ' '], '_', implode('_', $parts));
997 |
998 | return implode(' ', array_filter(explode('_', $collapsed)));
999 | }
1000 |
1001 | /**
1002 | * Get the singular form of an English word.
1003 | *
1004 | * @param string $value
1005 | * @return string
1006 | */
1007 | public static function singular($value)
1008 | {
1009 | return Pluralizer::singular($value);
1010 | }
1011 |
1012 | /**
1013 | * Generate a URL friendly "slug" from a given string.
1014 | *
1015 | * @param string $title
1016 | * @param string $separator
1017 | * @param string|null $language
1018 | * @param array $dictionary
1019 | * @return string
1020 | */
1021 | public static function slug($title, $separator = '-', $language = 'en', $dictionary = ['@' => 'at'])
1022 | {
1023 | $title = $language ? static::ascii($title, $language) : $title;
1024 |
1025 | // Convert all dashes/underscores into separator
1026 | $flip = $separator === '-' ? '_' : '-';
1027 |
1028 | $title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
1029 |
1030 | // Replace dictionary words
1031 | foreach ($dictionary as $key => $value) {
1032 | $dictionary[$key] = $separator.$value.$separator;
1033 | }
1034 |
1035 | $title = str_replace(array_keys($dictionary), array_values($dictionary), $title);
1036 |
1037 | // Remove all characters that are not the separator, letters, numbers, or whitespace
1038 | $title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s]+!u', '', static::lower($title));
1039 |
1040 | // Replace all separator characters and whitespace by a single separator
1041 | $title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
1042 |
1043 | return trim($title, $separator);
1044 | }
1045 |
1046 | /**
1047 | * Convert a string to snake case.
1048 | *
1049 | * @param string $value
1050 | * @param string $delimiter
1051 | * @return string
1052 | */
1053 | public static function snake($value, $delimiter = '_')
1054 | {
1055 | $key = $value;
1056 |
1057 | if (isset(static::$snakeCache[$key][$delimiter])) {
1058 | return static::$snakeCache[$key][$delimiter];
1059 | }
1060 |
1061 | if (! ctype_lower($value)) {
1062 | $value = preg_replace('/\s+/u', '', ucwords($value));
1063 |
1064 | $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value));
1065 | }
1066 |
1067 | return static::$snakeCache[$key][$delimiter] = $value;
1068 | }
1069 |
1070 | /**
1071 | * Remove all "extra" blank space from the given string.
1072 | *
1073 | * @param string $value
1074 | * @return string
1075 | */
1076 | public static function squish($value)
1077 | {
1078 | return preg_replace('~(\s|\x{3164})+~u', ' ', preg_replace('~^[\s\x{FEFF}]+|[\s\x{FEFF}]+$~u', '', $value));
1079 | }
1080 |
1081 | /**
1082 | * Determine if a given string starts with a given substring.
1083 | *
1084 | * @param string $haystack
1085 | * @param string|iterable $needles
1086 | * @return bool
1087 | */
1088 | public static function startsWith($haystack, $needles)
1089 | {
1090 | if (! is_iterable($needles)) {
1091 | $needles = [$needles];
1092 | }
1093 |
1094 | foreach ($needles as $needle) {
1095 | if ((string) $needle !== '' && str_starts_with($haystack, $needle)) {
1096 | return true;
1097 | }
1098 | }
1099 |
1100 | return false;
1101 | }
1102 |
1103 | /**
1104 | * Convert a value to studly caps case.
1105 | *
1106 | * @param string $value
1107 | * @return string
1108 | */
1109 | public static function studly($value)
1110 | {
1111 | $key = $value;
1112 |
1113 | if (isset(static::$studlyCache[$key])) {
1114 | return static::$studlyCache[$key];
1115 | }
1116 |
1117 | $words = explode(' ', static::replace(['-', '_'], ' ', $value));
1118 |
1119 | $studlyWords = array_map(fn ($word) => static::ucfirst($word), $words);
1120 |
1121 | return static::$studlyCache[$key] = implode($studlyWords);
1122 | }
1123 |
1124 | /**
1125 | * Returns the portion of the string specified by the start and length parameters.
1126 | *
1127 | * @param string $string
1128 | * @param int $start
1129 | * @param int|null $length
1130 | * @param string $encoding
1131 | * @return string
1132 | */
1133 | public static function substr($string, $start, $length = null, $encoding = 'UTF-8')
1134 | {
1135 | return mb_substr($string, $start, $length, $encoding);
1136 | }
1137 |
1138 | /**
1139 | * Returns the number of substring occurrences.
1140 | *
1141 | * @param string $haystack
1142 | * @param string $needle
1143 | * @param int $offset
1144 | * @param int|null $length
1145 | * @return int
1146 | */
1147 | public static function substrCount($haystack, $needle, $offset = 0, $length = null)
1148 | {
1149 | if (! is_null($length)) {
1150 | return substr_count($haystack, $needle, $offset, $length);
1151 | }
1152 |
1153 | return substr_count($haystack, $needle, $offset);
1154 | }
1155 |
1156 | /**
1157 | * Replace text within a portion of a string.
1158 | *
1159 | * @param string|string[] $string
1160 | * @param string|string[] $replace
1161 | * @param int|int[] $offset
1162 | * @param int|int[]|null $length
1163 | * @return string|string[]
1164 | */
1165 | public static function substrReplace($string, $replace, $offset = 0, $length = null)
1166 | {
1167 | if ($length === null) {
1168 | $length = strlen($string);
1169 | }
1170 |
1171 | return substr_replace($string, $replace, $offset, $length);
1172 | }
1173 |
1174 | /**
1175 | * Swap multiple keywords in a string with other keywords.
1176 | *
1177 | * @param array $map
1178 | * @param string $subject
1179 | * @return string
1180 | */
1181 | public static function swap(array $map, $subject)
1182 | {
1183 | return strtr($subject, $map);
1184 | }
1185 |
1186 | /**
1187 | * Make a string's first character lowercase.
1188 | *
1189 | * @param string $string
1190 | * @return string
1191 | */
1192 | public static function lcfirst($string)
1193 | {
1194 | return static::lower(static::substr($string, 0, 1)).static::substr($string, 1);
1195 | }
1196 |
1197 | /**
1198 | * Make a string's first character uppercase.
1199 | *
1200 | * @param string $string
1201 | * @return string
1202 | */
1203 | public static function ucfirst($string)
1204 | {
1205 | return static::upper(static::substr($string, 0, 1)).static::substr($string, 1);
1206 | }
1207 |
1208 | /**
1209 | * Split a string into pieces by uppercase characters.
1210 | *
1211 | * @param string $string
1212 | * @return string[]
1213 | */
1214 | public static function ucsplit($string)
1215 | {
1216 | return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY);
1217 | }
1218 |
1219 | /**
1220 | * Get the number of words a string contains.
1221 | *
1222 | * @param string $string
1223 | * @param string|null $characters
1224 | * @return int
1225 | */
1226 | public static function wordCount($string, $characters = null)
1227 | {
1228 | return str_word_count($string, 0, $characters);
1229 | }
1230 |
1231 | /**
1232 | * Generate a UUID (version 4).
1233 | *
1234 | * @return \Ramsey\Uuid\UuidInterface
1235 | */
1236 | public static function uuid()
1237 | {
1238 | return static::$uuidFactory
1239 | ? call_user_func(static::$uuidFactory)
1240 | : Uuid::uuid4();
1241 | }
1242 |
1243 | /**
1244 | * Generate a time-ordered UUID (version 4).
1245 | *
1246 | * @return \Ramsey\Uuid\UuidInterface
1247 | */
1248 | public static function orderedUuid()
1249 | {
1250 | if (static::$uuidFactory) {
1251 | return call_user_func(static::$uuidFactory);
1252 | }
1253 |
1254 | $factory = new UuidFactory;
1255 |
1256 | $factory->setRandomGenerator(new CombGenerator(
1257 | $factory->getRandomGenerator(),
1258 | $factory->getNumberConverter()
1259 | ));
1260 |
1261 | $factory->setCodec(new TimestampFirstCombCodec(
1262 | $factory->getUuidBuilder()
1263 | ));
1264 |
1265 | return $factory->uuid4();
1266 | }
1267 |
1268 | /**
1269 | * Set the callable that will be used to generate UUIDs.
1270 | *
1271 | * @param callable|null $factory
1272 | * @return void
1273 | */
1274 | public static function createUuidsUsing(callable $factory = null)
1275 | {
1276 | static::$uuidFactory = $factory;
1277 | }
1278 |
1279 | /**
1280 | * Set the sequence that will be used to generate UUIDs.
1281 | *
1282 | * @param array $sequence
1283 | * @param callable|null $whenMissing
1284 | * @return void
1285 | */
1286 | public static function createUuidsUsingSequence(array $sequence, $whenMissing = null)
1287 | {
1288 | $next = 0;
1289 |
1290 | $whenMissing ??= function () use (&$next) {
1291 | $factoryCache = static::$uuidFactory;
1292 |
1293 | static::$uuidFactory = null;
1294 |
1295 | $uuid = static::uuid();
1296 |
1297 | static::$uuidFactory = $factoryCache;
1298 |
1299 | $next++;
1300 |
1301 | return $uuid;
1302 | };
1303 |
1304 | static::createUuidsUsing(function () use (&$next, $sequence, $whenMissing) {
1305 | if (array_key_exists($next, $sequence)) {
1306 | return $sequence[$next++];
1307 | }
1308 |
1309 | return $whenMissing();
1310 | });
1311 | }
1312 |
1313 | /**
1314 | * Always return the same UUID when generating new UUIDs.
1315 | *
1316 | * @param \Closure|null $callback
1317 | * @return \Ramsey\Uuid\UuidInterface
1318 | */
1319 | public static function freezeUuids(Closure $callback = null)
1320 | {
1321 | $uuid = Str::uuid();
1322 |
1323 | Str::createUuidsUsing(fn () => $uuid);
1324 |
1325 | if ($callback !== null) {
1326 | try {
1327 | $callback($uuid);
1328 | } finally {
1329 | Str::createUuidsNormally();
1330 | }
1331 | }
1332 |
1333 | return $uuid;
1334 | }
1335 |
1336 | /**
1337 | * Indicate that UUIDs should be created normally and not using a custom factory.
1338 | *
1339 | * @return void
1340 | */
1341 | public static function createUuidsNormally()
1342 | {
1343 | static::$uuidFactory = null;
1344 | }
1345 |
1346 | /**
1347 | * Generate a ULID.
1348 | *
1349 | * @return \Symfony\Component\Uid\Ulid
1350 | */
1351 | public static function ulid()
1352 | {
1353 | return new Ulid();
1354 | }
1355 |
1356 | /**
1357 | * Remove all strings from the casing caches.
1358 | *
1359 | * @return void
1360 | */
1361 | public static function flushCache()
1362 | {
1363 | static::$snakeCache = [];
1364 | static::$camelCache = [];
1365 | static::$studlyCache = [];
1366 | }
1367 | }
1368 |
--------------------------------------------------------------------------------
/src/Collect/Support/Traits/Conditionable.php:
--------------------------------------------------------------------------------
1 | condition($value);
31 | }
32 |
33 | if ($value) {
34 | return $callback($this, $value) ?? $this;
35 | } elseif ($default) {
36 | return $default($this, $value) ?? $this;
37 | }
38 |
39 | return $this;
40 | }
41 |
42 | /**
43 | * Apply the callback if the given "value" is (or resolves to) falsy.
44 | *
45 | * @template TUnlessParameter
46 | * @template TUnlessReturnType
47 | *
48 | * @param (\Closure($this): TUnlessParameter)|TUnlessParameter|null $value
49 | * @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $callback
50 | * @param (callable($this, TUnlessParameter): TUnlessReturnType)|null $default
51 | * @return $this|TUnlessReturnType
52 | */
53 | public function unless($value = null, callable $callback = null, callable $default = null)
54 | {
55 | $value = $value instanceof Closure ? $value($this) : $value;
56 |
57 | if (func_num_args() === 0) {
58 | return (new HigherOrderWhenProxy($this))->negateConditionOnCapture();
59 | }
60 |
61 | if (func_num_args() === 1) {
62 | return (new HigherOrderWhenProxy($this))->condition(! $value);
63 | }
64 |
65 | if (! $value) {
66 | return $callback($this, $value) ?? $this;
67 | } elseif ($default) {
68 | return $default($this, $value) ?? $this;
69 | }
70 |
71 | return $this;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Collect/Support/Traits/EnumeratesValues.php:
--------------------------------------------------------------------------------
1 |
68 | */
69 | protected static $proxies = [
70 | 'average',
71 | 'avg',
72 | 'contains',
73 | 'doesntContain',
74 | 'each',
75 | 'every',
76 | 'filter',
77 | 'first',
78 | 'flatMap',
79 | 'groupBy',
80 | 'keyBy',
81 | 'map',
82 | 'max',
83 | 'min',
84 | 'partition',
85 | 'reject',
86 | 'skipUntil',
87 | 'skipWhile',
88 | 'some',
89 | 'sortBy',
90 | 'sortByDesc',
91 | 'sum',
92 | 'takeUntil',
93 | 'takeWhile',
94 | 'unique',
95 | 'unless',
96 | 'until',
97 | 'when',
98 | ];
99 |
100 | /**
101 | * Create a new collection instance if the value isn't one already.
102 | *
103 | * @template TMakeKey of array-key
104 | * @template TMakeValue
105 | *
106 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable|null $items
107 | * @return static
108 | */
109 | public static function make($items = [])
110 | {
111 | return new static($items);
112 | }
113 |
114 | /**
115 | * Wrap the given value in a collection if applicable.
116 | *
117 | * @template TWrapValue
118 | *
119 | * @param iterable|TWrapValue $value
120 | * @return static
121 | */
122 | public static function wrap($value)
123 | {
124 | return $value instanceof Enumerable
125 | ? new static($value)
126 | : new static(Arr::wrap($value));
127 | }
128 |
129 | /**
130 | * Get the underlying items from the given collection if applicable.
131 | *
132 | * @template TUnwrapKey of array-key
133 | * @template TUnwrapValue
134 | *
135 | * @param array|static $value
136 | * @return array
137 | */
138 | public static function unwrap($value)
139 | {
140 | return $value instanceof Enumerable ? $value->all() : $value;
141 | }
142 |
143 | /**
144 | * Create a new instance with no items.
145 | *
146 | * @return static
147 | */
148 | public static function empty()
149 | {
150 | return new static([]);
151 | }
152 |
153 | /**
154 | * Create a new collection by invoking the callback a given amount of times.
155 | *
156 | * @template TTimesValue
157 | *
158 | * @param int $number
159 | * @param (callable(int): TTimesValue)|null $callback
160 | * @return static
161 | */
162 | public static function times($number, callable $callback = null)
163 | {
164 | if ($number < 1) {
165 | return new static;
166 | }
167 |
168 | return static::range(1, $number)
169 | ->unless($callback == null)
170 | ->map($callback);
171 | }
172 |
173 | /**
174 | * Alias for the "avg" method.
175 | *
176 | * @param (callable(TValue): float|int)|string|null $callback
177 | * @return float|int|null
178 | */
179 | public function average($callback = null)
180 | {
181 | return $this->avg($callback);
182 | }
183 |
184 | /**
185 | * Alias for the "contains" method.
186 | *
187 | * @param (callable(TValue, TKey): bool)|TValue|string $key
188 | * @param mixed $operator
189 | * @param mixed $value
190 | * @return bool
191 | */
192 | public function some($key, $operator = null, $value = null)
193 | {
194 | return $this->contains(...func_get_args());
195 | }
196 |
197 | /**
198 | * Dump the items and end the script.
199 | *
200 | * @param mixed ...$args
201 | * @return never
202 | */
203 | public function dd(...$args)
204 | {
205 | $this->dump(...$args);
206 |
207 | exit(1);
208 | }
209 |
210 | /**
211 | * Dump the items.
212 | *
213 | * @return $this
214 | */
215 | public function dump()
216 | {
217 | (new Collection(func_get_args()))
218 | ->push($this->all())
219 | ->each(function ($item) {
220 | VarDumper::dump($item);
221 | });
222 |
223 | return $this;
224 | }
225 |
226 | /**
227 | * Execute a callback over each item.
228 | *
229 | * @param callable(TValue, TKey): mixed $callback
230 | * @return $this
231 | */
232 | public function each(callable $callback)
233 | {
234 | foreach ($this as $key => $item) {
235 | if ($callback($item, $key) === false) {
236 | break;
237 | }
238 | }
239 |
240 | return $this;
241 | }
242 |
243 | /**
244 | * Execute a callback over each nested chunk of items.
245 | *
246 | * @param callable(...mixed): mixed $callback
247 | * @return static
248 | */
249 | public function eachSpread(callable $callback)
250 | {
251 | return $this->each(function ($chunk, $key) use ($callback) {
252 | $chunk[] = $key;
253 |
254 | return $callback(...$chunk);
255 | });
256 | }
257 |
258 | /**
259 | * Determine if all items pass the given truth test.
260 | *
261 | * @param (callable(TValue, TKey): bool)|TValue|string $key
262 | * @param mixed $operator
263 | * @param mixed $value
264 | * @return bool
265 | */
266 | public function every($key, $operator = null, $value = null)
267 | {
268 | if (func_num_args() === 1) {
269 | $callback = $this->valueRetriever($key);
270 |
271 | foreach ($this as $k => $v) {
272 | if (! $callback($v, $k)) {
273 | return false;
274 | }
275 | }
276 |
277 | return true;
278 | }
279 |
280 | return $this->every($this->operatorForWhere(...func_get_args()));
281 | }
282 |
283 | /**
284 | * Get the first item by the given key value pair.
285 | *
286 | * @param callable|string $key
287 | * @param mixed $operator
288 | * @param mixed $value
289 | * @return TValue|null
290 | */
291 | public function firstWhere($key, $operator = null, $value = null)
292 | {
293 | return $this->first($this->operatorForWhere(...func_get_args()));
294 | }
295 |
296 | /**
297 | * Get a single key's value from the first matching item in the collection.
298 | *
299 | * @param string $key
300 | * @param mixed $default
301 | * @return mixed
302 | */
303 | public function value($key, $default = null)
304 | {
305 | if ($value = $this->firstWhere($key)) {
306 | return data_get($value, $key, $default);
307 | }
308 |
309 | return value($default);
310 | }
311 |
312 | /**
313 | * Determine if the collection is not empty.
314 | *
315 | * @return bool
316 | */
317 | public function isNotEmpty()
318 | {
319 | return ! $this->isEmpty();
320 | }
321 |
322 | /**
323 | * Run a map over each nested chunk of items.
324 | *
325 | * @template TMapSpreadValue
326 | *
327 | * @param callable(mixed): TMapSpreadValue $callback
328 | * @return static
329 | */
330 | public function mapSpread(callable $callback)
331 | {
332 | return $this->map(function ($chunk, $key) use ($callback) {
333 | $chunk[] = $key;
334 |
335 | return $callback(...$chunk);
336 | });
337 | }
338 |
339 | /**
340 | * Run a grouping map over the items.
341 | *
342 | * The callback should return an associative array with a single key/value pair.
343 | *
344 | * @template TMapToGroupsKey of array-key
345 | * @template TMapToGroupsValue
346 | *
347 | * @param callable(TValue, TKey): array $callback
348 | * @return static>
349 | */
350 | public function mapToGroups(callable $callback)
351 | {
352 | $groups = $this->mapToDictionary($callback);
353 |
354 | return $groups->map([$this, 'make']);
355 | }
356 |
357 | /**
358 | * Map a collection and flatten the result by a single level.
359 | *
360 | * @template TFlatMapKey of array-key
361 | * @template TFlatMapValue
362 | *
363 | * @param callable(TValue, TKey): (\QL\Collect\Support\Collection|array) $callback
364 | * @return static
365 | */
366 | public function flatMap(callable $callback)
367 | {
368 | return $this->map($callback)->collapse();
369 | }
370 |
371 | /**
372 | * Map the values into a new class.
373 | *
374 | * @template TMapIntoValue
375 | *
376 | * @param class-string $class
377 | * @return static
378 | */
379 | public function mapInto($class)
380 | {
381 | return $this->map(fn ($value, $key) => new $class($value, $key));
382 | }
383 |
384 | /**
385 | * Get the min value of a given key.
386 | *
387 | * @param (callable(TValue):mixed)|string|null $callback
388 | * @return mixed
389 | */
390 | public function min($callback = null)
391 | {
392 | $callback = $this->valueRetriever($callback);
393 |
394 | return $this->map(fn ($value) => $callback($value))
395 | ->filter(fn ($value) => ! is_null($value))
396 | ->reduce(fn ($result, $value) => is_null($result) || $value < $result ? $value : $result);
397 | }
398 |
399 | /**
400 | * Get the max value of a given key.
401 | *
402 | * @param (callable(TValue):mixed)|string|null $callback
403 | * @return mixed
404 | */
405 | public function max($callback = null)
406 | {
407 | $callback = $this->valueRetriever($callback);
408 |
409 | return $this->filter(fn ($value) => ! is_null($value))->reduce(function ($result, $item) use ($callback) {
410 | $value = $callback($item);
411 |
412 | return is_null($result) || $value > $result ? $value : $result;
413 | });
414 | }
415 |
416 | /**
417 | * "Paginate" the collection by slicing it into a smaller collection.
418 | *
419 | * @param int $page
420 | * @param int $perPage
421 | * @return static
422 | */
423 | public function forPage($page, $perPage)
424 | {
425 | $offset = max(0, ($page - 1) * $perPage);
426 |
427 | return $this->slice($offset, $perPage);
428 | }
429 |
430 | /**
431 | * Partition the collection into two arrays using the given callback or key.
432 | *
433 | * @param (callable(TValue, TKey): bool)|TValue|string $key
434 | * @param TValue|string|null $operator
435 | * @param TValue|null $value
436 | * @return static, static>
437 | */
438 | public function partition($key, $operator = null, $value = null)
439 | {
440 | $passed = [];
441 | $failed = [];
442 |
443 | $callback = func_num_args() === 1
444 | ? $this->valueRetriever($key)
445 | : $this->operatorForWhere(...func_get_args());
446 |
447 | foreach ($this as $key => $item) {
448 | if ($callback($item, $key)) {
449 | $passed[$key] = $item;
450 | } else {
451 | $failed[$key] = $item;
452 | }
453 | }
454 |
455 | return new static([new static($passed), new static($failed)]);
456 | }
457 |
458 | /**
459 | * Get the sum of the given values.
460 | *
461 | * @param (callable(TValue): mixed)|string|null $callback
462 | * @return mixed
463 | */
464 | public function sum($callback = null)
465 | {
466 | $callback = is_null($callback)
467 | ? $this->identity()
468 | : $this->valueRetriever($callback);
469 |
470 | return $this->reduce(fn ($result, $item) => $result + $callback($item), 0);
471 | }
472 |
473 | /**
474 | * Apply the callback if the collection is empty.
475 | *
476 | * @template TWhenEmptyReturnType
477 | *
478 | * @param (callable($this): TWhenEmptyReturnType) $callback
479 | * @param (callable($this): TWhenEmptyReturnType)|null $default
480 | * @return $this|TWhenEmptyReturnType
481 | */
482 | public function whenEmpty(callable $callback, callable $default = null)
483 | {
484 | return $this->when($this->isEmpty(), $callback, $default);
485 | }
486 |
487 | /**
488 | * Apply the callback if the collection is not empty.
489 | *
490 | * @template TWhenNotEmptyReturnType
491 | *
492 | * @param callable($this): TWhenNotEmptyReturnType $callback
493 | * @param (callable($this): TWhenNotEmptyReturnType)|null $default
494 | * @return $this|TWhenNotEmptyReturnType
495 | */
496 | public function whenNotEmpty(callable $callback, callable $default = null)
497 | {
498 | return $this->when($this->isNotEmpty(), $callback, $default);
499 | }
500 |
501 | /**
502 | * Apply the callback unless the collection is empty.
503 | *
504 | * @template TUnlessEmptyReturnType
505 | *
506 | * @param callable($this): TUnlessEmptyReturnType $callback
507 | * @param (callable($this): TUnlessEmptyReturnType)|null $default
508 | * @return $this|TUnlessEmptyReturnType
509 | */
510 | public function unlessEmpty(callable $callback, callable $default = null)
511 | {
512 | return $this->whenNotEmpty($callback, $default);
513 | }
514 |
515 | /**
516 | * Apply the callback unless the collection is not empty.
517 | *
518 | * @template TUnlessNotEmptyReturnType
519 | *
520 | * @param callable($this): TUnlessNotEmptyReturnType $callback
521 | * @param (callable($this): TUnlessNotEmptyReturnType)|null $default
522 | * @return $this|TUnlessNotEmptyReturnType
523 | */
524 | public function unlessNotEmpty(callable $callback, callable $default = null)
525 | {
526 | return $this->whenEmpty($callback, $default);
527 | }
528 |
529 | /**
530 | * Filter items by the given key value pair.
531 | *
532 | * @param callable|string $key
533 | * @param mixed $operator
534 | * @param mixed $value
535 | * @return static
536 | */
537 | public function where($key, $operator = null, $value = null)
538 | {
539 | return $this->filter($this->operatorForWhere(...func_get_args()));
540 | }
541 |
542 | /**
543 | * Filter items where the value for the given key is null.
544 | *
545 | * @param string|null $key
546 | * @return static
547 | */
548 | public function whereNull($key = null)
549 | {
550 | return $this->whereStrict($key, null);
551 | }
552 |
553 | /**
554 | * Filter items where the value for the given key is not null.
555 | *
556 | * @param string|null $key
557 | * @return static
558 | */
559 | public function whereNotNull($key = null)
560 | {
561 | return $this->where($key, '!==', null);
562 | }
563 |
564 | /**
565 | * Filter items by the given key value pair using strict comparison.
566 | *
567 | * @param string $key
568 | * @param mixed $value
569 | * @return static
570 | */
571 | public function whereStrict($key, $value)
572 | {
573 | return $this->where($key, '===', $value);
574 | }
575 |
576 | /**
577 | * Filter items by the given key value pair.
578 | *
579 | * @param string $key
580 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
581 | * @param bool $strict
582 | * @return static
583 | */
584 | public function whereIn($key, $values, $strict = false)
585 | {
586 | $values = $this->getArrayableItems($values);
587 |
588 | return $this->filter(fn ($item) => in_array(data_get($item, $key), $values, $strict));
589 | }
590 |
591 | /**
592 | * Filter items by the given key value pair using strict comparison.
593 | *
594 | * @param string $key
595 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
596 | * @return static
597 | */
598 | public function whereInStrict($key, $values)
599 | {
600 | return $this->whereIn($key, $values, true);
601 | }
602 |
603 | /**
604 | * Filter items such that the value of the given key is between the given values.
605 | *
606 | * @param string $key
607 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
608 | * @return static
609 | */
610 | public function whereBetween($key, $values)
611 | {
612 | return $this->where($key, '>=', reset($values))->where($key, '<=', end($values));
613 | }
614 |
615 | /**
616 | * Filter items such that the value of the given key is not between the given values.
617 | *
618 | * @param string $key
619 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
620 | * @return static
621 | */
622 | public function whereNotBetween($key, $values)
623 | {
624 | return $this->filter(
625 | fn ($item) => data_get($item, $key) < reset($values) || data_get($item, $key) > end($values)
626 | );
627 | }
628 |
629 | /**
630 | * Filter items by the given key value pair.
631 | *
632 | * @param string $key
633 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
634 | * @param bool $strict
635 | * @return static
636 | */
637 | public function whereNotIn($key, $values, $strict = false)
638 | {
639 | $values = $this->getArrayableItems($values);
640 |
641 | return $this->reject(fn ($item) => in_array(data_get($item, $key), $values, $strict));
642 | }
643 |
644 | /**
645 | * Filter items by the given key value pair using strict comparison.
646 | *
647 | * @param string $key
648 | * @param \QL\Collect\Contracts\Support\Arrayable|iterable $values
649 | * @return static
650 | */
651 | public function whereNotInStrict($key, $values)
652 | {
653 | return $this->whereNotIn($key, $values, true);
654 | }
655 |
656 | /**
657 | * Filter the items, removing any items that don't match the given type(s).
658 | *
659 | * @template TWhereInstanceOf
660 | *
661 | * @param class-string|array> $type
662 | * @return static
663 | */
664 | public function whereInstanceOf($type)
665 | {
666 | return $this->filter(function ($value) use ($type) {
667 | if (is_array($type)) {
668 | foreach ($type as $classType) {
669 | if ($value instanceof $classType) {
670 | return true;
671 | }
672 | }
673 |
674 | return false;
675 | }
676 |
677 | return $value instanceof $type;
678 | });
679 | }
680 |
681 | /**
682 | * Pass the collection to the given callback and return the result.
683 | *
684 | * @template TPipeReturnType
685 | *
686 | * @param callable($this): TPipeReturnType $callback
687 | * @return TPipeReturnType
688 | */
689 | public function pipe(callable $callback)
690 | {
691 | return $callback($this);
692 | }
693 |
694 | /**
695 | * Pass the collection into a new class.
696 | *
697 | * @param class-string $class
698 | * @return mixed
699 | */
700 | public function pipeInto($class)
701 | {
702 | return new $class($this);
703 | }
704 |
705 | /**
706 | * Pass the collection through a series of callable pipes and return the result.
707 | *
708 | * @param array $callbacks
709 | * @return mixed
710 | */
711 | public function pipeThrough($callbacks)
712 | {
713 | return Collection::make($callbacks)->reduce(
714 | fn ($carry, $callback) => $callback($carry),
715 | $this,
716 | );
717 | }
718 |
719 | /**
720 | * Reduce the collection to a single value.
721 | *
722 | * @template TReduceInitial
723 | * @template TReduceReturnType
724 | *
725 | * @param callable(TReduceInitial|TReduceReturnType, TValue, TKey): TReduceReturnType $callback
726 | * @param TReduceInitial $initial
727 | * @return TReduceReturnType
728 | */
729 | public function reduce(callable $callback, $initial = null)
730 | {
731 | $result = $initial;
732 |
733 | foreach ($this as $key => $value) {
734 | $result = $callback($result, $value, $key);
735 | }
736 |
737 | return $result;
738 | }
739 |
740 | /**
741 | * Reduce the collection to multiple aggregate values.
742 | *
743 | * @param callable $callback
744 | * @param mixed ...$initial
745 | * @return array
746 | *
747 | * @throws \UnexpectedValueException
748 | */
749 | public function reduceSpread(callable $callback, ...$initial)
750 | {
751 | $result = $initial;
752 |
753 | foreach ($this as $key => $value) {
754 | $result = call_user_func_array($callback, array_merge($result, [$value, $key]));
755 |
756 | if (! is_array($result)) {
757 | throw new UnexpectedValueException(sprintf(
758 | "%s::reduceSpread expects reducer to return an array, but got a '%s' instead.",
759 | class_basename(static::class), gettype($result)
760 | ));
761 | }
762 | }
763 |
764 | return $result;
765 | }
766 |
767 | /**
768 | * Create a collection of all elements that do not pass a given truth test.
769 | *
770 | * @param (callable(TValue, TKey): bool)|bool|TValue $callback
771 | * @return static
772 | */
773 | public function reject($callback = true)
774 | {
775 | $useAsCallable = $this->useAsCallable($callback);
776 |
777 | return $this->filter(function ($value, $key) use ($callback, $useAsCallable) {
778 | return $useAsCallable
779 | ? ! $callback($value, $key)
780 | : $value != $callback;
781 | });
782 | }
783 |
784 | /**
785 | * Pass the collection to the given callback and then return it.
786 | *
787 | * @param callable($this): mixed $callback
788 | * @return $this
789 | */
790 | public function tap(callable $callback)
791 | {
792 | $callback($this);
793 |
794 | return $this;
795 | }
796 |
797 | /**
798 | * Return only unique items from the collection array.
799 | *
800 | * @param (callable(TValue, TKey): mixed)|string|null $key
801 | * @param bool $strict
802 | * @return static
803 | */
804 | public function unique($key = null, $strict = false)
805 | {
806 | $callback = $this->valueRetriever($key);
807 |
808 | $exists = [];
809 |
810 | return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
811 | if (in_array($id = $callback($item, $key), $exists, $strict)) {
812 | return true;
813 | }
814 |
815 | $exists[] = $id;
816 | });
817 | }
818 |
819 | /**
820 | * Return only unique items from the collection array using strict comparison.
821 | *
822 | * @param (callable(TValue, TKey): mixed)|string|null $key
823 | * @return static
824 | */
825 | public function uniqueStrict($key = null)
826 | {
827 | return $this->unique($key, true);
828 | }
829 |
830 | /**
831 | * Collect the values into a collection.
832 | *
833 | * @return \QL\Collect\Support\Collection
834 | */
835 | public function collect()
836 | {
837 | return new Collection($this->all());
838 | }
839 |
840 | /**
841 | * Get the collection of items as a plain array.
842 | *
843 | * @return array
844 | */
845 | public function toArray()
846 | {
847 | return $this->map(fn ($value) => $value instanceof Arrayable ? $value->toArray() : $value)->all();
848 | }
849 |
850 | /**
851 | * Convert the object into something JSON serializable.
852 | *
853 | * @return array
854 | */
855 | public function jsonSerialize(): array
856 | {
857 | return array_map(function ($value) {
858 | if ($value instanceof JsonSerializable) {
859 | return $value->jsonSerialize();
860 | } elseif ($value instanceof Jsonable) {
861 | return json_decode($value->toJson(), true);
862 | } elseif ($value instanceof Arrayable) {
863 | return $value->toArray();
864 | }
865 |
866 | return $value;
867 | }, $this->all());
868 | }
869 |
870 | /**
871 | * Get the collection of items as JSON.
872 | *
873 | * @param int $options
874 | * @return string
875 | */
876 | public function toJson($options = 0)
877 | {
878 | return json_encode($this->jsonSerialize(), $options);
879 | }
880 |
881 | /**
882 | * Get a CachingIterator instance.
883 | *
884 | * @param int $flags
885 | * @return \CachingIterator
886 | */
887 | public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
888 | {
889 | return new CachingIterator($this->getIterator(), $flags);
890 | }
891 |
892 | /**
893 | * Convert the collection to its string representation.
894 | *
895 | * @return string
896 | */
897 | public function __toString()
898 | {
899 | return $this->escapeWhenCastingToString
900 | ? e($this->toJson())
901 | : $this->toJson();
902 | }
903 |
904 | /**
905 | * Indicate that the model's string representation should be escaped when __toString is invoked.
906 | *
907 | * @param bool $escape
908 | * @return $this
909 | */
910 | public function escapeWhenCastingToString($escape = true)
911 | {
912 | $this->escapeWhenCastingToString = $escape;
913 |
914 | return $this;
915 | }
916 |
917 | /**
918 | * Add a method to the list of proxied methods.
919 | *
920 | * @param string $method
921 | * @return void
922 | */
923 | public static function proxy($method)
924 | {
925 | static::$proxies[] = $method;
926 | }
927 |
928 | /**
929 | * Dynamically access collection proxies.
930 | *
931 | * @param string $key
932 | * @return mixed
933 | *
934 | * @throws \Exception
935 | */
936 | public function __get($key)
937 | {
938 | if (! in_array($key, static::$proxies)) {
939 | throw new Exception("Property [{$key}] does not exist on this collection instance.");
940 | }
941 |
942 | return new HigherOrderCollectionProxy($this, $key);
943 | }
944 |
945 | /**
946 | * Results array of items from Collection or Arrayable.
947 | *
948 | * @param mixed $items
949 | * @return array
950 | */
951 | protected function getArrayableItems($items)
952 | {
953 | if (is_array($items)) {
954 | return $items;
955 | } elseif ($items instanceof Enumerable) {
956 | return $items->all();
957 | } elseif ($items instanceof Arrayable) {
958 | return $items->toArray();
959 | } elseif ($items instanceof Traversable) {
960 | return iterator_to_array($items);
961 | } elseif ($items instanceof Jsonable) {
962 | return json_decode($items->toJson(), true);
963 | } elseif ($items instanceof JsonSerializable) {
964 | return (array) $items->jsonSerialize();
965 | } elseif ($items instanceof UnitEnum) {
966 | return [$items];
967 | }
968 |
969 | return (array) $items;
970 | }
971 |
972 | /**
973 | * Get an operator checker callback.
974 | *
975 | * @param callable|string $key
976 | * @param string|null $operator
977 | * @param mixed $value
978 | * @return \Closure
979 | */
980 | protected function operatorForWhere($key, $operator = null, $value = null)
981 | {
982 | if ($this->useAsCallable($key)) {
983 | return $key;
984 | }
985 |
986 | if (func_num_args() === 1) {
987 | $value = true;
988 |
989 | $operator = '=';
990 | }
991 |
992 | if (func_num_args() === 2) {
993 | $value = $operator;
994 |
995 | $operator = '=';
996 | }
997 |
998 | return function ($item) use ($key, $operator, $value) {
999 | $retrieved = data_get($item, $key);
1000 |
1001 | $strings = array_filter([$retrieved, $value], function ($value) {
1002 | return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
1003 | });
1004 |
1005 | if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
1006 | return in_array($operator, ['!=', '<>', '!==']);
1007 | }
1008 |
1009 | switch ($operator) {
1010 | default:
1011 | case '=':
1012 | case '==': return $retrieved == $value;
1013 | case '!=':
1014 | case '<>': return $retrieved != $value;
1015 | case '<': return $retrieved < $value;
1016 | case '>': return $retrieved > $value;
1017 | case '<=': return $retrieved <= $value;
1018 | case '>=': return $retrieved >= $value;
1019 | case '===': return $retrieved === $value;
1020 | case '!==': return $retrieved !== $value;
1021 | case '<=>': return $retrieved <=> $value;
1022 | }
1023 | };
1024 | }
1025 |
1026 | /**
1027 | * Determine if the given value is callable, but not a string.
1028 | *
1029 | * @param mixed $value
1030 | * @return bool
1031 | */
1032 | protected function useAsCallable($value)
1033 | {
1034 | return ! is_string($value) && is_callable($value);
1035 | }
1036 |
1037 | /**
1038 | * Get a value retrieving callback.
1039 | *
1040 | * @param callable|string|null $value
1041 | * @return callable
1042 | */
1043 | protected function valueRetriever($value)
1044 | {
1045 | if ($this->useAsCallable($value)) {
1046 | return $value;
1047 | }
1048 |
1049 | return fn ($item) => data_get($item, $value);
1050 | }
1051 |
1052 | /**
1053 | * Make a function to check an item's equality.
1054 | *
1055 | * @param mixed $value
1056 | * @return \Closure(mixed): bool
1057 | */
1058 | protected function equality($value)
1059 | {
1060 | return fn ($item) => $item === $value;
1061 | }
1062 |
1063 | /**
1064 | * Make a function using another function, by negating its result.
1065 | *
1066 | * @param \Closure $callback
1067 | * @return \Closure
1068 | */
1069 | protected function negate(Closure $callback)
1070 | {
1071 | return fn (...$params) => ! $callback(...$params);
1072 | }
1073 |
1074 | /**
1075 | * Make a function that returns what's passed to it.
1076 | *
1077 | * @return \Closure(TValue): TValue
1078 | */
1079 | protected function identity()
1080 | {
1081 | return fn ($value) => $value;
1082 | }
1083 | }
1084 |
--------------------------------------------------------------------------------
/src/Collect/Support/Traits/Macroable.php:
--------------------------------------------------------------------------------
1 | getMethods(
43 | ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
44 | );
45 |
46 | foreach ($methods as $method) {
47 | if ($replace || ! static::hasMacro($method->name)) {
48 | $method->setAccessible(true);
49 | static::macro($method->name, $method->invoke($mixin));
50 | }
51 | }
52 | }
53 |
54 | /**
55 | * Checks if macro is registered.
56 | *
57 | * @param string $name
58 | * @return bool
59 | */
60 | public static function hasMacro($name)
61 | {
62 | return isset(static::$macros[$name]);
63 | }
64 |
65 | /**
66 | * Flush the existing macros.
67 | *
68 | * @return void
69 | */
70 | public static function flushMacros()
71 | {
72 | static::$macros = [];
73 | }
74 |
75 | /**
76 | * Dynamically handle calls to the class.
77 | *
78 | * @param string $method
79 | * @param array $parameters
80 | * @return mixed
81 | *
82 | * @throws \BadMethodCallException
83 | */
84 | public static function __callStatic($method, $parameters)
85 | {
86 | if (! static::hasMacro($method)) {
87 | throw new BadMethodCallException(sprintf(
88 | 'Method %s::%s does not exist.', static::class, $method
89 | ));
90 | }
91 |
92 | $macro = static::$macros[$method];
93 |
94 | if ($macro instanceof Closure) {
95 | $macro = $macro->bindTo(null, static::class);
96 | }
97 |
98 | return $macro(...$parameters);
99 | }
100 |
101 | /**
102 | * Dynamically handle calls to the class.
103 | *
104 | * @param string $method
105 | * @param array $parameters
106 | * @return mixed
107 | *
108 | * @throws \BadMethodCallException
109 | */
110 | public function __call($method, $parameters)
111 | {
112 | if (! static::hasMacro($method)) {
113 | throw new BadMethodCallException(sprintf(
114 | 'Method %s::%s does not exist.', static::class, $method
115 | ));
116 | }
117 |
118 | $macro = static::$macros[$method];
119 |
120 | if ($macro instanceof Closure) {
121 | $macro = $macro->bindTo($this, static::class);
122 | }
123 |
124 | return $macro(...$parameters);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Collect/Support/Traits/Tappable.php:
--------------------------------------------------------------------------------
1 | Illuminate\Contracts\Support\Arrayable::class,
5 | QL\Collect\Contracts\Support\Jsonable::class => Illuminate\Contracts\Support\Jsonable::class,
6 | QL\Collect\Contracts\Support\Htmlable::class => Illuminate\Contracts\Support\Htmlable::class,
7 | QL\Collect\Contracts\Support\CanBeEscapedWhenCastToString::class => Illuminate\Contracts\Support\CanBeEscapedWhenCastToString::class,
8 | QL\Collect\Support\Arr::class => Illuminate\Support\Arr::class,
9 | QL\Collect\Support\Collection::class => Illuminate\Support\Collection::class,
10 | QL\Collect\Support\Enumerable::class => Illuminate\Support\Enumerable::class,
11 | QL\Collect\Support\HigherOrderCollectionProxy::class => Illuminate\Support\HigherOrderCollectionProxy::class,
12 | QL\Collect\Support\LazyCollection::class => Illuminate\Support\LazyCollection::class,
13 | QL\Collect\Support\Traits\EnumeratesValues::class => Illuminate\Support\Traits\EnumeratesValues::class,
14 | ];
15 |
16 | # echo "\n\n-- Aliasing....\n---------------------------------------------\n\n";
17 |
18 | foreach ($aliases as $tighten => $illuminate) {
19 | if (! class_exists($illuminate) && ! interface_exists($illuminate) && ! trait_exists($illuminate)) {
20 | # echo "Aliasing {$tighten} to {$illuminate}.\n";
21 | class_alias($tighten, $illuminate);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Collect/Support/helpers.php:
--------------------------------------------------------------------------------
1 | $segment) {
53 | unset($key[$i]);
54 |
55 | if (is_null($segment)) {
56 | return $target;
57 | }
58 |
59 | if ($segment === '*') {
60 | if ($target instanceof Collection) {
61 | $target = $target->all();
62 | } elseif (! is_array($target)) {
63 | return value($default);
64 | }
65 |
66 | $result = [];
67 |
68 | foreach ($target as $item) {
69 | $result[] = data_get($item, $key);
70 | }
71 |
72 | return in_array('*', $key) ? Arr::collapse($result) : $result;
73 | }
74 |
75 | if (Arr::accessible($target) && Arr::exists($target, $segment)) {
76 | $target = $target[$segment];
77 | } elseif (is_object($target) && isset($target->{$segment})) {
78 | $target = $target->{$segment};
79 | } else {
80 | return value($default);
81 | }
82 | }
83 |
84 | return $target;
85 | }
86 | }
87 |
88 | if (! function_exists('tap')) {
89 | /**
90 | * Call the given Closure with the given value then return the value.
91 | *
92 | * @param mixed $value
93 | * @param callable|null $callback
94 | * @return mixed
95 | */
96 | function tap($value, $callback = null)
97 | {
98 | if (is_null($callback)) {
99 | return new HigherOrderTapProxy($value);
100 | }
101 |
102 | $callback($value);
103 |
104 | return $value;
105 | }
106 | }
107 |
108 | if (! function_exists('class_basename')) {
109 | /**
110 | * Get the class "basename" of the given object / class.
111 | *
112 | * @param string|object $class
113 | * @return string
114 | */
115 | function class_basename($class)
116 | {
117 | $class = is_object($class) ? get_class($class) : $class;
118 |
119 | return basename(str_replace('\\', '/', $class));
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/Config.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL;
9 | use Closure;
10 | use QL\Collect\Support\Collection;
11 |
12 | class Config
13 | {
14 | protected static $instance = null;
15 |
16 | protected $plugins;
17 | protected $binds;
18 |
19 | /**
20 | * Config constructor.
21 | */
22 | public function __construct()
23 | {
24 | $this->plugins = new Collection();
25 | $this->binds = new Collection();
26 | }
27 |
28 |
29 | /**
30 | * Get the Config instance
31 | *
32 | * @return null|Config
33 | */
34 | public static function getInstance()
35 | {
36 | self::$instance || self::$instance = new self();
37 | return self::$instance;
38 | }
39 |
40 | /**
41 | * Global installation plugin
42 | *
43 | * @param $plugins
44 | * @param array ...$opt
45 | * @return $this
46 | */
47 | public function use($plugins,...$opt)
48 | {
49 | if(is_string($plugins)){
50 | $this->plugins->push([$plugins,$opt]);
51 | }else{
52 | $this->plugins = $this->plugins->merge($plugins);
53 | }
54 | return $this;
55 | }
56 |
57 | /**
58 | * Global binding custom method
59 | *
60 | * @param string $name
61 | * @param Closure $provider
62 | * @return $this
63 | */
64 | public function bind(string $name, Closure $provider)
65 | {
66 | $this->binds[$name] = $provider;
67 | return $this;
68 | }
69 |
70 | public function bootstrap(QueryList $queryList)
71 | {
72 | $this->installPlugins($queryList);
73 | $this->installBind($queryList);
74 | }
75 |
76 | protected function installPlugins(QueryList $queryList)
77 | {
78 | $this->plugins->each(function($plugin) use($queryList){
79 | if(is_string($plugin)){
80 | $queryList->use($plugin);
81 | }else{
82 | $queryList->use($plugin[0],...$plugin[1]);
83 | }
84 | });
85 | }
86 |
87 | protected function installBind(QueryList $queryList)
88 | {
89 | $this->binds->each(function ($provider,$name) use($queryList){
90 | $queryList->bind($name,$provider);
91 | });
92 | }
93 |
94 | }
--------------------------------------------------------------------------------
/src/Contracts/PluginContract.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL\Contracts;
9 |
10 | use QL\QueryList;
11 |
12 | interface PluginContract
13 | {
14 | public static function install(QueryList $queryList,...$opt);
15 | }
--------------------------------------------------------------------------------
/src/Contracts/ServiceProviderContract.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/20
6 | */
7 |
8 | namespace QL\Contracts;
9 |
10 | use QL\Kernel;
11 |
12 | interface ServiceProviderContract
13 | {
14 | public function register(Kernel $kernel);
15 | }
--------------------------------------------------------------------------------
/src/Dom/Dom.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/19
6 | */
7 |
8 | namespace QL\Dom;
9 |
10 | use phpQueryObject;
11 |
12 | class Dom
13 | {
14 |
15 | protected $document;
16 |
17 | /**
18 | * Dom constructor.
19 | */
20 | public function __construct(phpQueryObject $document)
21 | {
22 | $this->document = $document;
23 | }
24 |
25 | public function find($selector)
26 | {
27 | $elements = $this->document->find($selector);
28 | return new Elements($elements);
29 | }
30 | }
--------------------------------------------------------------------------------
/src/Dom/Elements.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/19
6 | */
7 |
8 | namespace QL\Dom;
9 |
10 | use phpDocumentor\Reflection\Types\Null_;
11 | use phpQueryObject;
12 | use QL\Collect\Support\Collection;
13 |
14 | /**
15 | * Class Elements
16 | * @package QL\Dom
17 | *
18 | * @method Elements toReference(&$var)
19 | * @method Elements documentFragment($state = null)
20 | * @method Elements toRoot()
21 | * @method Elements getDocumentIDRef(&$documentID)
22 | * @method Elements getDocument()
23 | * @method \DOMDocument getDOMDocument()
24 | * @method Elements getDocumentID()
25 | * @method Elements unloadDocument()
26 | * @method bool isHTML()
27 | * @method bool isXHTML()
28 | * @method bool isXML()
29 | * @method string serialize()
30 | * @method array serializeArray($submit = null)
31 | * @method \DOMElement|\DOMElement[] get($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
32 | * @method string|array getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
33 | * @method string|array getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
34 | * @method Elements newInstance($newStack = null)
35 | * @method Elements find($selectors, $context = null, $noHistory = false)
36 | * @method Elements|bool is($selector, $nodes = null)
37 | * @method Elements filterCallback($callback, $_skipHistory = false)
38 | * @method Elements filter($selectors, $_skipHistory = false)
39 | * @method Elements load($url, $data = null, $callback = null)
40 | * @method Elements trigger($type, $data = [])
41 | * @method Elements triggerHandler($type, $data = [])
42 | * @method Elements bind($type, $data, $callback = null)
43 | * @method Elements unbind($type = null, $callback = null)
44 | * @method Elements change($callback = null)
45 | * @method Elements submit($callback = null)
46 | * @method Elements click($callback = null)
47 | * @method Elements wrapAllOld($wrapper)
48 | * @method Elements wrapAll($wrapper)
49 | * @method Elements wrapAllPHP($codeBefore, $codeAfter)
50 | * @method Elements wrap($wrapper)
51 | * @method Elements wrapPHP($codeBefore, $codeAfter)
52 | * @method Elements wrapInner($wrapper)
53 | * @method Elements wrapInnerPHP($codeBefore, $codeAfter)
54 | * @method Elements contents()
55 | * @method Elements contentsUnwrap()
56 | * @method Elements switchWith($markup)
57 | * @method Elements eq($num)
58 | * @method Elements size()
59 | * @method Elements length()
60 | * @method int count()
61 | * @method Elements end($level = 1)
62 | * @method Elements _clone()
63 | * @method Elements replaceWithPHP($code)
64 | * @method Elements replaceWith($content)
65 | * @method Elements replaceAll($selector)
66 | * @method Elements remove($selector = null)
67 | * @method Elements|string markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null)
68 | * @method string markupOuter($callback1 = null, $callback2 = null, $callback3 = null)
69 | * @method Elements|string html($html = null, $callback1 = null, $callback2 = null, $callback3 = null)
70 | * @method Elements|string xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null)
71 | * @method string htmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
72 | * @method string xmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
73 | * @method Elements php($code)
74 | * @method string markupPHP($code)
75 | * @method string markupOuterPHP()
76 | * @method Elements children($selector)
77 | * @method Elements ancestors($selector)
78 | * @method Elements append($content)
79 | * @method Elements appendPHP($content)
80 | * @method Elements appendTo($seletor)
81 | * @method Elements prepend($content)
82 | * @method Elements prependPHP($content)
83 | * @method Elements prependTo($seletor)
84 | * @method Elements before($content)
85 | * @method Elements beforePHP($content)
86 | * @method Elements insertBefore($seletor)
87 | * @method Elements after($content)
88 | * @method Elements afterPHP($content)
89 | * @method Elements insertAfter($seletor)
90 | * @method Elements insert($target, $type)
91 | * @method int index($subject)
92 | * @method Elements slice($start, $end = null)
93 | * @method Elements reverse()
94 | * @method Elements|string text($text = null, $callback1 = null, $callback2 = null, $callback3 = null)
95 | * @method Elements plugin($class, $file = null)
96 | * @method Elements _next($selector = null)
97 | * @method Elements _prev($selector = null)
98 | * @method Elements prev($selector = null)
99 | * @method Elements prevAll($selector = null)
100 | * @method Elements nextAll($selector = null)
101 | * @method Elements siblings($selector = null)
102 | * @method Elements not($selector = null)
103 | * @method Elements add($selector = null)
104 | * @method Elements parent($selector = null)
105 | * @method Elements parents($selector = null)
106 | * @method Elements stack($nodeTypes = null)
107 | * @method Elements|string attr($attr = null, $value = null)
108 | * @method Elements attrPHP($attr, $code)
109 | * @method Elements removeAttr($attr)
110 | * @method Elements|string val($val = null)
111 | * @method Elements andSelf()
112 | * @method Elements addClass($className)
113 | * @method Elements addClassPHP($className)
114 | * @method bool hasClass($className)
115 | * @method Elements removeClass($className)
116 | * @method Elements toggleClass($className)
117 | * @method Elements _empty()
118 | * @method Elements callback($callback, $param1 = null, $param2 = null, $param3 = null)
119 | * @method string data($key, $value = null)
120 | * @method Elements removeData($key)
121 | * @method void rewind()
122 | * @method Elements current()
123 | * @method int key()
124 | * @method Elements next($cssSelector = null)
125 | * @method bool valid()
126 | * @method bool offsetExists($offset)
127 | * @method Elements offsetGet($offset)
128 | * @method void offsetSet($offset, $value)
129 | * @method string whois($oneNode)
130 | * @method Elements dump()
131 | * @method Elements dumpWhois()
132 | * @method Elements dumpLength()
133 | * @method Elements dumpTree($html, $title)
134 | * @method dumpDie()
135 | */
136 | class Elements
137 | {
138 | /**
139 | * @var phpQueryObject
140 | */
141 | protected $elements;
142 |
143 | /**
144 | * Elements constructor.
145 | * @param $elements
146 | */
147 | public function __construct(phpQueryObject $elements)
148 | {
149 | $this->elements = $elements;
150 | }
151 |
152 | public function __get($name)
153 | {
154 | return property_exists($this->elements, $name) ? $this->elements->$name : $this->elements->attr($name);
155 | }
156 |
157 | public function __call($name, $arguments)
158 | {
159 | $obj = call_user_func_array([$this->elements, $name], $arguments);
160 | if ($obj instanceof phpQueryObject) {
161 | $obj = new self($obj);
162 | } else if (is_string($obj)) {
163 | $obj = trim($obj);
164 | }
165 | return $obj;
166 | }
167 |
168 | /**
169 | * Iterating elements
170 | *
171 | * @param callable $callback
172 | *
173 | * @return $this
174 | */
175 | public function each(callable $callback)
176 | {
177 | foreach ($this->elements as $key => $element) {
178 | $break = $callback(new self(pq($element)), $key);
179 | if ($break === false) {
180 | break;
181 | }
182 | }
183 |
184 | return $this;
185 | }
186 |
187 | /**
188 | * Iterating elements
189 | *
190 | * @param $callback
191 | * @return \Illuminate\Support\Collection|\QL\Collect\Support\Collection
192 | */
193 | public function map($callback)
194 | {
195 | $collection = new Collection();
196 | $this->elements->each(function ($dom) use (& $collection, $callback) {
197 | $collection->push($callback(new self(pq($dom))));
198 | });
199 | return $collection;
200 | }
201 |
202 | /**
203 | * Gets the attributes of all the elements
204 | *
205 | * @param string $attr HTML attribute name
206 | * @return \Illuminate\Support\Collection|\QL\Collect\Support\Collection
207 | */
208 | public function attrs($attr)
209 | {
210 | return $this->map(function ($item) use ($attr) {
211 | return $item->attr($attr);
212 | });
213 | }
214 |
215 | /**
216 | * Gets the text of all the elements
217 | *
218 | * @return \Illuminate\Support\Collection|\QL\Collect\Support\Collection
219 | */
220 | public function texts()
221 | {
222 | return $this->map(function ($item) {
223 | return trim($item->text());
224 | });
225 | }
226 |
227 | /**
228 | * Gets the html of all the elements
229 | *
230 | * @return \Illuminate\Support\Collection|\QL\Collect\Support\Collection
231 | */
232 | public function htmls()
233 | {
234 | return $this->map(function ($item) {
235 | return trim($item->html());
236 | });
237 | }
238 |
239 | /**
240 | * Gets the htmlOuter of all the elements
241 | *
242 | * @return \Illuminate\Support\Collection|\QL\Collect\Support\Collection
243 | */
244 | public function htmlOuters()
245 | {
246 | return $this->map(function ($item) {
247 | return trim($item->htmlOuter());
248 | });
249 | }
250 |
251 |
252 | /**
253 | * @return phpQueryObject
254 | */
255 | public function getElements(): phpQueryObject
256 | {
257 | return $this->elements;
258 | }
259 |
260 | }
--------------------------------------------------------------------------------
/src/Dom/Query.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/21
6 | */
7 |
8 | namespace QL\Dom;
9 |
10 | use QL\Collect\Support\Collection;
11 | use phpQuery;
12 | use phpQueryObject;
13 | use QL\QueryList;
14 | use Closure;
15 |
16 | class Query
17 | {
18 | protected $html;
19 | /**
20 | * @var \phpQueryObject
21 | */
22 | protected $document;
23 | protected $rules;
24 | protected $range = null;
25 | protected $ql;
26 | /**
27 | * @var Collection
28 | */
29 | protected $data;
30 |
31 |
32 | public function __construct(QueryList $ql)
33 | {
34 | $this->ql = $ql;
35 | }
36 |
37 | /**
38 | * @param bool $rel
39 | * @return String
40 | */
41 | public function getHtml($rel = true)
42 | {
43 | return $rel ? $this->document->htmlOuter() : $this->html;
44 | }
45 |
46 | /**
47 | * @param $html
48 | * @param null $charset
49 | * @return QueryList
50 | */
51 | public function setHtml($html, $charset = null)
52 | {
53 | $this->html = value($html);
54 | $this->destroyDocument();
55 | $this->document = phpQuery::newDocumentHTML($this->html, $charset);
56 | return $this->ql;
57 | }
58 |
59 | /**
60 | * Get crawl results
61 | *
62 | * @param Closure|null $callback
63 | * @return Collection|static
64 | */
65 | public function getData(?Closure $callback = null)
66 | {
67 | return $this->handleData($this->data, $callback);
68 | }
69 |
70 | /**
71 | * @param Collection $data
72 | */
73 | public function setData(Collection $data)
74 | {
75 | $this->data = $data;
76 | }
77 |
78 |
79 | /**
80 | * Searches for all elements that match the specified expression.
81 | *
82 | * @param $selector A string containing a selector expression to match elements against.
83 | * @return Elements
84 | */
85 | public function find($selector)
86 | {
87 | return (new Dom($this->document))->find($selector);
88 | }
89 |
90 | /**
91 | * Set crawl rule
92 | *
93 | * $rules = [
94 | * 'rule_name1' => ['selector','HTML attribute | text | html','Tag filter list','callback'],
95 | * 'rule_name2' => ['selector','HTML attribute | text | html','Tag filter list','callback'],
96 | * // ...
97 | * ]
98 | *
99 | * @param array $rules
100 | * @return QueryList
101 | */
102 | public function rules(array $rules)
103 | {
104 | $this->rules = $rules;
105 | return $this->ql;
106 | }
107 |
108 |
109 | /**
110 | * Set the slice area for crawl list
111 | *
112 | * @param $selector
113 | * @return QueryList
114 | */
115 | public function range($selector)
116 | {
117 | $this->range = $selector;
118 | return $this->ql;
119 | }
120 |
121 | /**
122 | * Remove HTML head,try to solve the garbled
123 | *
124 | * @return QueryList
125 | */
126 | public function removeHead()
127 | {
128 | $html = preg_replace('/(|).+?<\/head>/is', '', $this->html);
129 | $html && $this->setHtml($html);
130 | return $this->ql;
131 | }
132 |
133 | /**
134 | * Execute the query rule
135 | *
136 | * @param Closure|null $callback
137 | * @return QueryList
138 | */
139 | public function query(?Closure $callback = null)
140 | {
141 | $this->data = $this->getList();
142 | $this->data = $this->handleData($this->data, $callback);
143 | return $this->ql;
144 | }
145 |
146 | public function handleData(Collection $data, $callback)
147 | {
148 | if (is_callable($callback)) {
149 | if (empty($this->range)) {
150 | $data = new Collection($callback($data->all(), null));
151 | } else {
152 | $data = $data->map($callback);
153 | }
154 | }
155 |
156 | return $data;
157 | }
158 |
159 | protected function getList()
160 | {
161 | $data = [];
162 | if (empty($this->range)) {
163 | foreach ($this->rules as $key => $reg_value) {
164 | $rule = $this->parseRule($reg_value);
165 | $contentElements = $this->document->find($rule['selector']);
166 | $data[$key] = $this->extractContent($contentElements, $key, $rule);
167 | }
168 | } else {
169 | $rangeElements = $this->document->find($this->range);
170 | $i = 0;
171 | foreach ($rangeElements as $element) {
172 | foreach ($this->rules as $key => $reg_value) {
173 | $rule = $this->parseRule($reg_value);
174 | $contentElements = pq($element)->find($rule['selector']);
175 | $data[$i][$key] = $this->extractContent($contentElements, $key, $rule);
176 | }
177 | $i++;
178 | }
179 | }
180 |
181 | return new Collection($data);
182 | }
183 |
184 | protected function extractContent(phpQueryObject $pqObj, $ruleName, $rule)
185 | {
186 | switch ($rule['attr']) {
187 | case 'text':
188 | $content = $this->allowTags($pqObj->html(), $rule['filter_tags']);
189 | break;
190 | case 'texts':
191 | $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) {
192 | return $this->allowTags($element->html(), $rule['filter_tags']);
193 | })->all();
194 | break;
195 | case 'html':
196 | $content = $this->stripTags($pqObj->html(), $rule['filter_tags']);
197 | break;
198 | case 'htmls':
199 | $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) {
200 | return $this->stripTags($element->html(), $rule['filter_tags']);
201 | })->all();
202 | break;
203 | case 'htmlOuter':
204 | $content = $this->stripTags($pqObj->htmlOuter(), $rule['filter_tags']);
205 | break;
206 | case 'htmlOuters':
207 | $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) {
208 | return $this->stripTags($element->htmlOuter(), $rule['filter_tags']);
209 | })->all();
210 | break;
211 | default:
212 | if(preg_match('/attr\((.+)\)/', $rule['attr'], $arr)) {
213 | $content = $pqObj->attr($arr[1]);
214 | } elseif (preg_match('/attrs\((.+)\)/', $rule['attr'], $arr)) {
215 | $content = (new Elements($pqObj))->attrs($arr[1])->all();
216 | } else {
217 | $content = $pqObj->attr($rule['attr']);
218 | }
219 | break;
220 | }
221 |
222 | if (is_callable($rule['handle_callback'])) {
223 | $content = call_user_func($rule['handle_callback'], $content, $ruleName);
224 | }
225 |
226 | return $content;
227 | }
228 |
229 | protected function parseRule($rule)
230 | {
231 | $result = [];
232 | $result['selector'] = $rule[0];
233 | $result['attr'] = $rule[1];
234 | $result['filter_tags'] = $rule[2] ?? '';
235 | $result['handle_callback'] = $rule[3] ?? null;
236 |
237 | return $result;
238 | }
239 |
240 | /**
241 | * 去除特定的html标签
242 | * @param string $html
243 | * @param string $tags_str 多个标签名之间用空格隔开
244 | * @return string
245 | */
246 | protected function stripTags($html, $tags_str)
247 | {
248 | $tagsArr = $this->tag($tags_str);
249 | $html = $this->removeTags($html, $tagsArr[1]);
250 | $p = array();
251 | foreach ($tagsArr[0] as $tag) {
252 | $p[] = "/(<(?:\/" . $tag . "|" . $tag . ")[^>]*>)/i";
253 | }
254 | $html = preg_replace($p, "", trim($html));
255 | return $html;
256 | }
257 |
258 | /**
259 | * 保留特定的html标签
260 | * @param string $html
261 | * @param string $tags_str 多个标签名之间用空格隔开
262 | * @return string
263 | */
264 | protected function allowTags($html, $tags_str)
265 | {
266 | $tagsArr = $this->tag($tags_str);
267 | $html = $this->removeTags($html, $tagsArr[1]);
268 | $allow = '';
269 | foreach ($tagsArr[0] as $tag) {
270 | $allow .= "<$tag> ";
271 | }
272 | return strip_tags(trim($html), $allow);
273 | }
274 |
275 | protected function tag($tags_str)
276 | {
277 | $tagArr = preg_split("/\s+/", $tags_str, -1, PREG_SPLIT_NO_EMPTY);
278 | $tags = array(array(), array());
279 | foreach ($tagArr as $tag) {
280 | if (preg_match('/-(.+)/', $tag, $arr)) {
281 | array_push($tags[1], $arr[1]);
282 | } else {
283 | array_push($tags[0], $tag);
284 | }
285 | }
286 | return $tags;
287 | }
288 |
289 | /**
290 | * 移除特定的html标签
291 | * @param string $html
292 | * @param array $tags 标签数组
293 | * @return string
294 | */
295 | protected function removeTags($html, $tags)
296 | {
297 | $tag_str = '';
298 | if (count($tags)) {
299 | foreach ($tags as $tag) {
300 | $tag_str .= $tag_str ? ',' . $tag : $tag;
301 | }
302 | // phpQuery::$defaultCharset = $this->inputEncoding?$this->inputEncoding:$this->htmlEncoding;
303 | $doc = phpQuery::newDocumentHTML($html);
304 | pq($doc)->find($tag_str)->remove();
305 | $html = pq($doc)->htmlOuter();
306 | $doc->unloadDocument();
307 | }
308 | return $html;
309 | }
310 |
311 | protected function destroyDocument()
312 | {
313 | if ($this->document instanceof phpQueryObject) {
314 | $this->document->unloadDocument();
315 | }
316 | }
317 |
318 | public function __destruct()
319 | {
320 | $this->destroyDocument();
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/src/Exceptions/ServiceNotFoundException.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/21
6 | */
7 |
8 | namespace QL\Exceptions;
9 |
10 | use Exception;
11 |
12 | class ServiceNotFoundException extends Exception
13 | {
14 |
15 | }
--------------------------------------------------------------------------------
/src/Kernel.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/21
6 | */
7 |
8 | namespace QL;
9 |
10 | use QL\Contracts\ServiceProviderContract;
11 | use QL\Exceptions\ServiceNotFoundException;
12 | use QL\Providers\EncodeServiceProvider;
13 | use Closure;
14 | use QL\Providers\HttpServiceProvider;
15 | use QL\Providers\PluginServiceProvider;
16 | use QL\Providers\SystemServiceProvider;
17 | use QL\Collect\Support\Collection;
18 |
19 | class Kernel
20 | {
21 | protected $providers = [
22 | SystemServiceProvider::class,
23 | HttpServiceProvider::class,
24 | EncodeServiceProvider::class,
25 | PluginServiceProvider::class
26 | ];
27 |
28 | protected $binds;
29 | protected $ql;
30 |
31 | /**
32 | * Kernel constructor.
33 | * @param $ql
34 | */
35 | public function __construct(QueryList $ql)
36 | {
37 | $this->ql = $ql;
38 | $this->binds = new Collection();
39 | }
40 |
41 | public function bootstrap()
42 | {
43 | //注册服务提供者
44 | $this->registerProviders();
45 | return $this;
46 | }
47 |
48 | public function registerProviders()
49 | {
50 | foreach ($this->providers as $provider) {
51 | $this->register(new $provider());
52 | }
53 | }
54 |
55 | public function bind(string $name,Closure $provider)
56 | {
57 | $this->binds[$name] = $provider;
58 | }
59 |
60 | public function getService(string $name)
61 | {
62 | if(!$this->binds->offsetExists($name)){
63 | throw new ServiceNotFoundException("Service: {$name} not found!");
64 | }
65 | return $this->binds[$name];
66 | }
67 |
68 | private function register(ServiceProviderContract $instance)
69 | {
70 | $instance->register($this);
71 | }
72 |
73 |
74 | }
--------------------------------------------------------------------------------
/src/Providers/EncodeServiceProvider.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/20
6 | */
7 |
8 | namespace QL\Providers;
9 |
10 | use QL\Contracts\ServiceProviderContract;
11 | use QL\Kernel;
12 | use QL\Services\EncodeService;
13 |
14 | class EncodeServiceProvider implements ServiceProviderContract
15 | {
16 | public function register(Kernel $kernel)
17 | {
18 | $kernel->bind('encoding',function (string $outputEncoding,string $inputEncoding = null){
19 | return EncodeService::convert($this,$outputEncoding,$inputEncoding);
20 | });
21 | }
22 | }
--------------------------------------------------------------------------------
/src/Providers/HttpServiceProvider.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL\Providers;
9 |
10 |
11 | use QL\Contracts\ServiceProviderContract;
12 | use QL\Kernel;
13 | use QL\Services\HttpService;
14 | use QL\Services\MultiRequestService;
15 |
16 | class HttpServiceProvider implements ServiceProviderContract
17 | {
18 | public function register(Kernel $kernel)
19 | {
20 | $kernel->bind('get',function (...$args){
21 | return HttpService::get($this,...$args);
22 | });
23 |
24 | $kernel->bind('post',function (...$args){
25 | return HttpService::post($this,...$args);
26 | });
27 |
28 | $kernel->bind('postJson',function (...$args){
29 | return HttpService::postJson($this,...$args);
30 | });
31 |
32 | $kernel->bind('multiGet',function (...$args){
33 | return new MultiRequestService($this,'get',...$args);
34 | });
35 |
36 | $kernel->bind('multiPost',function (...$args){
37 | return new MultiRequestService($this,'post',...$args);
38 | });
39 | }
40 | }
--------------------------------------------------------------------------------
/src/Providers/PluginServiceProvider.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL\Providers;
9 |
10 | use QL\Contracts\ServiceProviderContract;
11 | use QL\Kernel;
12 | use QL\Services\PluginService;
13 |
14 | class PluginServiceProvider implements ServiceProviderContract
15 | {
16 | public function register(Kernel $kernel)
17 | {
18 | $kernel->bind('use',function ($plugins,...$opt){
19 | return PluginService::install($this,$plugins,...$opt);
20 | });
21 | }
22 |
23 | }
--------------------------------------------------------------------------------
/src/Providers/SystemServiceProvider.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL\Providers;
9 |
10 | use QL\Contracts\ServiceProviderContract;
11 | use QL\Kernel;
12 | use Closure;
13 |
14 | class SystemServiceProvider implements ServiceProviderContract
15 | {
16 | public function register(Kernel $kernel)
17 | {
18 | $kernel->bind('html',function (...$args){
19 | $this->setHtml(...$args);
20 | return $this;
21 | });
22 |
23 | $kernel->bind('queryData',function (Closure $callback = null){
24 | return $this->query()->getData($callback)->all();
25 | });
26 |
27 | $kernel->bind('pipe',function (Closure $callback = null){
28 | return $callback($this);
29 | });
30 |
31 | }
32 | }
--------------------------------------------------------------------------------
/src/QueryList.php:
--------------------------------------------------------------------------------
1 | query = new Query($this);
58 | $this->kernel = (new Kernel($this))->bootstrap();
59 | Config::getInstance()->bootstrap($this);
60 | }
61 |
62 | public function __call($name, $arguments)
63 | {
64 | if(method_exists($this->query,$name)){
65 | $result = $this->query->$name(...$arguments);
66 | }else{
67 | $result = $this->kernel->getService($name)->call($this,...$arguments);
68 | }
69 | return $result;
70 | }
71 |
72 | public static function __callStatic($name, $arguments)
73 | {
74 | $instance = new self();
75 | return $instance->$name(...$arguments);
76 | }
77 |
78 | public function __destruct()
79 | {
80 | $this->destruct();
81 | }
82 |
83 | /**
84 | * Get the QueryList single instance
85 | *
86 | * @return QueryList
87 | */
88 | public static function getInstance()
89 | {
90 | self::$instance || self::$instance = new self();
91 | return self::$instance;
92 | }
93 |
94 | /**
95 | * Get the Config instance
96 | * @return null|Config
97 | */
98 | public static function config()
99 | {
100 | return Config::getInstance();
101 | }
102 |
103 | /**
104 | * Destruction of resources
105 | */
106 | public function destruct()
107 | {
108 | unset($this->query);
109 | unset($this->kernel);
110 | }
111 |
112 | /**
113 | * Destroy all documents
114 | */
115 | public static function destructDocuments()
116 | {
117 | phpQuery::$documents = [];
118 | }
119 |
120 | /**
121 | * Bind a custom method to the QueryList object
122 | *
123 | * @param string $name Invoking the name
124 | * @param Closure $provide Called method
125 | * @return $this
126 | */
127 | public function bind(string $name,Closure $provide)
128 | {
129 | $this->kernel->bind($name,$provide);
130 | return $this;
131 | }
132 |
133 | }
--------------------------------------------------------------------------------
/src/Services/EncodeService.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/20
6 | * 编码转换服务
7 | */
8 |
9 | namespace QL\Services;
10 |
11 | use QL\QueryList;
12 |
13 | class EncodeService
14 | {
15 | public static function convert(QueryList $ql,string $outputEncoding,string $inputEncoding = null)
16 | {
17 | $html = $ql->getHtml();
18 | $inputEncoding || $inputEncoding = self::detect($html);
19 | $html = iconv($inputEncoding,$outputEncoding.'//IGNORE',$html);
20 | $ql->setHtml($html);
21 | return $ql;
22 | }
23 |
24 | /**
25 | * Attempts to detect the encoding
26 | * @param $string
27 | * @return bool|false|mixed|string
28 | */
29 | public static function detect($string)
30 | {
31 | $charset=mb_detect_encoding($string, array('ASCII', 'GB2312', 'GBK', 'UTF-8'),true);
32 | if(strtolower($charset)=='cp936')
33 | $charset='GBK';
34 | return $charset;
35 | }
36 |
37 | }
--------------------------------------------------------------------------------
/src/Services/HttpService.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL\Services;
9 |
10 | use GuzzleHttp\Cookie\CookieJar;
11 | use Jaeger\GHttp;
12 | use QL\QueryList;
13 |
14 | class HttpService
15 | {
16 | protected static $cookieJar = null;
17 |
18 | public static function getCookieJar()
19 | {
20 | if(self::$cookieJar == null)
21 | {
22 | self::$cookieJar = new CookieJar();
23 | }
24 | return self::$cookieJar;
25 | }
26 |
27 | public static function get(QueryList $ql,$url,$args = null,$otherArgs = [])
28 | {
29 | $otherArgs = array_merge([
30 | 'cookies' => self::getCookieJar(),
31 | 'verify' => false
32 | ],$otherArgs);
33 | $html = GHttp::get($url,$args,$otherArgs);
34 | $ql->setHtml($html);
35 | return $ql;
36 | }
37 |
38 | public static function post(QueryList $ql,$url,$args = null,$otherArgs = [])
39 | {
40 | $otherArgs = array_merge([
41 | 'cookies' => self::getCookieJar(),
42 | 'verify' => false
43 | ],$otherArgs);
44 | $html = GHttp::post($url,$args,$otherArgs);
45 | $ql->setHtml($html);
46 | return $ql;
47 | }
48 |
49 | public static function postJson(QueryList $ql,$url,$args = null,$otherArgs = [])
50 | {
51 | $otherArgs = array_merge([
52 | 'cookies' => self::getCookieJar(),
53 | 'verify' => false
54 | ],$otherArgs);
55 | $html = GHttp::postJson($url,$args,$otherArgs);
56 | $ql->setHtml($html);
57 | return $ql;
58 | }
59 | }
--------------------------------------------------------------------------------
/src/Services/MultiRequestService.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 18/12/10
6 | * Time: 下午7:05
7 | */
8 |
9 | namespace QL\Services;
10 |
11 |
12 | use Jaeger\GHttp;
13 | use Closure;
14 | use GuzzleHttp\Psr7\Response;
15 | use QL\QueryList;
16 |
17 | /**
18 | * Class MultiRequestService
19 | * @package QL\Services
20 | *
21 | * @method MultiRequestService withHeaders($headers)
22 | * @method MultiRequestService withOptions($options)
23 | * @method MultiRequestService concurrency($concurrency)
24 | */
25 | class MultiRequestService
26 | {
27 | protected $ql;
28 | protected $multiRequest;
29 | protected $method;
30 |
31 | public function __construct(QueryList $ql,$method,$urls)
32 | {
33 | $this->ql = $ql;
34 | $this->method = $method;
35 | $this->multiRequest = GHttp::multiRequest($urls);
36 | }
37 |
38 | public function __call($name, $arguments)
39 | {
40 | $this->multiRequest = $this->multiRequest->$name(...$arguments);
41 | return $this;
42 | }
43 |
44 | public function success(Closure $success)
45 | {
46 | $this->multiRequest = $this->multiRequest->success(function(Response $response, $index) use($success){
47 | $this->ql->setHtml((String)$response->getBody());
48 | $success($this->ql,$response, $index);
49 | });
50 | return $this;
51 | }
52 |
53 | public function error(Closure $error)
54 | {
55 | $this->multiRequest = $this->multiRequest->error(function($reason, $index) use($error){
56 | $error($this->ql,$reason, $index);
57 | });
58 | return $this;
59 | }
60 |
61 | public function send()
62 | {
63 | $this->multiRequest->{$this->method}();
64 | }
65 | }
--------------------------------------------------------------------------------
/src/Services/PluginService.php:
--------------------------------------------------------------------------------
1 |
5 | * Date: 2017/9/22
6 | */
7 |
8 | namespace QL\Services;
9 |
10 | use QL\QueryList;
11 |
12 | class PluginService
13 | {
14 | public static function install(QueryList $queryList, $plugins, ...$opt)
15 | {
16 | if(is_array($plugins))
17 | {
18 | foreach ($plugins as $plugin) {
19 | $plugin::install($queryList);
20 | }
21 | }else{
22 | $plugins::install($queryList,...$opt);
23 | }
24 | return $queryList;
25 | }
26 | }
--------------------------------------------------------------------------------