├── .github
├── pull_request_template.md
└── workflows
│ └── jsphp.yml
├── .gitignore
├── composer.json
├── composer.lock
├── examples
└── index.php
├── index.php
├── license.txt
├── phpcs.xml
├── phpunit.xml
├── phpunit.xsd
├── readme.md
├── src
├── Core
│ ├── Interfaces
│ │ └── CoreInterface.php
│ └── JsBase.php
├── JsArray.php
├── JsObject.php
├── Traits
│ ├── Arrays
│ │ ├── BasicsTrait.php
│ │ ├── ConditionalTrait.php
│ │ ├── IteratorTrait.php
│ │ ├── ModifierTrait.php
│ │ ├── SearchingTrait.php
│ │ └── SortingTrait.php
│ └── Objects
│ │ └── StaticTrait.php
└── Utilities.php
└── tests
└── unit
├── arrays
├── ArrayBasicsTest.php
├── ArrayConditionalTest.php
├── ArrayIteratorTest.php
├── ArrayModifierTest.php
└── JsArrayTest.php
└── objects
└── ObjectStaticTest.php
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | # Description
2 | Please describe your changes with bullet points.
3 |
4 | # Preview
5 | Share a preview image for better understanding.
6 |
7 | # Task
8 | Share the JIRA task link.
9 |
10 | # Review Requests
11 | @ahamed
12 |
--------------------------------------------------------------------------------
/.github/workflows/jsphp.yml:
--------------------------------------------------------------------------------
1 | name: PHP Composer
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - master
8 | - dev
9 |
10 | jobs:
11 | build:
12 | strategy:
13 | matrix:
14 | operating-system: [ubuntu-latest]
15 | php-versions: ['7.4', '8.0']
16 |
17 | runs-on: ${{ matrix.operating-system }}
18 |
19 | steps:
20 | - uses: actions/checkout@v3
21 |
22 | - name: Get composer cache directory
23 | id: composer-cache
24 | run: echo "::set-output name=dir::$(composer config cache-files-dir)"
25 | - name: Cache composer dependencies
26 | uses: actions/cache@v3
27 | with:
28 | path: ${{ steps.composer-cache.outputs.dir }}
29 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
30 | restore-keys: ${{ runner.os }}-composer-
31 | - name: Install dependencies
32 | run: composer install --no-progress --no-suggest --prefer-dist --optimize-autoloader
33 | - name: Unit Test
34 | run: composer run-script test
35 |
36 | - name: phpcs
37 | run: composer run-script phpcs
38 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .history/
2 | .idea/
3 | .vscode/
4 | .DS_Store
5 | .phpunit.result.cache
6 |
7 | .env
8 |
9 | /vendor
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ahamed/jsphp",
3 | "description": "A php library for implementing Array, Object, String methods in JavaScript way.",
4 | "type": "library",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "Sajeeb Ahamed",
9 | "email": "sajeeb07ahamed@gmail.com"
10 | }
11 | ],
12 | "scripts": {
13 | "test": "./vendor/bin/phpunit --configuration ./phpunit.xml",
14 | "phpcs": "./vendor/bin/phpcs --extensions=php -p ."
15 | },
16 | "minimum-stability": "dev",
17 | "prefer-stable": true,
18 | "require": {
19 | "php": "^7.4|^8.0"
20 | },
21 | "autoload": {
22 | "psr-4": {
23 | "Ahamed\\JsPhp\\": "src/"
24 | }
25 | },
26 | "require-dev": {
27 | "phpunit/phpunit": "^9.2",
28 | "squizlabs/php_codesniffer": "*"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/examples/index.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 |
9 |
10 | ini_set('display_errors', 1);
11 | ini_set('display_startup_errors', 1);
12 | error_reporting(E_ALL);
13 |
14 | require_once __DIR__ . '/../vendor/autoload.php';
15 | use Ahamed\JsPhp\JsArray;
16 | use Ahamed\JsPhp\JsObject;
17 |
18 | $obj = [
19 | 'name' => 'Jon Doe',
20 | 'age' => 24,
21 | 'sex' => 'male',
22 | 'email' => 'john@doe.com',
23 | 'contact' => [
24 | 'mobile' => '01xxxxxx',
25 | 'phone' => '09xxxxx'
26 | ],
27 | 'password' => 'secret'
28 | ];
29 | $object = new JsObject($obj);
30 |
31 | $v = JsObject::fromEntries(JsObject::entries($object)->filter(fn($item) => $item[0] !== 'password'));
32 |
33 | echo '
';
34 | print_r($v);
35 | echo '';
36 | die();
37 |
38 |
39 |
--------------------------------------------------------------------------------
/index.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | require_once __DIR__ . '/bootstrap.php';
9 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) <2020>
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | The JsPhp Coding standard
4 |
5 |
6 |
7 |
8 | ./vendor
9 | ./tests/*
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | 0
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | Multi-line IF statement not indented correctly; expected %s spaces but found %s. Note: the autofixer will convert spaces to tabs
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Multi-line function call not indented correctly; expected %s spaces but found %s. Note: the autofixer will convert spaces to tabs
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | Multi-line function declaration not indented correctly; expected %s spaces but found %s. Note: the autofixer will convert spaces to tabs
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | Object operator not indented correctly; expected %s spaces but found %s. Note: the autofixer will convert spaces to tabs
110 |
111 |
112 |
113 |
114 |
115 | error
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 | Do not use global variables. Use static class properties or constants instead of globals.
145 |
146 |
147 |
148 |
149 | No scope modifier specified for function "%s
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 | Closing brace indented incorrectly; expected %s spaces, found %s. Note: the autofixer will convert spaces to tabs
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | tests
12 |
13 |
14 |
--------------------------------------------------------------------------------
/phpunit.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | This Schema file defines the rules by which the XML configuration file of PHPUnit 9.3 may be structured.
6 |
7 |
8 |
9 |
11 |
12 | Root Element
13 |
14 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
38 |
39 |
42 |
45 |
48 |
51 |
54 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
66 |
69 |
70 |
71 |
73 |
74 |
75 |
76 |
77 |
78 |
81 |
82 |
83 |
84 |
85 |
88 |
89 |
90 |
91 |
92 |
95 |
96 |
97 |
98 |
99 |
101 |
102 |
103 |
104 |
105 |
106 |
109 |
111 |
112 |
113 |
114 |
118 |
119 |
120 |
121 |
122 |
124 |
125 |
126 |
127 |
129 |
131 |
133 |
135 |
137 |
138 |
140 |
142 |
144 |
146 |
147 |
148 |
149 |
150 |
151 |
155 |
159 |
163 |
167 |
170 |
174 |
178 |
182 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
204 |
206 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
217 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
257 |
260 |
261 |
262 |
263 |
264 |
268 |
272 |
276 |
280 |
284 |
288 |
292 |
296 |
300 |
304 |
308 |
309 |
310 |
311 |
312 |
315 |
318 |
321 |
324 |
325 |
326 |
327 | The main type specifying the document structure
328 |
329 |
330 |
331 |
332 |
333 |
336 |
339 |
341 |
344 |
346 |
349 |
352 |
355 |
358 |
361 |
364 |
367 |
370 |
372 |
375 |
378 |
381 |
384 |
387 |
390 |
393 |
396 |
399 |
402 |
405 |
408 |
411 |
414 |
417 |
420 |
423 |
426 |
429 |
432 |
435 |
438 |
441 |
444 |
446 |
449 |
452 |
455 |
458 |
461 |
464 |
466 |
469 |
472 |
475 |
476 |
477 |
478 |
480 |
483 |
486 |
489 |
492 |
495 |
498 |
501 |
502 |
503 |
505 |
508 |
511 |
512 |
513 |
516 |
517 |
518 |
519 |
520 |
521 |
525 |
526 |
529 |
530 |
531 |
532 |
535 |
538 |
541 |
544 |
547 |
550 |
551 |
552 |
553 |
554 |
557 |
560 |
563 |
566 |
569 |
572 |
573 |
574 |
575 |
578 |
579 |
580 |
583 |
584 |
585 |
588 |
590 |
591 |
592 |
595 |
598 |
601 |
602 |
603 |
606 |
609 |
612 |
613 |
614 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |                
6 |
7 |   
8 |
9 | ### Why this library?
10 | While using ***php*** **Array** methods it feels troublesome because of their unstructured patterns.
11 |
12 | For example, you are using the `array_map` and the `array_filter` methods of *php*. At the time of using, you may notice that for the `array_map` method, the `$callback` comes as the first parameter of the method, then the `$array` but for the `array_filter` method, the `$array` comes first then the `$callback`. And this mixed structure exists everywhere.
13 |
14 | Then I've discovered that the **JavaScript** uses a good pattern for these cases and I am also a big fan of JavaScript. That's why I've decided to build this library. I can say that the JavaScript lovers can get the pure feelings of JavaScript by using this and the JavaScript non-lovers also get the advantage of the good structure of **array** manipulations.
15 |
16 | ### Future Journey
17 | Currently I've covered only the `array` methods. In near future I will add the `object` and `string` methods.
18 |
19 | ### Installation
20 | `composer` is needed for installing the package. If you have composer installed then run the command.
21 |
22 | ```console
23 | composer require ahamed/jsphp
24 | ```
25 |
26 | ### Usage
27 | After successful installation, include the library into your project.
28 |
29 | ```php
30 | require_once __DIR__ . '/vendor/autoload.php';
31 |
32 | use Ahamed\JsPhp\JsArray;
33 |
34 | $data = [1, 2, 3, 4, 5];
35 | $array = new JsArray($data);
36 |
37 | $square = $array->map(
38 | function ($item) {
39 | return $item * $item;
40 | }
41 | );
42 |
43 | print_r($square);
44 | ```
45 | # Documentation
46 | > For writing this documentation I've followed the [MDN](https://developer.mozilla.org/en-US/) a lot. Thanks to **MDN**, this site helps me to learn a lot of JS.
47 |
48 | Follow the wiki pages for the details documentation.
49 | + [JsArray](https://github.com/ahamed/JsPhp/wiki/JsArray)
50 | + [JsObject](https://github.com/ahamed/JsPhp/wiki/JsObject)
51 |
52 |
53 |
54 | ### Testing
55 | You can run `PHPUnit` testing and `PHP_CodeSniffer`.
56 | - For running unit test
57 | ```console
58 | composer run-script test
59 | ```
60 | - For running phpcs test
61 | ```console
62 | composer run-script phpcs
63 | ```
64 |
--------------------------------------------------------------------------------
/src/Core/Interfaces/CoreInterface.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Core\Interfaces;
9 |
10 | /**
11 | * The core Interface
12 | *
13 | * @since 1.0.0
14 | */
15 | interface CoreInterface
16 | {
17 | /**
18 | * The bind function to store the element
19 | * i.e. the array|object|string for being modification
20 | *
21 | * @param mixed $elements The element in which the methods will be applied.
22 | * @param boolean $makeMutable If true then bind will mutate the array, Otherwise it will create a new array.
23 | *
24 | * @return void
25 | * @since 1.0.0
26 | */
27 | public function bind($elements, $makeMutable = true);
28 |
29 | /**
30 | * Check the elements is valid or not.
31 | *
32 | * @param mixed $elements The elements to check.
33 | *
34 | * @return bool The checked result. True if the elements are valid, false otherwise.
35 | * @since 1.0.0
36 | */
37 | public function check($elements) : bool;
38 |
39 | /**
40 | * Reset the elements
41 | *
42 | * @return void
43 | * @since 1.0.0
44 | */
45 | public function reset();
46 |
47 | /**
48 | * Get elements
49 | *
50 | * @return array|object|string
51 | * @sine 1.0.0
52 | */
53 | public function get();
54 | }
55 |
--------------------------------------------------------------------------------
/src/Core/JsBase.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Core;
9 | /**
10 | * JavaScript base functionalities
11 | * This is a common class which extends all the helpers
12 | *
13 | */
14 | class JsBase
15 | {
16 | /**
17 | * Elements variable.
18 | *
19 | * @var array|object|string The element(s) to be modified.
20 | * @since 1.0.0
21 | */
22 | protected $elements = null;
23 |
24 | /**
25 | * Constructor function.
26 | *
27 | * @param array|object|string $elements The input elements.
28 | *
29 | * @since 1.0.0
30 | */
31 | public function __construct($elements)
32 | {
33 | $this->elements = $elements;
34 | }
35 |
36 | /**
37 | * Check if the given callback function is a callable.
38 | * If not then throw an exception.
39 | *
40 | * @param callable $callback The callback function.
41 | *
42 | * @return void
43 | * @throws InvalidArgumentException
44 | * @since 1.0.0
45 | */
46 | protected function isCallable($callback)
47 | {
48 | if (!\is_callable($callback))
49 | {
50 | $trace = debug_backtrace();
51 | $caller = $trace[1];
52 | $method = $caller['class'] . "\\" . $caller['function'] . "()";
53 |
54 | throw new \InvalidArgumentException(sprintf("The first parameter of \"%s\" must be a callable function", $method));
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/JsArray.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
7 | * @license MIT https://opensource.org/licenses/MIT
8 | */
9 |
10 | namespace Ahamed\JsPhp;
11 |
12 | use Ahamed\JsPhp\Core\Interfaces\CoreInterface;
13 | use Ahamed\JsPhp\Core\JsBase;
14 | use Ahamed\JsPhp\Traits\Arrays\BasicsTrait;
15 | use Ahamed\JsPhp\Traits\Arrays\ConditionalTrait;
16 | use Ahamed\JsPhp\Traits\Arrays\IteratorTrait;
17 | use Ahamed\JsPhp\Traits\Arrays\ModifierTrait;
18 | use Ahamed\JsPhp\Traits\Arrays\SearchingTrait;
19 | use Ahamed\JsPhp\Traits\Arrays\SortingTrait;
20 | use ArrayAccess;
21 | use Traversable;
22 |
23 | /**
24 | * JsArray class gives the array methods
25 | *
26 | */
27 | class JsArray extends JsBase implements
28 | CoreInterface,
29 | \IteratorAggregate,
30 | \ArrayAccess,
31 | \Countable,
32 | \JsonSerializable
33 | {
34 | /** Import the Array traits. */
35 | use BasicsTrait;
36 | use ModifierTrait;
37 | use SearchingTrait;
38 | use ConditionalTrait;
39 | use IteratorTrait;
40 | use SortingTrait;
41 |
42 | /**
43 | * Length of the array. This value will be calculated at every time a new
44 | * instance is created or bind
45 | *
46 | * @var integer $length The length of the array
47 | * @since 1.0.0
48 | */
49 | private $length = 0;
50 |
51 | /**
52 | * Constructor function
53 | *
54 | * @param array $elements The array of elements
55 | *
56 | * @since 1.0.0
57 | */
58 | public function __construct(array $elements = [])
59 | {
60 | /**
61 | * Force user to pass a valid php array as
62 | * the argument of the constructor function.
63 | */
64 | if (!$this->check($elements))
65 | {
66 | throw new \InvalidArgumentException(
67 | sprintf('You must have to pass a valid array or JsArray object as the argument of JsArray, "%s" given!', ucfirst(gettype($elements)))
68 | );
69 | }
70 |
71 | if ($elements instanceof JsArray)
72 | {
73 | $elements = $elements->get();
74 | }
75 |
76 | $this->length = count($elements);
77 |
78 | parent::__construct($elements);
79 | }
80 |
81 |
82 | /**
83 | * Bind the elements which are being modified
84 | *
85 | * @param array|object|string $elements The elements are being modified
86 | * @param boolean $makeMutable If true then bind will mutate the array, Otherwise it will create a new array.
87 | *
88 | * @return JsArray
89 | * @since 1.0.0
90 | */
91 | public function bind($elements, $makeMutable = true): JsArray
92 | {
93 | if (!$this->check($elements))
94 | {
95 | throw new \InvalidArgumentException(
96 | sprintf('The elements must be an array or JsArray, %s given', gettype($elements))
97 | );
98 | }
99 |
100 | if ($elements instanceof JsArray)
101 | {
102 | $elements = $elements->get();
103 | }
104 |
105 | /**
106 | * If makeMutable is enabled then it update the data of the
107 | * current object and mutate the previous values.
108 | */
109 | if ($makeMutable)
110 | {
111 | // Update the length of the array
112 | $this->length = count($elements);
113 | $this->elements = $elements;
114 | $instance = $this;
115 | }
116 | else
117 | {
118 | $instance = new static($elements);
119 | }
120 |
121 | return $instance;
122 | }
123 |
124 | /**
125 | * Check if the bind value is valid or not.
126 | * If the bind value i.e the elements are invalid then throw errors.
127 | *
128 | * @param mixed $elements The elements to check.
129 | *
130 | * @return bool
131 | * @since 1.0.0
132 | */
133 | public function check($elements): bool
134 | {
135 | return \is_array($elements)
136 | || $elements instanceof JsArray;
137 | }
138 |
139 | /**
140 | * Get elements. This is not directly called with the class
141 | * but can call with any chaining method.
142 | *
143 | * @return array The native elements for native operations.
144 | * @since 1.0.0
145 | */
146 | public function get()
147 | {
148 | return $this->elements;
149 | }
150 |
151 | /**
152 | * Reset the elements to null
153 | *
154 | * @return void
155 | * @since 1.0.0
156 | */
157 | public function reset()
158 | {
159 | $this->elements = [];
160 | }
161 |
162 | /**
163 | * Get the length of the array.
164 | * This is an alias of the `length` property
165 | *
166 | * @return integer The array length
167 | * @since 1.0.0
168 | */
169 | public function length(): int
170 | {
171 | return count($this->get());
172 | }
173 |
174 | /**
175 | * Get the keys of an array
176 | *
177 | * @return array The keys array
178 | * @since 1.0.0
179 | */
180 | public function keys()
181 | {
182 | $elements = $this->get();
183 |
184 | return $this->bind(array_keys($elements), false);
185 | }
186 |
187 | /**
188 | * Get the values of an array
189 | *
190 | * @return array The values array
191 | * @since 1.0.0
192 | */
193 | public function values()
194 | {
195 | $elements = $this->get();
196 |
197 | return array_values($elements);
198 | }
199 |
200 | /**
201 | * Check if the array is an associative array or sequential array.
202 | *
203 | * @param array $array The array to check
204 | *
205 | * @return boolean True if is an associative array, False otherwise.
206 | * @since 1.0.0
207 | */
208 | public static function isAssociativeArray($array): bool
209 | {
210 | /**
211 | * If the array is an instance of this class then retrieve the original array
212 | */
213 | if ($array instanceof self)
214 | {
215 | $array = $array->get();
216 | }
217 |
218 | /**
219 | * If an empty array then it's not an associative array
220 | */
221 | if ($array === [])
222 | {
223 | return false;
224 | }
225 |
226 | /**
227 | * If the the array has all integer numbered index then it's
228 | * not an associative array
229 | */
230 | $integerKeys = 0;
231 |
232 | foreach (array_keys($array) as $key)
233 | {
234 | if ($key === (int) $key)
235 | {
236 | ++$integerKeys;
237 | }
238 | }
239 |
240 | if ($integerKeys === count(array_keys($array)))
241 | {
242 | return false;
243 | }
244 |
245 | return true;
246 | }
247 |
248 | /**
249 | * Check if the item is an array or not
250 | *
251 | * @param array|JsArray $array PHP array or instance of JsArray
252 | *
253 | * @return boolean True if it's an array, false otherwise.
254 | * @since 1.0.0
255 | */
256 | public static function isArray($array): bool
257 | {
258 | /**
259 | * If the array is an instance of this class then retrieve the original array
260 | */
261 | if ($array instanceof self)
262 | {
263 | $array = $array->get();
264 | }
265 |
266 | return is_array($array);
267 | }
268 |
269 | /**
270 | * @see \IteratorAggregate::getIterator()
271 | *
272 | * Make the JsArray instance iterable.
273 | * Now one can iterate the JsArray instance using foreach
274 | *
275 | * @return \Iterator
276 | * @since 1.0.0
277 | */
278 | public function getIterator(): Traversable
279 | {
280 | $elements = $this->get();
281 |
282 | return new \ArrayIterator($elements);
283 | }
284 |
285 | /**
286 | * @see \ArrayAccess::offsetExists($offset)
287 | *
288 | * This will check if a specific key is exists in the elements array.
289 | *
290 | * @param string|int $key The key to check.
291 | *
292 | * @return boolean True if key exists on the elements array, false otherwise.
293 | * @since 1.0.0
294 | */
295 | public function offsetExists($key): bool
296 | {
297 | $elements = $this->get();
298 |
299 | return isset($elements[$key]);
300 | }
301 |
302 | /**
303 | * @see \ArrayAccess::offsetSet($offset, $value)
304 | *
305 | * Set an value by using a specific offset.
306 | *
307 | * @param string|int $key The offset key
308 | * @param mixed $value The value to set at the offset position
309 | *
310 | * @return void
311 | * @since 1.0.0
312 | */
313 | public function offsetSet($key, $value): void
314 | {
315 | $elements = $this->get();
316 |
317 | /**
318 | * If key no key provided then push the value into the array,
319 | * otherwise it sets a value at the specific key.
320 | * If key already exists then it updates the value, otherwise insert.
321 | */
322 | if (is_null($key))
323 | {
324 | $elements[] = $value;
325 | }
326 | else
327 | {
328 | $elements[$key] = $value;
329 | }
330 |
331 | // Bind and mutate the elements array by new array after setting the offset
332 | $this->bind($elements);
333 | }
334 |
335 | /**
336 | * @see \ArrayAccess::offsetGet($offset)
337 | *
338 | * @param string|int $key The key of the array value to get.
339 | *
340 | * @return mixed If key found then return the value, otherwise returns null
341 | * @since 1.0.0
342 | */
343 | #[\ReturnTypeWillChange]
344 | public function offsetGet($key)
345 | {
346 | $elements = $this->get();
347 |
348 | if (isset($elements[$key]))
349 | {
350 | return $elements[$key];
351 | }
352 |
353 | \trigger_error(\sprintf('Undefined index: %s', $key), \E_USER_NOTICE);
354 |
355 | return null;
356 | }
357 |
358 | /**
359 | * @see \ArrayAccess::offsetUnset($offset)
360 | *
361 | * This is to unset a specific offset or key of the array.
362 | *
363 | * @param string|int $key The key to unset the element
364 | *
365 | * @return void
366 | * @since 1.0.0
367 | */
368 | public function offsetUnset($key): void
369 | {
370 | $elements = $this->get();
371 |
372 | if (isset($elements[$key]))
373 | {
374 | unset($elements[$key]);
375 |
376 | if (!self::isAssociativeArray($elements))
377 | {
378 | $elements = array_values($elements);
379 | }
380 |
381 | $this->bind($elements);
382 | }
383 | }
384 |
385 | /**
386 | * @see Countable::count()
387 | *
388 | * Count the total number of elements of the array.
389 | *
390 | * @return integer The length of the array
391 | * @since 1.0.0
392 | */
393 | public function count(): int
394 | {
395 | return $this->length;
396 | }
397 |
398 | /**
399 | * @see \Serializable::serialize()
400 | *
401 | * Serialize the object.
402 | *
403 | * @return array The serialized string.
404 | * @since 1.0.0
405 | */
406 | public function __serialize(): array
407 | {
408 | $elements = $this->get();
409 |
410 | return $elements;
411 | }
412 |
413 | /**
414 | * @see \Serializable::unserialize(string $serialized)
415 | *
416 | * Unserialize a serialized string and retrieve the array.
417 | *
418 | * @param string $serialized The serialized string
419 | *
420 | * @return void
421 | * @since 1.0.0
422 | */
423 | public function __unserialize($serialized): void
424 | {
425 | if (!empty($serialized))
426 | {
427 | $unserialize = null;
428 |
429 | try
430 | {
431 | $unserialize = unserialize($serialized);
432 | }
433 | catch (\Exception $e)
434 | {
435 | throw new \InvalidArgumentException(sprintf($e->getMessage()));
436 | }
437 |
438 | $this->bind($unserialize);
439 | }
440 | else
441 | {
442 | throw new \InvalidArgumentException(sprintf('You have to pass serialized string to unserialize.'));
443 | }
444 | }
445 |
446 | /**
447 | * @see \JsonSerializable::jsonSerialize()
448 | *
449 | * This method is called when the json_encode/json_decode is called.
450 | *
451 | * @return array The elements array
452 | * @since 1.0.0
453 | */
454 | public function jsonSerialize(): mixed
455 | {
456 | return $this->get();
457 | }
458 |
459 | /**
460 | * Destructing method.
461 | * This is used here to collect all the remaining garbage cycles.
462 | *
463 | * @since 1.0.0
464 | */
465 | public function __destruct()
466 | {
467 | // Forces collection of any existing garbage cycles
468 | gc_collect_cycles();
469 | }
470 |
471 | /**
472 | * Getter function for JsArray class
473 | *
474 | * @param string $name The property name.
475 | *
476 | * @return mixed The property value
477 | * @since 1.0.0
478 | */
479 | public function __get($name)
480 | {
481 | switch ($name)
482 | {
483 | case 'length':
484 | return $this->length;
485 | break;
486 | default:
487 | throw new \Exception('You are not allowed to get the property');
488 | }
489 | }
490 |
491 | /**
492 | * Setter function for the class JsArray.
493 | *
494 | * @param string $name The property name
495 | * @param mixed $value The new value to assign into the property
496 | *
497 | * @return void
498 | * @since 1.0.0
499 | */
500 | public function __set($name, $value)
501 | {
502 | switch ($name)
503 | {
504 | case 'length':
505 | if (!\is_numeric($value))
506 | {
507 | throw new \InvalidArgumentException(sprintf('You cannot assign any non numeric value as length!'));
508 | }
509 |
510 | $len = (int) $value;
511 |
512 | if ($len < 0)
513 | {
514 | throw new \InvalidArgumentException(sprintf('Length cannot take a negative value.'));
515 | }
516 | elseif ($len === 0)
517 | {
518 | $this->bind([]);
519 | }
520 | else
521 | {
522 | $len = min($len, $this->length);
523 | $newArray = $this->slice(0, $len);
524 | $this->bind($newArray->get());
525 | }
526 |
527 | break;
528 |
529 | default:
530 | throw new \InvalidArgumentException(sprintf('This property is not accepted to set'));
531 | }
532 | }
533 |
534 | /**
535 | * Magic method __toString
536 | *
537 | * @return string Object echo message
538 | * @since 1.0.0
539 | */
540 | public function __toString()
541 | {
542 | return sprintf('Call `get()` if a method is returning a `JsArray` instance for getting the native array elements.');
543 | }
544 |
545 | /**
546 | * Magic method __debugInfo
547 | *
548 | * @return array Object echo message
549 | * @since 1.0.0
550 | */
551 | public function __debugInfo()
552 | {
553 | return $this->get();
554 | }
555 | }
556 |
--------------------------------------------------------------------------------
/src/JsObject.php:
--------------------------------------------------------------------------------
1 |
6 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
7 | * @license MIT https://opensource.org/licenses/MIT
8 | */
9 |
10 | namespace Ahamed\JsPhp;
11 |
12 | use Ahamed\JsPhp\Core\Interfaces\CoreInterface;
13 | use Ahamed\JsPhp\Core\JsBase;
14 | use Ahamed\JsPhp\JsArray;
15 | use Ahamed\JsPhp\Traits\Objects\StaticTrait;
16 |
17 | /**
18 | * JsObject the class implementation for various object methods.
19 | *
20 | */
21 | class JsObject extends JsBase implements CoreInterface, \ArrayAccess, \IteratorAggregate
22 | {
23 | use StaticTrait;
24 |
25 | /**
26 | * Constructor function for the JsObject class
27 | *
28 | * @param array|object $elements The elements to create the JsObject
29 | *
30 | * @return void
31 | * @since 1.0.0
32 | */
33 | public function __construct($elements = null)
34 | {
35 | /**
36 | * If no argument provided or NULL given then make the elements as
37 | * an empty \stdClass object.
38 | */
39 | if (\is_null($elements))
40 | {
41 | $elements = new \stdClass;
42 | }
43 |
44 | /**
45 | * Force user to pass a valid array|object as the argument
46 | * of the constructor function of the JsObject class.
47 | */
48 | if (!$this->check($elements))
49 | {
50 | throw new \InvalidArgumentException(
51 | sprintf('You must have to provide Object or Array as the argument of the JsObject class, "%s" given!', ucfirst(gettype($elements)))
52 | );
53 | }
54 |
55 | /**
56 | * If the given element is an object and it's an object of
57 | * a custom class then parse the object and get all the
58 | * public properties and makes a \stdClass object.
59 | */
60 | if (\is_object($elements))
61 | {
62 | if ($elements instanceof JsObject)
63 | {
64 | $elements = $elements->get();
65 | }
66 | elseif (!($elements instanceof \stdClass))
67 | {
68 | $elements = $this->parseObject($elements);
69 | }
70 | }
71 | elseif (\is_array($elements))
72 | {
73 | $elements = (object) $elements;
74 | }
75 |
76 | parent::__construct($elements);
77 | }
78 |
79 | /**
80 | * Bind the elements which are being modified
81 | *
82 | * @param array|object|JsObject $elements The elements are being modified
83 | * @param boolean $makeMutable If true then bind will mutate the array, Otherwise it will create a new array.
84 | *
85 | * @return JsObject
86 | * @since 1.0.0
87 | */
88 | public function bind($elements, $makeMutable = true): JsObject
89 | {
90 | if (!$this->check($elements))
91 | {
92 | throw new \Exception(
93 | sprintf('The elements must be an stdClass or JsObject, %s given', gettype($elements))
94 | );
95 | }
96 |
97 | if (!$makeMutable)
98 | {
99 | return (new $this($elements));
100 | }
101 |
102 | /**
103 | * If the given element is an object and it's an object of
104 | * a custom class then parse the object and get all the
105 | * public properties and makes a \stdClass object.
106 | */
107 | if (\is_object($elements))
108 | {
109 | if ($elements instanceof JsObject)
110 | {
111 | $elements = $elements->get();
112 | }
113 | elseif (!($elements instanceof \stdClass))
114 | {
115 | $elements = $this->parseObject($elements);
116 | }
117 | }
118 | elseif (\is_array($elements))
119 | {
120 | $elements = (object) $elements;
121 | }
122 |
123 | if ($makeMutable)
124 | {
125 | $this->elements = $elements;
126 | $instance = $this;
127 | }
128 |
129 | return $instance;
130 | }
131 |
132 | /**
133 | * Check if the bind value is valid or not.
134 | * If the bind value i.e the elements are invalid then throw errors.
135 | *
136 | * @param mixed $elements The elements to check.
137 | *
138 | * @return bool
139 | * @since 1.0.0
140 | */
141 | public function check($elements): bool
142 | {
143 | return \is_object($elements)
144 | || \is_array($elements)
145 | || $elements instanceof JsObject;
146 | }
147 |
148 | /**
149 | * Get elements. This is not directly called with the class
150 | * but can call with any chaining method.
151 | *
152 | * @return \stdClass The native elements for native operations.
153 | * @since 1.0.0
154 | */
155 | public function get(): \stdClass
156 | {
157 | return $this->elements;
158 | }
159 |
160 | /**
161 | * Reset the elements to null
162 | *
163 | * @return void
164 | * @since 1.0.0
165 | */
166 | public function reset()
167 | {
168 | $this->elements = new \stdClass;
169 | }
170 |
171 | /**
172 | * Parse the custom class object and take only the public properties
173 | * and returns a stdClass.
174 | *
175 | * @param object $elements custom class object
176 | *
177 | * @return \stdClass A \stdClass with public properties
178 | * @since 1.0.0
179 | */
180 | private function parseObject(object $elements): \stdClass
181 | {
182 | /**
183 | * Create a \ReflectionObject instance with the $elements object
184 | * and gets all the public properties.
185 | */
186 | $reflection = new \ReflectionObject($elements);
187 | $properties = $reflection->getProperties(\ReflectionProperty::IS_PUBLIC);
188 |
189 | $parsedObject = new \stdClass;
190 |
191 | /**
192 | * If public properties exists then the reflection returns an array
193 | * of properties. Make the array as a JsArray and get the property name
194 | * and set the properties to the returning stdClass object.
195 | *
196 | * If no public property exists then it returns an empty stdClass.
197 | */
198 | if (!empty($properties))
199 | {
200 | $properties = new JsArray($properties);
201 | $publicProperties = $properties->map(
202 | function ($prop)
203 | {
204 | return $prop->name;
205 | }
206 | );
207 |
208 | $publicProperties->forEach(
209 | function ($prop) use ($elements, $parsedObject)
210 | {
211 | $parsedObject->$prop = $elements->$prop;
212 | }
213 | );
214 | }
215 |
216 | return $parsedObject;
217 | }
218 |
219 | /**
220 | * @see \IteratorAggregate::getIterator()
221 | *
222 | * Make the JsArray instance iterable.
223 | * Now one can iterate the JsArray instance using foreach
224 | *
225 | * @return \ArrayIterator
226 | * @since 1.0.0
227 | */
228 | public function getIterator()
229 | {
230 | $elements = $this->get();
231 |
232 | return new \ArrayIterator($elements);
233 | }
234 |
235 | /**
236 | * @see \ArrayAccess::offsetExists($offset)
237 | *
238 | * This will check if a specific key is exists in the elements array.
239 | *
240 | * @param string|int $key The key to check.
241 | *
242 | * @return boolean True if key exists on the elements array, false otherwise.
243 | * @since 1.0.0
244 | */
245 | public function offsetExists($key): bool
246 | {
247 | $elements = (array) $this->get();
248 |
249 | return array_key_exists($key, $elements);
250 | }
251 |
252 | /**
253 | * @see \ArrayAccess::offsetSet($offset, $value)
254 | *
255 | * Set an value by using a specific offset.
256 | *
257 | * @param string|int $key The offset key
258 | * @param mixed $value The value to set at the offset position
259 | *
260 | * @return void
261 | * @since 1.0.0
262 | */
263 | public function offsetSet($key, $value): void
264 | {
265 | $elements = $this->get();
266 |
267 | /**
268 | * One must have to pass a key for setting a value
269 | */
270 | if (\is_null($key))
271 | {
272 | throw new \InvalidArgumentException(
273 | \sprintf('You must have to provide the key/property of the object to set the value.')
274 | );
275 | }
276 |
277 | $elements->$key = $value;
278 |
279 | // Bind and mutate the elements array by new array after setting the offset
280 | $this->bind($elements);
281 | }
282 |
283 | /**
284 | * @see \ArrayAccess::offsetGet($offset)
285 | *
286 | * @param string|int $key The key of the array value to get.
287 | *
288 | * @return mixed If key found then return the value, otherwise returns null
289 | * @since 1.0.0
290 | */
291 | public function offsetGet($key)
292 | {
293 | $elements = $this->get();
294 |
295 | if (isset($elements->$key))
296 | {
297 | return $elements->$key;
298 | }
299 |
300 | \trigger_error(\sprintf('Undefined index: %s', $key), \E_USER_NOTICE);
301 |
302 | return null;
303 | }
304 |
305 | /**
306 | * @see \ArrayAccess::offsetUnset($offset)
307 | *
308 | * This is to unset a specific offset or key of the array.
309 | *
310 | * @param string|int $key The key to unset the element
311 | *
312 | * @return void
313 | * @since 1.0.0
314 | */
315 | public function offsetUnset($key): void
316 | {
317 | $elements = $this->get();
318 |
319 | if (isset($elements->$key))
320 | {
321 | unset($elements->$key);
322 | $this->bind($elements);
323 | }
324 | }
325 |
326 | /**
327 | * The magic getter method for getting the element by property.
328 | *
329 | * @param string $name The property name
330 | *
331 | * @return mixed The value of the the elements object for the property.
332 | * @throws \NotFoundException
333 | */
334 | public function __get($name)
335 | {
336 | $elements = $this->get();
337 |
338 | if (!isset($elements->$name))
339 | {
340 | throw new \Exception(
341 | sprintf('Unknown property "%s".', $name)
342 | );
343 | }
344 |
345 | return $elements->$name;
346 | }
347 |
348 | /**
349 | * Magic setter method for setting/changing object property.
350 | *
351 | * @param string $name The property name.
352 | * @param mixed $value The value to change/set.
353 | *
354 | * @return void
355 | * @since 1.0.0
356 | */
357 | public function __set($name, $value): void
358 | {
359 | $elements = $this->get();
360 | $elements->$name = $value;
361 |
362 | $this->bind($elements);
363 | }
364 |
365 | /**
366 | * Magic method __isset for checking isset()
367 | *
368 | * @param string $name The property name.
369 | *
370 | * @return boolean true if property exists, false otherwise.
371 | * @since 1.0.0
372 | */
373 | public function __isset($name)
374 | {
375 | $elements = $this->get();
376 |
377 | return isset($elements->$name);
378 | }
379 |
380 | /**
381 | * Magic method __unset for applying the unset() method on object property.
382 | *
383 | * @param string $name The property name.
384 | *
385 | * @return void
386 | * @since 1.0.0
387 | */
388 | public function __unset($name)
389 | {
390 | $elements = $this->get();
391 |
392 | if (isset($elements->$name))
393 | {
394 | unset($elements->$name);
395 | }
396 | }
397 |
398 | /**
399 | * Magic method __toString
400 | *
401 | * @return string Object echo message
402 | * @since 1.0.0
403 | */
404 | public function __toString(): string
405 | {
406 | return sprintf('Call `get()` if a method is returning a `JsObject` instance for getting the native stdClass object.');
407 | }
408 |
409 | /**
410 | * Method to run __toString() method.
411 | *
412 | * @return string The return string of __toString()
413 | * @since 1.0.0
414 | */
415 | public function toString(): string
416 | {
417 | return (string) $this;
418 | }
419 |
420 | /**
421 | * Destructing method.
422 | * This is used here to collect all the remaining garbage cycles.
423 | *
424 | * @since 1.0.0
425 | */
426 | public function __destruct()
427 | {
428 | // Forces collection of any existing garbage cycles
429 | gc_collect_cycles();
430 | }
431 |
432 | /**
433 | * Magic method __debugInfo
434 | *
435 | * @return array Object echo message
436 | * @since 1.0.0
437 | */
438 | public function __debugInfo(): array
439 | {
440 | return [
441 | 'data' => $this->get()
442 | ];
443 | }
444 | }
445 |
--------------------------------------------------------------------------------
/src/Traits/Arrays/BasicsTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Arrays;
9 |
10 | use Ahamed\JsPhp\JsArray;
11 |
12 | /**
13 | * Trait function for array conditionals
14 | *
15 | * @since 1.0.0
16 | */
17 | trait BasicsTrait
18 | {
19 | /**
20 | * Array::from method for generating an array (or JsArray instance) from an iterable
21 | * with a user defined callable modifier.
22 | *
23 | * @param array|string $iterable The iterable.
24 | * @param callable $callable The user defined callable.
25 | *
26 | * @return JsArray The created JsArray instance.
27 | * @since 1.0.0
28 | */
29 | public static function from($iterable, callable $callable = null) : JsArray
30 | {
31 | $array = new JsArray();
32 | $localArray = [];
33 |
34 | /** If JsArray provided as iterable then get the array elements. */
35 | if ($iterable instanceof JsArray)
36 | {
37 | $iterable = $iterable->get();
38 | }
39 |
40 | /** If string given as iterable then split the string and make an array of characters. */
41 | if (\is_string($iterable))
42 | {
43 | $iterable = \str_split($iterable, 1);
44 | }
45 |
46 | /** If iterable is not an array then return an JsArray instance with empty array. */
47 | if (!\is_array($iterable))
48 | {
49 | return $array->bind([], false);
50 | }
51 |
52 | $isAssoc = JsArray::isAssociativeArray($iterable);
53 |
54 | /**
55 | * Check if the iterable is an associative array.
56 | * If so then check if this array contains the `length` property.
57 | * If length not found then return a JsArray instance with empty array.
58 | * Otherwise create an array from 0 to $length - 1 and if $callable provided
59 | * then create the array by using the $callable function's return value
60 | * otherwise fill by null and return the newly created array.
61 | */
62 | if ($isAssoc && !isset($iterable['length']))
63 | {
64 | return $array->bind([], false);
65 | }
66 | elseif ($isAssoc && isset($iterable['length']))
67 | {
68 | $length = (int) $iterable['length'];
69 |
70 | for ($i = 0; $i < $length; $i++)
71 | {
72 | if (!$callable)
73 | {
74 | $localArray[] = null;
75 | }
76 | else
77 | {
78 | $localArray[] = \call_user_func_array($callable, [null, $i]);
79 | }
80 | }
81 |
82 | return $array->bind($localArray, false);
83 | }
84 |
85 | /** If $iterable is a plain array. */
86 | foreach ($iterable as $index => $item)
87 | {
88 | $localArray[$index] = $callable
89 | ? \call_user_func_array($callable, [$item, $index])
90 | : $item;
91 | }
92 |
93 | return $array->bind($localArray, false);
94 | }
95 |
96 | /**
97 | * Array at method. This method will return the value of the array in the
98 | * provided at index. The benefit over the square bracket index is that it
99 | * can return you a value of negative index.
100 | * This method is only works on sequential array. For associative array it
101 | * returns null.
102 | *
103 | * @param integer $index The at index.
104 | *
105 | * @return mixed The corresponding value situated at the index.
106 | * @since 1.0.0
107 | */
108 | public function at(int $index)
109 | {
110 | $elements = $this->get();
111 |
112 | /** For associative array it always returns null. */
113 | if (self::isAssociativeArray($elements))
114 | {
115 | return null;
116 | }
117 |
118 | $length = $this->length;
119 |
120 | if ($index < 0)
121 | {
122 | $index = $length + $index;
123 | }
124 |
125 | if ($index >= $length || $index < 0)
126 | {
127 | throw new \OutOfRangeException(\sprintf('The provided index %d is out of range.', $index));
128 | }
129 |
130 | return $elements[$index];
131 | }
132 |
133 | /**
134 | * Array push method
135 | *
136 | * @param mixed ...$items Variable number of elements to push into the array
137 | *
138 | * @return integer The length of the array after pushing the elements
139 | * @since 1.0.0
140 | */
141 | public function push(...$items) : int
142 | {
143 | $elements = $this->get();
144 |
145 | if (empty($items))
146 | {
147 | $this->bind($elements);
148 |
149 | return $this->length;
150 | }
151 |
152 | /**
153 | * Push the element(s) into the elements and finally mutate the array
154 | * with updated array.
155 | */
156 | array_push($elements, ...$items);
157 | $this->bind($elements);
158 |
159 | // Returns the length of the newly created array.
160 | return $this->length;
161 | }
162 |
163 | /**
164 | * Remove an element from end the array and returns the removed element
165 | * and update the existing array
166 | *
167 | * @return mixed Removed element from the end of the array.
168 | * @since 1.0.0
169 | */
170 | public function pop()
171 | {
172 | $elements = $this->get();
173 | $length = $this->length;
174 |
175 | /**
176 | * We cannot perform a pop operation on a empty array
177 | */
178 | if ($length === 0)
179 | {
180 | return null;
181 | }
182 |
183 | // Popes the last element of the array.
184 | $removedValue = array_pop($elements);
185 |
186 | // Mutates the existing array with the array after popped.
187 | $this->bind($elements);
188 |
189 | // Returns the popped value.
190 | return $removedValue;
191 | }
192 |
193 | /**
194 | * Remove an element from the start of the array and returns the removed element
195 | * and update the existing array
196 | *
197 | * @return mixed Removed element from the start of the array.
198 | * @since 1.0.0
199 | */
200 | public function shift()
201 | {
202 | $elements = $this->get();
203 | $length = $this->length;
204 | $isAssoc = self::isAssociativeArray($elements);
205 |
206 | /**
207 | * We cannot perform a pop operation on a empty array
208 | */
209 | if ($length === 0)
210 | {
211 | return null;
212 | }
213 |
214 | /**
215 | * Get the keys of the array and take the value of the first key of the array
216 | * and unset the value of the first key of the array.
217 | * And finally update the array by updated array and return the removed element.
218 | */
219 | if ($isAssoc)
220 | {
221 | $keys = $this->keys()->get();
222 | $removedValue = $elements[$keys[0]];
223 | unset($elements[$keys[0]]);
224 | }
225 | else
226 | {
227 | $removedValue = $elements[0];
228 | unset($elements[0]);
229 | }
230 |
231 | // Rebase the array keys.
232 | $elements = array_merge($elements);
233 |
234 | // Mutates the existing array with the array after popped.
235 | $this->bind($elements);
236 |
237 | // Returns the popped value.
238 | return $removedValue;
239 | }
240 |
241 | /**
242 | * Push 1-N numbers of items in front of the array.
243 | *
244 | * @param mixed ...$items The items to push at the front.
245 | *
246 | * @return integer The length of updated array.
247 | * @since 1.0.0
248 | */
249 | public function unshift(...$items) : int
250 | {
251 | if (!isset($items) || count($items) <= 0)
252 | {
253 | throw new \UnexpectedValueException(
254 | \sprintf('You must have to pass at least one value to push')
255 | );
256 | }
257 |
258 | $elements = $this->get();
259 | $length = $this->length;
260 | $itemsLength = count($items);
261 | $keys = $this->keys()->get();
262 |
263 | $unshiftArray = [];
264 |
265 | /**
266 | * Push the items in front of the array first.
267 | * This will keep the keys if it's an associative array
268 | * otherwise its rebase the indices.
269 | */
270 | for ($k = 0; $k < $itemsLength; ++$k)
271 | {
272 | $unshiftArray[] = $items[$k];
273 | }
274 |
275 | /**
276 | * After pushing the items into the unshiftArray adding the
277 | * existing array elements after them.
278 | *
279 | * All numerical array keys will be modified to start counting from zero
280 | * while literal keys won't be changed.
281 | *
282 | * That means if key of the original array is numerical then the value
283 | * of the key is updated after counting this from the beginning.
284 | * For an example, if we unshift 3 values at the front of the array and
285 | * the first encounter of the numerical key is 1 then it's updated to 3.
286 | *
287 | * The literal keys won't be changes.
288 | */
289 | $numericKey = $itemsLength;
290 |
291 | for ($i = 0; $i < $length; ++$i)
292 | {
293 | if (\is_numeric($keys[$i]))
294 | {
295 | $key = $numericKey;
296 | ++$numericKey;
297 | }
298 | else
299 | {
300 | $key = $keys[$i];
301 | }
302 |
303 | $unshiftArray[$key] = $elements[$keys[$i]];
304 | }
305 |
306 | $instance = $this->bind($unshiftArray);
307 |
308 | return $instance->length;
309 | }
310 |
311 | /**
312 | * Join an array elements with help of a separator value and returns a string
313 | *
314 | * @param string $separator The separator value
315 | *
316 | * @return string Joined string.
317 | * @since 1.0.0
318 | */
319 | public function join(string $separator = ',') : string
320 | {
321 | $elements = $this->get();
322 | $length = $this->length;
323 |
324 | /**
325 | * Get the values of the array. We are not handling the keys of the array.
326 | * For joining purpose we just need the values.
327 | */
328 | $elements = array_values($elements);
329 |
330 | $joinedString = '';
331 |
332 | // Force make the separator string
333 | $separator = (string) $separator;
334 |
335 | /**
336 | * If no elements in the array or the array is null then returns empty string.
337 | */
338 | if (empty($elements) || $elements === null)
339 | {
340 | return '';
341 | }
342 |
343 | /**
344 | * If there is only one element and the element is a string or boolean
345 | * number then return the element making string.
346 | */
347 | if ($length === 1)
348 | {
349 | if (\is_array($elements[0]))
350 | {
351 | $item = $this->bind($elements[0], false);
352 | $string = $item->join();
353 | unset($item);
354 |
355 | return $string;
356 | }
357 | else
358 | {
359 | return (string) $elements[0];
360 | }
361 | }
362 |
363 | /**
364 | * If there are more than one elements. Its detached the first and
365 | * last elements from the array and join them separately. If any of
366 | * the elements of the array is an array then join this array separately
367 | * with default (,) separator.
368 | */
369 | if (is_array($elements[0]))
370 | {
371 | $item = $this->bind($elements[0], false);
372 | $joinedString .= $item->join();
373 | unset($item);
374 | }
375 | else
376 | {
377 | $joinedString .= $elements[0];
378 | }
379 |
380 | /**
381 | * Join all the elements except the first and last one.
382 | */
383 | for ($i = 1; $i < $length - 1; ++$i)
384 | {
385 | if (\is_array($elements[$i]))
386 | {
387 | $item = $this->bind($elements[$i], false);
388 | $joinedString .= $separator . $item->join();
389 | unset($item);
390 | }
391 | else
392 | {
393 | $joinedString .= $separator . $elements[$i];
394 | }
395 | }
396 |
397 | /**
398 | * Join the last element.
399 | */
400 | if (is_array($elements[$length - 1]))
401 | {
402 | $item = $this->bind($elements[$length - 1], false);
403 | $joinedString .= $separator . $item->join();
404 | unset($item);
405 | }
406 | else
407 | {
408 | $joinedString .= $separator . $elements[$length - 1];
409 | }
410 |
411 | return $joinedString;
412 | }
413 |
414 | /**
415 | * Slice the array and returns a new array within the given range parameters.
416 | *
417 | * @param int $start The start value
418 | * @param int $end The end value.
419 | *
420 | * @return JsArray The sliced array
421 | * @since 1.0.0
422 | */
423 | public function slice(int $start = 0, int $end = null) : JsArray
424 | {
425 | $elements = $this->get();
426 | $length = $this->length;
427 |
428 | $start = (int) $start;
429 | $end = \is_null($end) ? $length : $end;
430 |
431 | /**
432 | * Get the calculated start index from where the sliced array
433 | * takes the elements.
434 | */
435 | $startIndex = $start < 0 ?
436 | max($length + $start, 0) :
437 | min($start, $length);
438 |
439 | /**
440 | * Get the calculated end index or final index to where the sliced
441 | * array stops taking elements.
442 | */
443 | $finalIndex = $end < 0 ?
444 | max($length + $end, 0) :
445 | min($end, $length);
446 |
447 | /**
448 | * If the start index be greater than the final index then nothing
449 | * to slice. Returns an empty array.
450 | */
451 | if ($startIndex > $finalIndex)
452 | {
453 | return $this->bind([], false);
454 | }
455 |
456 | $preserveKeys = false;
457 |
458 | /**
459 | * If the array is an associative array then keeps the keys of the array.
460 | */
461 | if (self::isAssociativeArray($elements))
462 | {
463 | $preserveKeys = true;
464 | }
465 |
466 | $keys = $this->keys()->get();
467 | $slicedArray = [];
468 |
469 | /**
470 | * Generate the sliced array and return a new array without
471 | * modifying the original one.
472 | */
473 | for ($i = $startIndex; $i < $finalIndex; ++$i)
474 | {
475 | if ($preserveKeys)
476 | {
477 | $slicedArray[$keys[$i]] = $elements[$keys[$i]];
478 | }
479 | else
480 | {
481 | $slicedArray[] = $elements[$keys[$i]];
482 | }
483 | }
484 |
485 | return $this->bind($slicedArray, false);
486 | }
487 |
488 | /**
489 | * Splice an array with the help of start index and delete count,
490 | * and if items are given for insertion then push them after deletion.
491 | *
492 | * @param int $start The star index
493 | * @param int $deleteCount How many items to delete after the start index.
494 | * @param mixed ...$items A variable number of items parameter to insert after deletion after the start index.
495 | *
496 | * @return JsArray Deleted items array but the self instance for chaining.
497 | * @since 1.0.0
498 | */
499 | public function splice(int $start = null, int $deleteCount = null, ...$items) : JsArray
500 | {
501 | $elements = $this->get();
502 | $length = $this->length;
503 | $keys = $this->keys()->get();
504 |
505 | /**
506 | * If no any parameter are provided then return empty array
507 | */
508 | if (\is_null($start) && \is_null($deleteCount) && empty($items))
509 | {
510 | return $this->bind([], false);
511 | }
512 |
513 | $start = (int) $start;
514 |
515 | /**
516 | * If delete count is not provided or null provided and if there is items
517 | * to push then deleteCount is zero (0) otherwise deleteCount is the
518 | * minimum of $length - $start and $length.
519 | *
520 | * If deleteCount is provided then takes the integer value of the deleteCount
521 | */
522 | $deleteCount = \is_null($deleteCount) ?
523 | (empty($items) ? min($length - $start, $length) : 0) :
524 | (int) $deleteCount;
525 |
526 | $assoc = self::isAssociativeArray($elements);
527 |
528 | /**
529 | * The delete start index. If the start index is a negative number
530 | * then add the start with the length of the array and take the maximum
531 | * between 0 and start + length. This is because if the summation of start + length
532 | * goes down to 0 then we take the 0 as the starting value as the index could not be a negative number.
533 | *
534 | * If the start index is a positive then take the minimum of start and length.
535 | * This is because if the provided start value is greater then the length itself
536 | * then we take the length as start.
537 | */
538 | $deleteStart = $start < 0 ?
539 | max($start + $length, 0) :
540 | min($start, $length);
541 |
542 | /**
543 | * Delete Count is the how many items to delete after the start index.
544 | * We take the minimum between the length and the summation of deleteStart and deleteCount.
545 | */
546 | $deleteEnds = min($deleteStart + $deleteCount, $length);
547 |
548 | $deletedArray = [];
549 | $finalArray = [];
550 |
551 | /**
552 | * Generate the deleted items array and update the existing array
553 | * after deletion.
554 | */
555 | for ($i = $deleteStart; $i < $deleteEnds; ++$i)
556 | {
557 | if ($assoc)
558 | {
559 | $deletedArray[$keys[$i]] = $elements[$keys[$i]];
560 | unset($elements[$keys[$i]]);
561 | }
562 | else
563 | {
564 | $deletedArray[] = $elements[$i];
565 | unset($elements[$i]);
566 | }
567 | }
568 |
569 | /**
570 | * Rebase the updated array indices.
571 | */
572 | $elements = array_merge($elements);
573 |
574 | /**
575 | * Re-calculate the properties of the array after deletion.
576 | */
577 | $newLength = count($elements);
578 | $newKeys = array_keys($elements);
579 | $itemsLength = count($items);
580 |
581 | /**
582 | * If no item to add into the split position then mutate the original array
583 | * with the after deletion array and return the new deleted Array.
584 | */
585 | if ($itemsLength <= 0)
586 | {
587 | $this->bind($elements);
588 |
589 | return $this->bind($deletedArray, false);
590 | }
591 |
592 | /**
593 | * If Items are given for attach then we split the array into two parts
594 | * called leftArray and rightArray at deleteStart position.
595 | *
596 | * If splice indicates to delete from index zero and insert value at there
597 | * then in the leftArray nothing would be there and the rightArray contains
598 | * the whole array.
599 | *
600 | * If the splice indicates to delete and insert from and into the length of
601 | * the array then the rightArray contains nothing and the rest of the array
602 | * belongs to the left array.
603 | *
604 | * Otherwise we generate two parts of the array by using the rule
605 | * leftPart: [0, max($ds - 1, 0)] and rightPart: [$ds, max($l - 1, 0)].
606 | * Where $ds is the index position of the deleteStart and $l is the length of th array.
607 | *
608 | * After that, generates the two array leftArray and rightArray by using the
609 | * ranges leftPart and rightParts respectively.
610 | */
611 | $leftArray = [];
612 | $rightArray = [];
613 |
614 | if ($deleteStart === 0)
615 | {
616 | $rightArray = $elements;
617 | }
618 | elseif ($deleteStart >= $newLength)
619 | {
620 | $leftArray = $elements;
621 | }
622 | else
623 | {
624 | if ($newLength > 0)
625 | {
626 | /**
627 | * Calculating the array chunk ranges.
628 | */
629 | $leftPart = [0, max($deleteStart - 1, 0)];
630 | $rightPart = [$deleteStart, max($newLength - 1, 0)];
631 |
632 | /**
633 | * Generating the leftArray from the range leftPart
634 | */
635 | if ($leftPart[0] <= $leftPart[1])
636 | {
637 | for ($x = $leftPart[0]; $x <= $leftPart[1]; ++$x)
638 | {
639 | $leftArray[$newKeys[$x]] = $elements[$newKeys[$x]];
640 | }
641 | }
642 |
643 | /**
644 | * Generating the rightArray from the range rightPart
645 | */
646 | if ($rightPart[0] <= $rightPart[1])
647 | {
648 | for ($x = $rightPart[0]; $x <= $rightPart[1]; ++$x)
649 | {
650 | $rightArray[$newKeys[$x]] = $elements[$newKeys[$x]];
651 | }
652 | }
653 | }
654 | }
655 |
656 | /**
657 | * Finally merge the items along with two chunks. The items array
658 | * acts like the middle point of the left and right arrays.
659 | */
660 | $finalArray = array_merge($leftArray, $items, $rightArray);
661 |
662 | /**
663 | * Mutate the array by updated array.
664 | */
665 | $this->bind($finalArray);
666 |
667 | // Create and JsArray instance and return the deleted items array
668 | return $this->bind($deletedArray, false);
669 | }
670 |
671 | /**
672 | * Concat arrays or values with the reference array and returns
673 | * a new array. This is not mutate the original array rather
674 | * returns a new array.
675 | *
676 | * @param mixed ...$values Scaler value(s) or array(s)
677 | *
678 | * @return JsArray Concatenated array.
679 | * @since 1.0.0
680 | */
681 | public function concat(...$values) : JsArray
682 | {
683 | $elements = $this->get();
684 |
685 | /**
686 | * If nothing provided to concatenate then return the
687 | * current array values but with a new instance.
688 | */
689 | if (empty($values))
690 | {
691 | return $this->bind($elements, false);
692 | }
693 |
694 | // Initially merge the elements and put them into the concatenated array.
695 | $concatenated = array_merge([], $elements);
696 |
697 | /**
698 | * Iterate through the values parameter if the given parameter is
699 | * an instance of JsArray then get the plain array elements and
700 | * merge with concatenated array.
701 | * If value is a plain array then concat it with the concatenate array.
702 | * If value is a scaler value then push this.
703 | */
704 | foreach ($values as $value)
705 | {
706 | if ($value instanceof JsArray)
707 | {
708 | $concatenated = array_merge($concatenated, $value->get());
709 | }
710 | elseif (\is_array($value))
711 | {
712 | $concatenated = array_merge($concatenated, $value);
713 | }
714 | else
715 | {
716 | array_push($concatenated, $value);
717 | }
718 | }
719 |
720 | return $this->bind($concatenated, false);
721 | }
722 | }
723 |
--------------------------------------------------------------------------------
/src/Traits/Arrays/ConditionalTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Arrays;
9 |
10 | /**
11 | * Trait function for array conditionals
12 | *
13 | * @since 1.0.0
14 | */
15 | trait ConditionalTrait
16 | {
17 | /**
18 | * Every is a function which returns a boolean value if every value of an array has passed
19 | * a specific condition,
20 | *
21 | * @param callable $callback The callback function which defines the rule. If each and
22 | * every element of the array has passed the rule then it returns
23 | * true. Otherwise it returns false.
24 | *
25 | * @return boolean True if each and every element passes the condition, false otherwise.
26 | * @since 1.0.0
27 | */
28 | public function every(callable $callback) : bool
29 | {
30 | $elements = $this->get();
31 | $passed = 0;
32 | $index = 0;
33 |
34 | foreach ($elements as $key => $item)
35 | {
36 | $condition = \call_user_func_array($callback, [$item, $index, $key]);
37 | ++$index;
38 |
39 | /**
40 | * If it returns a truthful value then increment the passed variable.
41 | */
42 | if (!empty($condition))
43 | {
44 | $passed++;
45 | }
46 | }
47 |
48 | // Returns true if it passes for all the items, false otherwise.
49 | return $passed === $this->length;
50 | }
51 |
52 | /**
53 | * Some is a function which returns a boolean value if only one element of an array has passed
54 | * a specific condition,
55 | *
56 | * @param callable $callback The callback function which defines the rule. If any element
57 | * of the array has passed the rule then it returns
58 | * true. If all elements fail the test then returns false.
59 | *
60 | * @return boolean True if only one element passes the condition, false if all elements fail the test otherwise.
61 | * @since 1.0.0
62 | */
63 | public function some(callable $callback) : bool
64 | {
65 | $elements = $this->get();
66 | $passed = false;
67 | $index = 0;
68 |
69 | foreach ($elements as $key => $item)
70 | {
71 | $condition = \call_user_func_array($callback, [$item, $index, $key]);
72 |
73 | /**
74 | * If the callback returns a truth value then
75 | * the whole function passes the test. so return true.
76 | */
77 | if (!empty($condition))
78 | {
79 | $passed = true;
80 | break;
81 | }
82 |
83 | ++$index;
84 | }
85 |
86 | return $passed;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/Traits/Arrays/IteratorTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Arrays;
9 |
10 | use Ahamed\JsPhp\JsArray;
11 |
12 | /**
13 | * Trait function for array conditionals
14 | *
15 | * @since 1.0.0
16 | */
17 | trait IteratorTrait
18 | {
19 |
20 | /**
21 | * Method to iterate through an array and apply the callback
22 | * function to each element and return the element
23 | *
24 | * @param callable $callback The callback function
25 | *
26 | * @return void
27 | * @since 1.0.0
28 | */
29 | public function forEach(callable $callback) : void
30 | {
31 | $elements = $this->get();
32 | $index = 0;
33 |
34 | foreach ($elements as $key => $item)
35 | {
36 | if (\is_array($item))
37 | {
38 | $item = $this->bind($item, false);
39 | }
40 |
41 | \call_user_func_array($callback, [$item, $index, $key]);
42 |
43 | ++$index;
44 | }
45 | }
46 |
47 | /**
48 | * Method to iterate through an array and apply an modification
49 | * to each elements and returns a new array.
50 | *
51 | * @param callable $callback The callback function. This function must have a
52 | * return value. If there is no return value then it
53 | * returns `null`.
54 | *
55 | * @return JsArray Modified array
56 | * @since 1.0.0
57 | */
58 | public function map(callable $callback) : JsArray
59 | {
60 | $elements = $this->get();
61 | $modifiedArray = [];
62 | $index = 0;
63 |
64 | foreach ($elements as $key => $item)
65 | {
66 | /**
67 | * If item is an array then make the item an instance of JsArray
68 | */
69 | if (\is_array($item))
70 | {
71 | $item = $this->bind($item, false);
72 | }
73 |
74 | $modifiedItem = \call_user_func_array($callback, [$item, $index, $key]);
75 |
76 | ++$index;
77 |
78 | /**
79 | * Retrieve the original array instead of the JsArray instance.
80 | */
81 | if ($modifiedItem instanceof JsArray)
82 | {
83 | $modifiedItem = $modifiedItem->get();
84 | }
85 |
86 | /**
87 | * If nothing returns from the callback function then
88 | * set the value as null
89 | */
90 | if (!isset($modifiedItem))
91 | {
92 | $modifiedItem = null;
93 | }
94 |
95 | /**
96 | * This method keeps the original keys
97 | */
98 | $modifiedArray[$key] = $modifiedItem;
99 |
100 | /**
101 | * Unset the modifiedItem so that if it's an instance of
102 | * JsArray then it releases the memory.
103 | */
104 | unset($modifiedItem);
105 | }
106 |
107 | /**
108 | * Create the newly created modifiedArray as an instance of
109 | * JsArray for chaining support.
110 | */
111 | $newInstance = $this->bind($modifiedArray, false);
112 |
113 | return $newInstance;
114 | }
115 |
116 | /**
117 | * Filters an array and returns a new array.
118 | *
119 | * @param callable $callback The callback function. This function must
120 | * return a boolean value. If returns something
121 | * else rather than true/false then it takes the
122 | * truth value of the returned value. If nothing
123 | * returns then it counts this as a falsy value.
124 | *
125 | * @return JsArray Filtered Array
126 | * @since 1.0.0
127 | */
128 | public function filter(callable $callback) : JsArray
129 | {
130 | $elements = $this->get();
131 | $isAssoc = self::isAssociativeArray($elements);
132 | $filteredArray = [];
133 | $index = 0;
134 |
135 | foreach ($elements as $key => $item)
136 | {
137 | /**
138 | * If item is an array then make the item an instance of JsArray
139 | */
140 | if (\is_array($item))
141 | {
142 | $item = $this->bind($item, false);
143 | }
144 |
145 | $condition = \call_user_func_array($callback, [$item, $index, $key]);
146 |
147 | ++$index;
148 |
149 | /**
150 | * If the callback returns a truth value then keep the item
151 | */
152 | if (!empty($condition))
153 | {
154 | /**
155 | * If the item is an instance of JsArray then retrieve the
156 | * original array elements.
157 | */
158 | if ($item instanceof JsArray)
159 | {
160 | $item = $item->get();
161 | }
162 |
163 | /**
164 | * If the array is an associative array then preserve the keys
165 | * If any numeric key exists on the associative array then
166 | * rearrange the numeric indics from zero.
167 | */
168 | if ($isAssoc)
169 | {
170 | if (\is_numeric($key))
171 | {
172 | $filteredArray[] = $item;
173 | }
174 | else
175 | {
176 | $filteredArray[$key] = $item;
177 | }
178 | }
179 | else
180 | {
181 | $filteredArray[] = $item;
182 | }
183 | }
184 | }
185 |
186 | /**
187 | * Make an instance of JsArray for chaining support.
188 | */
189 | $newInstance = $this->bind($filteredArray, false);
190 |
191 | return $newInstance;
192 | }
193 |
194 | /**
195 | * FlatMap method. This method run a map on an array using user callable and
196 | * flatten the the mapped array (into 1 depth) if nested found.
197 | *
198 | * @param callable $callable The user defined callable.
199 | *
200 | * @return JsArray The flat mapped JsArray instance for chaining.
201 | * @since 1.0.0
202 | */
203 | public function flatMap(callable $callable) : JsArray
204 | {
205 | $mapped = $this->map($callable);
206 | $flatArrayInstance = $mapped->flat();
207 |
208 | return $flatArrayInstance;
209 | }
210 |
211 | /**
212 | * Reduce function to loop through an array and returns
213 | * a new array based on it's callback.
214 | *
215 | * @param callable $callback Callback function which takes (accumulator, currentValue, index)
216 | * as it's arguments and returns the accumulator.
217 | * @param mixed $initial Initial value. If this is not defined then the first index of
218 | * array will be used as the initial value.
219 | *
220 | * @return self
221 | * @since 1.0.0
222 | */
223 | public function reduce($callback, $initial = null)
224 | {
225 | // Get the array
226 | $elements = $this->get();
227 | $skipFirst = false;
228 |
229 | /**
230 | * Check if the initial param is provided then starts the
231 | * accumulator with the initiator.
232 | * Otherwise the first value of the elements array will be
233 | * the initial value of the accumulator and user gets the
234 | * current value item from the second value of the array.
235 | */
236 | if (!isset($initial))
237 | {
238 | $accumulator = $elements[0];
239 | $skipFirst = true;
240 | }
241 | else
242 | {
243 | $accumulator = $initial;
244 | }
245 |
246 | $index = 0;
247 |
248 | foreach ($elements as $key => $item)
249 | {
250 | /**
251 | * If the initial param is provided then skip the first item to execute.
252 | */
253 | if ($skipFirst)
254 | {
255 | $skipFirst = false;
256 | continue;
257 | }
258 |
259 | /**
260 | * If the item is an array then makes it as an instance of JsArray.
261 | */
262 | if (\is_array($item))
263 | {
264 | $item = $this->bind($item, false);
265 | }
266 |
267 | /**
268 | * If the accumulator is an array them makes it as an instance of JsArray.
269 | */
270 | if (\is_array($accumulator))
271 | {
272 | $accumulator = $this->bind($accumulator, false);
273 | }
274 |
275 | $accumulator = \call_user_func_array($callback, [$accumulator, $item, $index, $key]);
276 |
277 | ++$index;
278 |
279 | /**
280 | * If the updated accumulator is returned as an instance of JsArray
281 | * then check the elements of the accumulator and if any of them
282 | * is an instance of JsArray then retrieve the original
283 | * array data and unset the JsArray instance/object as there is no
284 | * need of this anymore and this also lead the program to collect
285 | * the existing garbages.
286 | */
287 | if ($accumulator instanceof JsArray)
288 | {
289 | $accumulator = $accumulator->map(
290 | function ($acc) {
291 | if ($acc instanceof JsArray)
292 | {
293 | $data = $acc->get();
294 | unset($acc);
295 |
296 | return $data;
297 | }
298 |
299 | return $acc;
300 | }
301 | );
302 | }
303 | }
304 |
305 | /**
306 | * If the accumulator is an array then makes it as an instance of JsArray
307 | * as we can continue chaining.
308 | */
309 | if (\is_array($accumulator))
310 | {
311 | $newInstance = $this->bind($accumulator, false);
312 |
313 | return $newInstance;
314 | }
315 |
316 | return $accumulator;
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/src/Traits/Arrays/ModifierTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Arrays;
9 |
10 | use Ahamed\JsPhp\JsArray;
11 | use Generator;
12 |
13 | /**
14 | * Trait function for array modifiers
15 | *
16 | * @since 1.0.0
17 | */
18 | trait ModifierTrait
19 | {
20 | /**
21 | * Fill an array by a given value within a range or full array.
22 | *
23 | * @param mixed $value The value by which the array will be filled.
24 | * @param integer $start The starting index from which filling starts.
25 | * @param integer $end The ending index to which the filling happens.
26 | *
27 | * @return self
28 | * @since 1.0.0
29 | */
30 | public function fill($value, $start = null, $end = null)
31 | {
32 | if (!isset($value))
33 | {
34 | throw new \InvalidArgumentException(\sprintf('Set fill value.'));
35 | }
36 |
37 | $elements = $this->get();
38 | $length = $this->length;
39 |
40 | /**
41 | * Make the start value a 32 bit integer value by 0 bit right shifting.
42 | */
43 | $relativeStart = (int) $start;
44 |
45 | /**
46 | * If then relativeStart is a negative number then add this with the length of the
47 | * array and set the value of $k as the maximum of the summation and 0 because if
48 | * the summation of the length and relative start becomes a negative number again
49 | * then we should take 0, as the index could not be negative.
50 | *
51 | * And if the relativeStart is a positive number then set $k is equal to
52 | * the minimum of relativeStart and the length of the array. The reason of this
53 | * is, if the value of the relative start is exceeded the length of the array then take the
54 | * length as the start value.
55 | */
56 | $k = $relativeStart < 0 ?
57 | max($length + $relativeStart, 0) :
58 | min($relativeStart, $length);
59 |
60 | /**
61 | * If end limit is not given then take the length as the ene value,
62 | * otherwise take the end value and make it 32 bit number by right shifting 0 bit.
63 | */
64 | $relativeEnd = is_null($end) ? $length : (int) $end;
65 |
66 | /**
67 | * As described before the upper limit could not less than 0 or greater than the length of the array.
68 | */
69 | $finalValue = $relativeEnd < 0 ?
70 | max($length + $relativeEnd, 0) :
71 | min($relativeEnd, $length);
72 |
73 | $filledArray = [];
74 |
75 | $inc = 0;
76 |
77 | foreach ($elements as $key => $item)
78 | {
79 | /**
80 | * If the current index value is within the given range
81 | * then fill the array index by the fill value otherwise keep the
82 | * original value.
83 | */
84 | if ($inc >= $k && $inc < $finalValue)
85 | {
86 | $filledArray[$key] = $value;
87 | }
88 | else
89 | {
90 | $filledArray[$key] = $item;
91 | }
92 |
93 | ++$inc;
94 | }
95 |
96 | /**
97 | * Finally mutate the original array with the filled one
98 | */
99 | $this->bind($filledArray);
100 |
101 | return $this;
102 | }
103 |
104 | /**
105 | * Reverse the array and return a new one.
106 | *
107 | * @return self Reversed Array
108 | * @since 1.0.0
109 | */
110 | public function reverse()
111 | {
112 | $elements = $this->get();
113 | $length = $this->length;
114 |
115 | $reversedArray = [];
116 |
117 | /**
118 | * Check if the array is an associative array or a sequential array.
119 | * If this is an associative array then reverse with the keys otherwise
120 | * keys don't matter.
121 | */
122 | $isAssoc = self::isAssociativeArray($elements);
123 | $keys = array_keys($elements);
124 |
125 | for ($i = $length - 1; $i >= 0; --$i)
126 | {
127 | if ($isAssoc)
128 | {
129 | $key = $keys[$i];
130 |
131 | if (\is_numeric($key))
132 | {
133 | $reversedArray[] = $elements[$keys[$i]];
134 | }
135 | else
136 | {
137 | $reversedArray[$keys[$i]] = $elements[$keys[$i]];
138 | }
139 | }
140 | else
141 | {
142 | $reversedArray[] = $elements[$i];
143 | }
144 | }
145 |
146 | /**
147 | * Mutate the original array with the reversed one and return the instance for chaining.
148 | */
149 | $this->bind($reversedArray);
150 |
151 | return $this;
152 | }
153 |
154 | /**
155 | * Flatten a multi-dimensional array to a 1-D array.
156 | * Currently there is no level of flattening the array, it flattens
157 | * using Infinite level and makes a flat array.
158 | *
159 | * @param int $depth The flatten depth.
160 | *
161 | * @return JsArray The flatten array as the instanceof JsArray so that user can chain it.
162 | * @since 1.0.0
163 | */
164 | public function flat(int $depth = 1)
165 | {
166 | /**
167 | * Created a stack with the array
168 | */
169 | $elements = $this->get();
170 |
171 | /**
172 | * Check the array is an associative array or not. If it's an associative
173 | * array then throw an error that Flattening could not be applied on an associative array.
174 | */
175 | $isAssoc = self::isAssociativeArray($elements);
176 |
177 | if ($isAssoc)
178 | {
179 | throw new \UnexpectedValueException(\sprintf('Flatten could not be applied to an associative array. Please use it in sequential array.'));
180 | }
181 |
182 | $flattenArray = iterator_to_array($this->flatten($elements, $depth), false);
183 |
184 | return $this->bind($flattenArray, false);
185 | }
186 |
187 | /**
188 | * Flatten the array elements by using depth and generator.
189 | *
190 | * @param array $elements The elements array.
191 | * @param int $depth The depth value.
192 | *
193 | * @return Generator A generator with flatten values.
194 | * @since 1.0.0
195 | */
196 | private function flatten(array $elements, int $depth) : Generator
197 | {
198 | if ($depth === null)
199 | {
200 | $depth = 1;
201 | }
202 |
203 | foreach ($elements as $item)
204 | {
205 | if (is_array($item) && $depth > 0)
206 | {
207 | yield from $this->flatten($item, $depth - 1);
208 | }
209 | else
210 | {
211 | yield $item;
212 | }
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/Traits/Arrays/SearchingTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Arrays;
9 |
10 | use Ahamed\JsPhp\JsArray;
11 |
12 | /**
13 | * Trait function for array modifiers
14 | *
15 | * @since 1.0.0
16 | */
17 | trait SearchingTrait
18 | {
19 | /**
20 | * Find element if it satisfies the callback's rule.
21 | *
22 | * @param callable $callback Callback function which contains two arguments ($item, $index)
23 | * and returns a boolean value. If the return value is true then
24 | * it understands that the searching item is found and returns it.
25 | *
26 | * @return mixed Searched value if found. null otherwise.
27 | * @since 1.0.0
28 | */
29 | public function find(callable $callback)
30 | {
31 | $elements = $this->get();
32 | $findValue = null;
33 | $index = 0;
34 |
35 | foreach ($elements as $key => $item)
36 | {
37 | /**
38 | * If the item is an array then make it as an instance of JsArray
39 | */
40 | if (\is_array($item))
41 | {
42 | $item = $this->bind($item, false);
43 | }
44 |
45 | $condition = \call_user_func_array($callback, [$item, $index, $key]);
46 |
47 | ++$index;
48 |
49 | /**
50 | * If user condition returns true then the item is found
51 | * and takes the item and returns it.
52 | */
53 | if (!empty($condition))
54 | {
55 | $findValue = $item;
56 | break;
57 | }
58 | }
59 |
60 | /**
61 | * If the item is an instance of JsArray then retrieve the original array.
62 | */
63 | if ($findValue instanceof JsArray)
64 | {
65 | $findValue = $findValue->get();
66 | }
67 |
68 | return $findValue;
69 | }
70 |
71 | /**
72 | * Find element index if it satisfies the callback's rule.
73 | *
74 | * @param callable $callback Callback function which contains two arguments ($item, $index)
75 | * and returns a boolean value. If the return value is true then
76 | * it understands that the searching item is found and returns it's index value.
77 | *
78 | * @return integer Searched value if found. -1 otherwise.
79 | * @since 1.0.0
80 | */
81 | public function findIndex($callback) : int
82 | {
83 | $elements = $this->get();
84 | $index = 0;
85 | $findIndex = -1;
86 | $arrayIndex = 0;
87 |
88 | foreach ($elements as $key => $item)
89 | {
90 | /**
91 | * If an array them make it an instance of JsArray
92 | */
93 | if (\is_array($item))
94 | {
95 | $item = $this->bind($item, false);
96 | }
97 |
98 | $condition = \call_user_func_array($callback, [$item, $arrayIndex, $key]);
99 |
100 | ++$arrayIndex;
101 |
102 | /**
103 | * If user condition matches then find the item and return the index.
104 | */
105 | if (!empty($condition))
106 | {
107 | $findIndex = $index;
108 | break;
109 | }
110 |
111 | $index++;
112 | }
113 |
114 | return $findIndex;
115 | }
116 |
117 | /**
118 | * Find the last element index if it satisfies the callback's rule.
119 | *
120 | * @param callable $callback Callback function which contains two arguments ($item, $index)
121 | * and returns a boolean value. If the return value is true then
122 | * it understands that the searching item is found and returns it's index value.
123 | *
124 | * @return integer Searched value if found. -1 otherwise.
125 | * @since 1.0.0
126 | */
127 | public function findLastIndex($callback) : int
128 | {
129 | $elements = $this->get();
130 | $length = $this->length;
131 |
132 | $index = 0;
133 | $findLastIndex = -1;
134 | $keys = array_keys($elements);
135 |
136 | for ($i = $length - 1; $i >= 0; --$i)
137 | {
138 | $item = $elements[$i];
139 |
140 | if (\is_array($item))
141 | {
142 | $item = $this->bind($item, false);
143 | }
144 |
145 | $condition = \call_user_func_array($callback, [$item, $i, $keys[$i]]);
146 |
147 | if (!empty($condition))
148 | {
149 | /**
150 | * We traverse from the end of the array and when find the
151 | * item there we stopped so the index is increased with respect to the
152 | * last to first direction. But out index value needs from the first position.
153 | */
154 | $findLastIndex = ($length - 1) - $index;
155 | break;
156 | }
157 |
158 | ++$index;
159 | }
160 |
161 | return $findLastIndex;
162 | }
163 |
164 | /**
165 | * Test if a value includes on an array.
166 | * Note: It checks loose equality while searching.
167 | *
168 | * @param mixed $item The testing item
169 | * @param int $fromIndex From which index the search starts.
170 | *
171 | * @return boolean true if found, false otherwise.
172 | */
173 | public function includes($item, $fromIndex = 0)
174 | {
175 | $elements = $this->get();
176 | $length = $this->length;
177 |
178 | /**
179 | * Sanitize the search starting position. If the provided fromIndex
180 | * is a negative number then take the maximum between the 0 and $length + $fromIndex
181 | * so that it never being a negative index number.
182 | * For positive numbers the from index never be greater than the $length
183 | *
184 | */
185 | $fromIndex = (int) $fromIndex;
186 | $start = $fromIndex < 0 ?
187 | max(0, $length + $fromIndex) :
188 | min($length, $fromIndex);
189 |
190 | for ($i = $start; $i < $length; ++$i)
191 | {
192 | /**
193 | * It matches the array elements with loose equality with the searched item.
194 | */
195 | if ($elements[$i] == $item)
196 | {
197 | return true;
198 | }
199 | }
200 |
201 | return false;
202 | }
203 |
204 | /**
205 | * Find the indexOf of an item in the array.
206 | *
207 | * @param mixed $item The item to find.
208 | * @param integer $fromIndex From index from where it starts searching.
209 | *
210 | * @return boolean Index value of first occurrence. If not found then returns -1
211 | * @since 1.0.0
212 | */
213 | public function indexOf($item, $fromIndex = 0)
214 | {
215 | $elements = $this->get();
216 | $length = $this->length;
217 |
218 | $findIndex = -1;
219 |
220 | $fromIndex = (int) $fromIndex;
221 |
222 | /**
223 | * If no value in the array then return -1.
224 | */
225 | if ($length === 0)
226 | {
227 | return -1;
228 | }
229 |
230 | /**
231 | * If the fromIndex value is greater than or equal to the length
232 | * of the array then return -1. That means there is nothing to search.
233 | */
234 | if ($fromIndex >= $length)
235 | {
236 | return -1;
237 | }
238 |
239 | /**
240 | * Find the kth place from where it starts searching.
241 | * If fromIndex is negative then take the maximum value of between
242 | * length + fromIndex and zero.
243 | *
244 | * If it's a positive number then takes the fromIndex directly.
245 | */
246 | $k = $fromIndex < 0 ?
247 | max($length + $fromIndex, 0) :
248 | $fromIndex;
249 |
250 | for ($i = 0; $i < $length; ++$i)
251 | {
252 | /**
253 | * Check if the index value is greater than or equal to the
254 | * starting index $k and if the element strictly equal to the
255 | * searching item then index found then return the index value.
256 | *
257 | * If nothing found then return -1.
258 | */
259 | if ($i >= $k && $elements[$i] === $item)
260 | {
261 | $findIndex = $i;
262 | break;
263 | }
264 | }
265 |
266 | return $findIndex;
267 | }
268 |
269 | /**
270 | * Find the lastIndexOf of an item in the array.
271 | *
272 | * @param mixed $item The item to find.
273 | * @param integer $fromIndex From index from where it starts searching.
274 | *
275 | * @return boolean Index value of last occurrence. If not found then returns -1
276 | * @since 1.0.0
277 | */
278 | public function lastIndexOf($item, $fromIndex = 0)
279 | {
280 | $elements = $this->get();
281 | $length = $this->length;
282 |
283 | $findIndex = -1;
284 |
285 | $fromIndex = (int) $fromIndex;
286 |
287 | /**
288 | * If no value in the array then return -1.
289 | */
290 | if ($length === 0)
291 | {
292 | return -1;
293 | }
294 |
295 | /**
296 | * If the fromIndex value is greater than or equal to the length
297 | * of the array then return -1. That means there is nothing to search.
298 | */
299 | if ($fromIndex >= $length)
300 | {
301 | return -1;
302 | }
303 |
304 | /**
305 | * Find the kth place from where it starts searching.
306 | * If fromIndex is negative then take the maximum value of between
307 | * length + fromIndex and zero.
308 | *
309 | * If it's a positive number then takes the fromIndex directly.
310 | */
311 | $k = $fromIndex < 0 ?
312 | max($length + $fromIndex, 0) :
313 | $fromIndex;
314 |
315 | $index = 0;
316 |
317 | for ($i = $length - 1; $i >= 0; --$i)
318 | {
319 | /**
320 | * Check if the index value is greater than or equal to the
321 | * starting index $k and if the element strictly equal to the
322 | * searching item then index found then return the index value.
323 | *
324 | * If nothing found then return -1.
325 | */
326 | if ($i >= $k && $elements[$i] === $item)
327 | {
328 | $findIndex = $index;
329 | break;
330 | }
331 |
332 | ++$index;
333 | }
334 |
335 | return $findIndex;
336 | }
337 | }
338 |
--------------------------------------------------------------------------------
/src/Traits/Arrays/SortingTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Arrays;
9 |
10 | use Ahamed\JsPhp\JsArray;
11 | use Ahamed\JsPhp\Utilities;
12 |
13 | /**
14 | * Trait for array sorting
15 | *
16 | * @since 1.0.0
17 | */
18 | trait SortingTrait
19 | {
20 | /**
21 | * Method for sorting an array.
22 | *
23 | * @param callable $callback Callback function for the sort
24 | *
25 | * @return JsArray The sorted array.
26 | * @since 1.0.0
27 | */
28 | public function sort(callable $callback = null)
29 | {
30 | $sorted = Utilities::mergeSort($this, $callback);
31 |
32 | if ($sorted instanceof JsArray)
33 | {
34 | $this->bind($sorted->get());
35 | }
36 | elseif (\is_array($sorted))
37 | {
38 | $this->bind($sorted);
39 | }
40 |
41 | return $this;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Traits/Objects/StaticTrait.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp\Traits\Objects;
9 |
10 | use Ahamed\JsPhp\JsArray;
11 | use Ahamed\JsPhp\JsObject;
12 |
13 | /**
14 | * Trait function for array conditionals
15 | *
16 | * @since 1.0.0
17 | */
18 | trait StaticTrait
19 | {
20 | /**
21 | * Assign a source object with a target object.
22 | *
23 | * @param mixed $target The target object.
24 | * @param mixed ...$sources The source object.
25 | *
26 | * @return JsObject The merged object.
27 | * @since 1.0.0
28 | */
29 | public static function assign($target, ...$sources) : JsObject
30 | {
31 | $target = !($target instanceof JsObject) ? new JsObject($target) : $target;
32 |
33 | if (!\is_null($sources))
34 | {
35 | foreach ($sources as $source)
36 | {
37 | $source = !($source instanceof JsObject) ? new JsObject($source) : $source;
38 |
39 | if (JsObject::keys($source)->length() > 0)
40 | {
41 | foreach ($source as $key => $value)
42 | {
43 | $target[$key] = $value;
44 | }
45 | }
46 | }
47 | }
48 |
49 | return $target;
50 | }
51 |
52 | /**
53 | * Create a JsObject from entries. entries are map like array of arrays
54 | * which contains first value as key and the second value as value.
55 | *
56 | * @param array|JsArray $entries The entries array or JsArray instance.
57 | *
58 | * @return JsObject The generated Object from entries.
59 | * @throws InvalidArgumentException
60 | * @since 1.0.0
61 | */
62 | public static function fromEntries($entries) : JsObject
63 | {
64 | if (!(\is_array($entries) || $entries instanceof JsArray))
65 | {
66 | throw new \InvalidArgumentException(
67 | \sprintf('Invalid entries provided!')
68 | );
69 | }
70 |
71 | if (empty($entries))
72 | {
73 | return new JsObject();
74 | }
75 |
76 | $object = new JsObject();
77 |
78 | foreach ($entries as $item)
79 | {
80 | list ($key, $value) = $item;
81 |
82 | if (isset($key, $value))
83 | {
84 | $object->$key = $value;
85 | }
86 | }
87 |
88 | return $object;
89 | }
90 |
91 | /**
92 | * Static method for getting the keys of an object.
93 | *
94 | * @param mixed $object The object elements for which the keys return.
95 | *
96 | * @return JsArray An instance of JsArray with the keys.
97 | * @since 1.0.0
98 | */
99 | public static function keys($object)
100 | {
101 | $instance = new JsObject($object);
102 | $elements = $instance->get();
103 | $keys = array_keys(\get_object_vars($elements));
104 |
105 | return new JsArray($keys);
106 | }
107 |
108 | /**
109 | * Static method for getting the values of an object.
110 | *
111 | * @param mixed $object The object elements for which the values return.
112 | *
113 | * @return JsArray An instance of JsArray with the values.
114 | * @since 1.0.0
115 | */
116 | public static function values($object)
117 | {
118 | $instance = new JsObject($object);
119 | $elements = $instance->get();
120 | $values = array_values(\get_object_vars($elements));
121 |
122 | return new JsArray($values);
123 | }
124 |
125 | /**
126 | * Get entries of an object. The entries is the array of [key, value] pair.
127 | *
128 | * @param array|object|JsObject $object The object for which we get the entries.
129 | *
130 | * @return JsArray The entries array.
131 | * @since 1.0.0
132 | */
133 | public static function entries($object)
134 | {
135 | $instance = new JsObject($object);
136 | $elements = $instance->get();
137 |
138 | $vars = \get_object_vars($elements);
139 | $keys = array_keys($vars);
140 | $values = array_values($vars);
141 | $entries = [];
142 |
143 | for ($i = 0, $length = count($keys); $i < $length; ++$i)
144 | {
145 | $entries[] = [$keys[$i], $values[$i]];
146 | }
147 |
148 | return new JsArray($entries);
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/Utilities.php:
--------------------------------------------------------------------------------
1 |
5 | * @copyright Copyright (c) 2020 Sajeeb Ahamed
6 | * @license MIT https://opensource.org/licenses/MIT
7 | */
8 | namespace Ahamed\JsPhp;
9 |
10 | /**
11 | * Utility class. This class contains all the utilities or the helper
12 | * functions of the system. All the methods are static.
13 | */
14 | class Utilities
15 | {
16 | /**
17 | * Merge sort of an array.
18 | *
19 | * @param JsArray $array An JsArray instance
20 | * @param Func $compareFunc Compare function for user defined sorting
21 | *
22 | * @return JsArray
23 | * @since 1.0.0
24 | */
25 | public static function mergeSort($array, $compareFunc = null)
26 | {
27 | $length = $array->length;
28 |
29 | /**
30 | * If the given array has only one or no element then nothing to sort.
31 | */
32 | if ($length <= 1)
33 | {
34 | return $array;
35 | }
36 |
37 | /**
38 | * Check if the array is an associative array. If then reserve the keys
39 | */
40 | $isAssoc = JsArray::isAssociativeArray($array);
41 |
42 | /**
43 | * Divide the array into two parts just in middle.
44 | *
45 | */
46 | $middle = floor($length / 2);
47 | $leftArray = $array->slice(0, $middle);
48 | $rightArray = $array->slice($middle);
49 |
50 | /**
51 | * Merge and conquer the problem ;)
52 | * This will merge but continue dividing the divided parts also
53 | * and divide until gets one element
54 | */
55 | return self::merge(
56 | self::mergeSort($leftArray, $compareFunc),
57 | self::mergeSort($rightArray, $compareFunc),
58 | $compareFunc,
59 | $isAssoc
60 | );
61 | }
62 |
63 | /**
64 | * Merge two array after sorting them.
65 | *
66 | * @param JsArray $leftArray The left part to merge
67 | * @param JsArray $rightArray The right part to merge
68 | * @param Func $compareFunc Compare function for user defined sorting
69 | * @param bool $isAssoc If the array is an associative array.
70 | *
71 | * @return JsArray Merged array after sorting.
72 | * @since 1.0.0
73 | */
74 | public static function merge($leftArray, $rightArray, $compareFunc, $isAssoc)
75 | {
76 | $sorted = new JsArray([]);
77 | $leftIndex = 0;
78 | $rightIndex = 0;
79 |
80 | /**
81 | * Get the keys of the leftArray and rightArray.
82 | * These will be used for reserving the associative array keys
83 | */
84 | $leftKeys = array_keys($leftArray->get());
85 | $rightKeys = array_keys($rightArray->get());
86 |
87 | /**
88 | * If leftIndex is not exceeded the leftArray's length and
89 | * rightIndex is not exceeded the rightArray's length
90 | * continue checking and merging.
91 | */
92 | while ($leftIndex < $leftArray->length && $rightIndex < $rightArray->length)
93 | {
94 | /**
95 | * Get the key value of a certain index
96 | * for providing them to the user compare function
97 | */
98 | $itemLeftKey = $leftKeys[$leftIndex];
99 | $itemRightKey = $rightKeys[$rightIndex];
100 |
101 | /**
102 | * If the main array is an associative array then get the item
103 | * with the help of keys.
104 | * Otherwise get the item by using index.
105 | */
106 | if ($isAssoc)
107 | {
108 | $itemLeft = $leftArray[$itemLeftKey];
109 | $itemRight = $rightArray[$itemRightKey];
110 | }
111 | else
112 | {
113 | $itemLeft = $leftArray[$itemLeftKey];
114 | $itemRight = $rightArray[$itemRightKey];
115 | }
116 |
117 | /**
118 | * If an item of the main array is an array then make it
119 | * as an instance of JsArray for providing user privilege
120 | * to handle them inside compare function.
121 | */
122 | if (\is_array($itemLeft))
123 | {
124 | $itemLeft = new JsArray($itemLeft);
125 | }
126 |
127 | if (\is_array($itemRight))
128 | {
129 | $itemRight = new JsArray($itemRight);
130 | }
131 |
132 | /**
133 | * If your compare function is given then get the response and go accordingly.
134 | */
135 | if (!\is_null($compareFunc))
136 | {
137 | // Get the user compare function's return value.
138 | $condition = \call_user_func_array($compareFunc, [$itemLeft, $itemRight, $itemLeftKey, $itemRightKey]);
139 |
140 | /**
141 | * If nothing return by the compare function then check default
142 | * leftItem less than rightItem.
143 | * If the compare function returns a value greater than 0 i.e.
144 | * a positive value then swap the values and right value comes
145 | * before the left value.
146 | * Otherwise, it stands as it is.
147 | */
148 | if (\is_null($condition))
149 | {
150 | $compare = $itemLeft < $itemRight;
151 | }
152 | elseif ($condition > 0)
153 | {
154 | $compare = false;
155 | }
156 | else
157 | {
158 | $compare = true;
159 | }
160 | }
161 | else
162 | {
163 | $compare = $itemLeft < $itemRight;
164 | }
165 |
166 | /**
167 | * If compare is true then no swap happens. Push the left array value
168 | * before the right array.
169 | * Otherwise, right array value comes first than the left array.
170 | */
171 | if ($compare)
172 | {
173 | /**
174 | * Revert the inner JsArray into regular array
175 | */
176 | if ($itemLeft instanceof JsArray)
177 | {
178 | $itemLeft = $itemLeft->get();
179 | }
180 |
181 | /**
182 | * For associative array the keys would be preserved.
183 | * And for sequential array the index would be rebase.
184 | */
185 | if ($isAssoc)
186 | {
187 | if (\is_numeric($itemLeftKey))
188 | {
189 | $sorted[] = $itemLeft;
190 | }
191 | else
192 | {
193 | $sorted[$itemLeftKey] = $itemLeft;
194 | }
195 | }
196 | else
197 | {
198 | $sorted->push($itemLeft);
199 | }
200 |
201 | ++$leftIndex;
202 | }
203 | else
204 | {
205 | /**
206 | * Revert the inner JsArray into regular array
207 | */
208 | if ($itemRight instanceof JsArray)
209 | {
210 | $itemRight = $itemRight->get();
211 | }
212 |
213 | /**
214 | * For associative array the keys would be preserved.
215 | * And for sequential array the index would be rebase.
216 | */
217 | if ($isAssoc)
218 | {
219 | if (\is_numeric($itemRightKey))
220 | {
221 | $sorted[] = $itemRight;
222 | }
223 | else
224 | {
225 | $sorted[$itemRightKey] = $itemRight;
226 | }
227 | }
228 | else
229 | {
230 | $sorted->push($itemRight);
231 | }
232 |
233 | ++$rightIndex;
234 | }
235 | }
236 |
237 | /**
238 | * Concat the remaining values of the left Array and right Array
239 | * after merging two arrays.
240 | */
241 | return $sorted->concat(
242 | $leftArray->slice($leftIndex),
243 | $rightArray->slice($rightIndex)
244 | );
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/tests/unit/arrays/ArrayBasicsTest.php:
--------------------------------------------------------------------------------
1 | 1, 'two' => 2, 'three' => 3, 4 => 40], [0, 1, -1, 4], [null, null, null, null]]
15 | ];
16 | }
17 |
18 | public function pushDataProvider()
19 | {
20 | return [
21 | [[], [1], 1],
22 | [[1, 2 ,3], [4], 4],
23 | [['Jan', 'Feb', 'Mar'], ['Apr', 'May'], 5],
24 | [['one' => 1, 'two' => 2, 'three' => 3], [4, 5, 6], 6],
25 | [['a', 'b', 'c', 'd'], [], 4],
26 | [[1, 2, 3], [], 3]
27 | ];
28 | }
29 |
30 | public function popDataProvider()
31 | {
32 | return [
33 | [[], null, []],
34 | [[1, 2 ,3], 3, [1, 2]],
35 | [['Jan', 'Feb', 'Mar'], 'Mar', ['Jan', 'Feb']],
36 | [['one' => 1, 'two' => 2, 'three' => 3], 3, ['one' => 1, 'two' => 2]],
37 | [['one' => 1, 'two' => 2, 'three' => 3, 4, 5, 6], 6, ['one' => 1, 'two' => 2, 'three' => 3, 4, 5]]
38 | ];
39 | }
40 |
41 | public function shiftDataProvider()
42 | {
43 | return [
44 | [[], null, []],
45 | [[1, 2 ,3], 1, [2, 3]],
46 | [['Jan', 'Feb', 'Mar'], 'Jan', ['Feb', 'Mar']],
47 | [['one' => 1, 'two' => 2, 'three' => 3], 1, ['two' => 2, 'three' => 3]],
48 | [[4, 5, 6, 'one' => 1, 'two' => 2, 'three' => 3], 4, [5, 6, 'one' => 1, 'two' => 2, 'three' => 3]],
49 | [['one' => 1, 'two' => 2, 'three' => 3, 4, 5, 6], 1, ['two' => 2, 'three' => 3, 4, 5, 6]],
50 | [['one' => 1, 'two' => 2, 3, 4, 5, 'six' => 6], 1, ['two' => 2, 3, 4, 5, 'six' => 6]]
51 | ];
52 | }
53 |
54 | public function unshiftDataProvider()
55 | {
56 | return [
57 | [[], [1], 1, [1]],
58 | [[2, 3], [1], 3, [1, 2, 3]],
59 | [[2, 3], [0, 1], 4, [0, 1, 2, 3]],
60 | [['Jan', 'Feb', 'Mar'], [''], 4, ['', 'Jan', 'Feb', 'Mar']],
61 | [['Jan', 'Feb', 'Mar'], [' '], 4, [' ', 'Jan', 'Feb', 'Mar']],
62 | [['one' => 1, 'two' => 2, 'three' => 3], [0], 4, [0, 'one' => 1, 'two' => 2, 'three' => 3]],
63 | [[4, 5, 6, 'one' => 1, 'two' => 2, 'three' => 3], [0, 4], 8, [0, 4, 4, 5, 6, 'one' => 1, 'two' => 2, 'three' => 3]],
64 | [['1' => 1, 'two' => 2, 'three' => 3, 4, 5, 6], [-3, -2, -1, 0], 10, [-3, -2, -1, 0, '4' => 1, 'two' => 2, 'three' => 3, 4, 5, 6]],
65 | [['one' => 1, 'two' => 2, 3, 4, 5, 'six' => 6], [0, 0, 0], 9, [0, 0, 0, 'one' => 1, 'two' => 2, 3, 4, 5, 'six' => 6]]
66 | ];
67 | }
68 |
69 | public function joiningDataProvider()
70 | {
71 | return [
72 | [[], ',', ''],
73 | [[2, 3], '+', '2+3'],
74 | [[2, 3], ', ', '2, 3'],
75 | [[2, 3], ' ,', '2 ,3'],
76 | [['Jan', 'Feb', 'Mar'], ', ', 'Jan, Feb, Mar'],
77 | [['Jan', 'Feb', 'Mar'], ' + ', 'Jan + Feb + Mar'],
78 | [['one' => 1, 'two' => 2, 'three' => 3], null, '1,2,3'],
79 | [['one' => 1, 'two' => 2, 'three' => 3], ', ', '1, 2, 3'],
80 | [[4, 5, 6, 'one' => 1, 'two' => 2, 'three' => 3], ' + ', '4 + 5 + 6 + 1 + 2 + 3'],
81 | [[-3, -2, -1, 0, '1' => 1, 'two' => 2, 'three' => 3, 4, 5, 6], null, '-3,1,-1,0,2,3,4,5,6'],
82 | [[1, 2, 3, [4, 5, 6], 7, 8], null, '1,2,3,4,5,6,7,8'],
83 | [[1, 2, 3, [4, 5, 6], 7, 8], ' + ', '1 + 2 + 3 + 4,5,6 + 7 + 8'],
84 | [[[1, 2, 3], [4, 5, 6], [7, 8]], ' + ', '1,2,3 + 4,5,6 + 7,8'],
85 | ];
86 | }
87 |
88 | public function sliceDataProvider()
89 | {
90 | return [
91 | [[], null, null, []],
92 | [[], 0, -1, []],
93 | [[], 0, 10, []],
94 | [[], -10, null, []],
95 | [[1, 2, 3], null, null, [1, 2, 3]],
96 | [[1, 2, 3], 0, 1, [1]],
97 | [[1, 2, 3, 4, 5], 2, null, [3, 4, 5]],
98 | [[1, 2, 3, 4, 5], 2, 4, [3, 4]],
99 | [[1, 2, 3, 4, 5, 6, 7], -1, null, [7]],
100 | [[1, 2, 3, 4, 5, 6, 7], 0, -1, [1, 2, 3, 4, 5, 6]],
101 | [[1, 2, 3, 4, 5, 6, 7], -1, -2, []],
102 | [[1, 2, 3, 4, 5, 6, 7], 0, -8, []],
103 | [[1, 2, 3, 4, 5, 6, 7], 6, null, [7]],
104 | [[1, 2, 3, 4, 5, 6, 7], 7, null, []],
105 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4], null, null, ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]],
106 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4], 0, -1, ['one' => 1, 'two' => 2, 'three' => 3]],
107 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4], 0, -5, []],
108 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4], -5, -5, []],
109 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4], -5, null, ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]],
110 | [['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six' => 6], 2, 4, [3, 4]],
111 | [['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six' => 6], null, null, ['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six' => 6]],
112 | [['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six' => 6], -4, -2, [3, 4]],
113 | ];
114 | }
115 |
116 | /**
117 | * data, (start, end), pushValue, result, remaining
118 | * The odd arrays are without insertion and the even
119 | * ones with insertion.
120 | */
121 | public function spliceDataProvider()
122 | {
123 | return [
124 | [
125 | [], [null, null], [], [], []
126 | ],
127 | [
128 | [], [null, null], [11], [], [11]
129 | ],
130 | [
131 | [], [0, -1], [], [], []
132 | ],
133 | [
134 | [], [0, -1], [10, 20], [], [10, 20]
135 | ],
136 | [
137 | [], [0, 10], [], [], []
138 | ],
139 | [
140 | [], [0, 10], ['a', 'b', 'c'], [], ['a', 'b', 'c']
141 | ],
142 | [
143 | [], [-10, null], [], [], []
144 | ],
145 | [
146 | [], [-10, null], [1, 2, 3], [], [1, 2, 3]
147 | ],
148 | [
149 | [1, 2, 3], [null, null],[], [], [1, 2, 3]
150 | ],
151 | [
152 | [1, 2, 3], [null, null],[11], [], [11, 1, 2, 3]
153 | ],
154 | [
155 | [1, 2, 3], [0, null], [], [1, 2, 3], []
156 | ],
157 | [
158 | [1, 2, 3], [0, null], ['x', 'y', 5], [], ['x', 'y', 5, 1, 2, 3]
159 | ],
160 | [
161 | [1, 2, 3, 4, 5], [2, null], [], [3, 4, 5], [1, 2]
162 | ],
163 | [
164 | [1, 2, 3, 4, 5], [2, null], ['x', 'y'], [], [1, 2, 'x', 'y', 3, 4, 5]
165 | ],
166 | [
167 | [1, 2, 3, 4, 5], [2, 4], [], [3, 4, 5], [1, 2]
168 | ],
169 | [
170 | [1, 2, 3, 4, 5], [2, 4], ['x', 'y'], [3, 4, 5], [1, 2, 'x', 'y']
171 | ],
172 | [
173 | [1, 2, 3, 4, 5, 6, 7], [-1, null], [], [7], [1, 2, 3, 4, 5, 6]
174 | ],
175 | [
176 | [1, 2, 3, 4, 5, 6, 7], [-1, null], ['x', 'y'], [], [1, 2, 3, 4, 5, 6, 'x', 'y', 7]
177 | ],
178 | [
179 | [1, 2, 3, 4, 5, 6, 7], [0, -1], ['a', 'b'], [], ['a', 'b', 1, 2, 3, 4, 5, 6, 7]
180 | ],
181 | [
182 | [1, 2, 3, 4, 5, 6, 7], [-1, 0], [], [], [1, 2, 3, 4, 5, 6, 7]
183 | ],
184 | [
185 | [1, 2, 3, 4, 5, 6, 7], [-1, 0], ['a', 'b'], [], [1, 2, 3, 4, 5, 6, 'a', 'b', 7]
186 | ],
187 | [
188 | [1, 2, 3, 4, 5, 6, 7], [-8, 1], [], [1], [2, 3, 4, 5, 6, 7]
189 | ],
190 | [
191 | [1, 2, 3, 4, 5, 6, 7], [-8, 1], ['x', 'y'], [1], ['x', 'y', 2, 3, 4, 5, 6, 7]
192 | ],
193 | [
194 | [1, 2, 3, 4, 5, 6, 7], [6, null], [], [7], [1, 2, 3, 4, 5, 6]
195 | ],
196 | [
197 | [1, 2, 3, 4, 5, 6, 7], [6, null], ['x', 'y'], [], [1, 2, 3, 4, 5, 6, 'x', 'y', 7]
198 | ],
199 | [
200 | [1, 2, 3, 4, 5, 6, 7], [7, null], [], [], [1, 2, 3, 4, 5, 6, 7]
201 | ],
202 | [
203 | [1, 2, 3, 4, 5, 6, 7], [7, null], ['a'], [], [1, 2, 3, 4, 5, 6, 7, 'a']
204 | ],
205 | [
206 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
207 | [null, null],
208 | [],
209 | [],
210 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]
211 | ],
212 | [
213 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
214 | [null, null],
215 | ['x', 'y'],
216 | [],
217 | ['x', 'y', 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]
218 | ],
219 | [
220 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
221 | [0, -1],
222 | [],
223 | [],
224 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]
225 | ],
226 | [
227 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
228 | [0, -1],
229 | ['x', 'y'],
230 | [],
231 | ['x', 'y', 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]
232 | ],
233 | [
234 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
235 | [0, 4],
236 | [],
237 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
238 | []
239 | ],
240 | [
241 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
242 | [0, 4],
243 | ['a', 'b', 10],
244 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
245 | ['a', 'b', 10]
246 | ],
247 | [
248 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
249 | [-2, 1],
250 | [],
251 | ['three' => 3],
252 | ['one' => 1, 'two' => 2, 'four' => 4]
253 | ],
254 | [
255 | ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4],
256 | [-2, 1],
257 | ['three'],
258 | ['three' => 3],
259 | ['one' => 1, 'two' => 2, 'three', 'four' => 4]
260 | ],
261 | [
262 | ['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six' => 6],
263 | [2, 4],
264 | [],
265 | [3, 4, 'five' => 5, 'six' => 6],
266 | ['one' => 1, 'two' => 2]
267 | ],
268 | [
269 | ['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six' => 6],
270 | [2, 4],
271 | [100, 200],
272 | [3, 4, 'five' => 5, 'six' => 6],
273 | ['one' => 1, 'two' => 2, 100, 200]
274 | ]
275 | ];
276 | }
277 |
278 | public function concatDataProvider()
279 | {
280 | return [
281 | [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]],
282 | [1, 2, 3, [1, 2, 3]],
283 | [[1, 2, 3, [4, 5]], [6, 7], [1, 2, 3, [4, 5], 6, 7]],
284 | [['one' => 1, 'two' => 2], ['three' => 3, 'four' => 4], ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]],
285 | [['one' => 1, 'two' => 2], ['two' => 2, 'three' => 3, 'four' => 4], ['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]],
286 | [['one' => 1, 2, 'three' => 3], [4, 5, 'six' => 6], ['one' => 1, '0' => 2, 'three' => 3, '1' => 4, '2' => 5, 'six' => 6]]
287 | ];
288 | }
289 |
290 | /** ------------- Test Functions ------------- */
291 | /**
292 | * @dataProvider atDataProvider()
293 | */
294 | public function testAt($data, $indics, $result)
295 | {
296 | $array = new JsArray($data);
297 |
298 | foreach ($indics as $key => $index)
299 | {
300 | $value = $result[$key];
301 |
302 | if ($value === 'OutOfRangeException')
303 | {
304 | $this->expectException(OutOfRangeException::class);
305 | $this->expectExceptionMessage(\sprintf('The provided index %d is out of range.', $index));
306 | $array->at($index);
307 | }
308 | else
309 | {
310 | $this->assertEquals($value, $array->at($index));
311 | }
312 | }
313 |
314 | $this->expectException(ArgumentCountError::class);
315 | $array->at();
316 | }
317 |
318 | /**
319 | * @dataProvider pushDataProvider()
320 | */
321 | public function testPush($data, $push, $result)
322 | {
323 | $array = new JsArray($data);
324 |
325 | if (count($push) > 0)
326 | {
327 | $this->assertEquals($result, $array->push(...$push));
328 | }
329 | else
330 | {
331 | $this->assertEquals($result, $array->push());
332 | }
333 | }
334 |
335 | /**
336 | * @dataProvider popDataProvider()
337 | */
338 | public function testPop($data, $result, $remaining)
339 | {
340 | $array = new JsArray($data);
341 |
342 | $this->assertEquals($result, $array->pop());
343 | $this->assertEquals($array->get(), $remaining);
344 | }
345 |
346 | /**
347 | * @dataProvider shiftDataProvider()
348 | */
349 | public function testShift($data, $result, $remaining)
350 | {
351 | $array = new JsArray($data);
352 |
353 | $this->assertEquals($result, $array->shift());
354 | $this->assertEquals($array->get(), $remaining);
355 | }
356 |
357 | /**
358 | * @dataProvider unshiftDataProvider()
359 | */
360 | public function testJsArrayUnshift($data, $props, $result, $remaining)
361 | {
362 | $array = new JsArray($data);
363 |
364 | $this->assertEquals($result, $array->unshift(...$props));
365 | $this->assertEquals($array->get(), $remaining);
366 | }
367 |
368 | /**
369 | * @dataProvider joiningDataProvider()
370 | */
371 | public function testJsArrayJoin($data, $separator, $result)
372 | {
373 | $array = new JsArray($data);
374 |
375 | if (\is_null($separator))
376 | {
377 | $this->assertEquals($result, $array->join());
378 | }
379 | else
380 | {
381 | $this->assertEquals($result, $array->join($separator));
382 | }
383 | }
384 |
385 | /**
386 | * @dataProvider sliceDataProvider()
387 | */
388 | public function testJsArraySlice($data, $start, $end, $result)
389 | {
390 | $array = new JsArray($data);
391 |
392 | if ($start === null)
393 | {
394 | $this->assertEquals($result, $array->slice()->get());
395 | }
396 | elseif ($start !== null && $end === null)
397 | {
398 | $this->assertEquals($result, $array->slice($start)->get());
399 | }
400 | elseif ($start !== null && $end !== null)
401 | {
402 | $this->assertEquals($result, $array->slice($start, $end)->get());
403 | }
404 | }
405 |
406 | /**
407 | * @dataProvider spliceDataProvider()
408 | */
409 | public function testJsArraySplice($data, $props, $pushValues, $result, $remaining)
410 | {
411 | $array = new JsArray($data);
412 |
413 | if (!empty($pushValues))
414 | {
415 | $this->assertEquals((new JsArray($result)), $array->splice(...$props, ...$pushValues));
416 | $this->assertEquals((new JsArray($remaining)), $array);
417 | }
418 | else
419 | {
420 | $this->assertEquals((new JsArray($result)), $array->splice(...$props));
421 | $this->assertEquals((new JsArray($remaining)), $array);
422 | }
423 | }
424 |
425 | /**
426 | * @dataProvider concatDataProvider()
427 | */
428 | public function testJsArrayConcat(...$data)
429 | {
430 | $dataArray = new JsArray($data);
431 | $array = new JsArray([]);
432 | $result = new JsArray($data[count($data) - 1]);
433 | $array = $array->concat(...$dataArray->slice(count($data) - 1)->get());
434 |
435 | $this->assertEquals($result, $array);
436 | }
437 | }
438 |
--------------------------------------------------------------------------------
/tests/unit/arrays/ArrayConditionalTest.php:
--------------------------------------------------------------------------------
1 | 0', false],
15 | [[0.5, .23, 4, 0.000000002, 1e5, 0.0], '\is_numeric($item)', true],
16 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5], 'strlen($key) >= 3', true],
17 | [['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six', 'seven', 8.0], '\is_numeric($key)', false],
18 | ];
19 | }
20 |
21 | public function conditionalSomeDataProvider()
22 | {
23 | return [
24 | [[1, 2, 3, 4, 5], '$item === 1', true],
25 | [['Jan', 'Feb', 'Mar', 'Apr'], 'strlen($item) > 3', false],
26 | [['Jan', 'Feb', 'Mar', 'April'], 'strlen($item) > 3', true],
27 | [[-1, 0, 10, 20, -100, 2, 5], '\is_numeric($item)', true],
28 | [[-1, 0, 10, 20, -100, 2, 5], '!\is_numeric($item)', false],
29 | [[-1, 0, 10, 20, -100, 2, 5], '$item > 0', true],
30 | [[0.5, .23, 4, 0.000000002, 1e5, 0.0], '\is_numeric($item)', true],
31 | [['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5], 'strlen($key) === 3', true],
32 | [['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 'six', 'seven', 8.0], '\is_numeric($key)', true],
33 | ];
34 | }
35 |
36 | /**
37 | * @dataProvider conditionalDataProvider()
38 | */
39 | public function testJsArrayEvery($data, $condition, $result)
40 | {
41 | $array = new JsArray($data);
42 |
43 | $isEvery = $array->every(
44 | function ($item, $index, $key) use ($condition) {
45 | return eval("return " . $condition . ";");
46 | }
47 | );
48 |
49 | if ($result)
50 | {
51 | $this->assertTrue($isEvery);
52 | }
53 | else
54 | {
55 | $this->assertFalse($isEvery);
56 | }
57 | }
58 |
59 | /**
60 | * @dataProvider conditionalSomeDataProvider()
61 | */
62 | public function testJsArraySome($data, $condition, $result)
63 | {
64 | $array = new JsArray($data);
65 |
66 | $isEvery = $array->some(
67 | function ($item, $index, $key) use ($condition) {
68 | return eval("return " . $condition . ";");
69 | }
70 | );
71 |
72 | if ($result)
73 | {
74 | $this->assertTrue($isEvery);
75 | }
76 | else
77 | {
78 | $this->assertFalse($isEvery);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/unit/arrays/ArrayIteratorTest.php:
--------------------------------------------------------------------------------
1 | forEach(
14 | function ($item, $index) {
15 | $this->assertEquals($index + 1, $item);
16 |
17 | if ($index % 2 === 1)
18 | {
19 | $this->assertEquals($item % 2, 0);
20 | }
21 | }
22 | );
23 |
24 | $data = [[1, 2, 3], [3, 4, 5], [5, 6, 7]];
25 | $array = new JsArray($data);
26 |
27 | $array->forEach(
28 | function ($item, $index) {
29 | $this->assertInstanceOf(JsArray::class, $item);
30 | $this->assertIsInt($item[0]);
31 | }
32 | );
33 | }
34 |
35 | public function testJsArrayMap()
36 | {
37 | $array = new JsArray([-1, 0, 1, 2, -5, 3]);
38 |
39 | $mapped = $array->map(
40 | function ($item) {
41 | return $item * $item;
42 | }
43 | );
44 |
45 | $this->assertEquals($mapped->get(), [1, 0, 1, 4, 25, 9]);
46 | }
47 |
48 | public function testJsArrayFlatMap()
49 | {
50 | $array = new JsArray([-1, 0, 1, 2, -5, 3]);
51 | $flatten = $array->flatMap(function($item) {
52 | return [$item * 2];
53 | });
54 |
55 | $this->assertEquals([-2, 0, 2, 4, -10, 6], $flatten->get());
56 | }
57 |
58 | public function testJsArrayFilter()
59 | {
60 | $array = new JsArray([1, 2, 3, 4, 5, 6]);
61 | $filtered = $array->filter(
62 | function ($item) {
63 | return $item % 2 === 0;
64 | }
65 | );
66 |
67 | $this->assertEquals((new JsArray([2, 4, 6])), $filtered);
68 |
69 | $array = new JsArray(['one' => 1, 'two' => 2, 'three' => 3, 'four' => 4]);
70 | $filtered = $array->filter(
71 | function ($item, $index, $key) {
72 | return strlen($key) >= 4;
73 | }
74 | );
75 |
76 | $this->assertEquals((new JsArray(['four' => 4, 'three' => 3])), $filtered);
77 |
78 | $array = new JsArray(['one' => 1, 'two' => 2, 3, 4, 'five' => 5, 6]);
79 | $filtered = $array->filter(
80 | function ($item) {
81 | return $item > 1;
82 | }
83 | );
84 |
85 | $this->assertEquals((new JsArray(['two' => 2, 3, 4, 'five' => 5, 6])), $filtered);
86 | }
87 | }
--------------------------------------------------------------------------------
/tests/unit/arrays/ArrayModifierTest.php:
--------------------------------------------------------------------------------
1 | 1, 'two' => 2, 'three' => 3], ['three' => 3, 'two' => 2, 'one' => 1]],
28 | [['one' => 1, 'two', 'three' => 3, 4, 5], [5, 4, 'three' => 3, 'two', 'one' => 1]]
29 | ];
30 | }
31 |
32 | public function flatDataProvider()
33 | {
34 | return [
35 | [[1, 2, 3, 4], [1, 2, 3, 4], PHP_INT_MAX],
36 | [[[1, 2, [3, 4]]], [1, 2, 3, 4], PHP_INT_MAX],
37 | [[[1, 2], [3, [4, [5, [6, [7]]]]]], [1, 2, 3, 4, 5, 6, 7], PHP_INT_MAX],
38 | [[[1, 2], [3, 4], [5, 6]], [1, 2, 3, 4, 5, 6], PHP_INT_MAX],
39 | [['one' => 1, 'two' => 2, ['three' => 3]], 'error', PHP_INT_MAX]
40 | ];
41 | }
42 |
43 | /**
44 | * @dataProvider fillDataProvider()
45 | */
46 | public function testModifierFill($data, $value, $range, $result)
47 | {
48 | $array = new JsArray($data);
49 | $array->fill($value, ...$range);
50 | $this->assertEquals((new JsArray($result)), $array);
51 | }
52 |
53 | /**
54 | * @dataProvider reverseDataProvider()
55 | */
56 | public function testReverse($data, $result)
57 | {
58 | $array = new JsArray($data);
59 |
60 | $this->assertEquals((new JsArray($result)), $array->reverse());
61 | }
62 |
63 | /**
64 | * @dataProvider flatDataProvider()
65 | */
66 | public function testFlatArray($data, $result, $depth)
67 | {
68 | $array = new JsArray($data);
69 |
70 | if (\is_array($result))
71 | {
72 | $this->assertEquals($result, $array->flat($depth)->get());
73 | }
74 | elseif (\is_string($result) && $result === 'error')
75 | {
76 | $this->expectException(\UnexpectedValueException::class);
77 | $this->expectExceptionMessage('Flatten could not be applied to an associative array. Please use it in sequential array.');
78 | $this->assertEquals($result, $array->flat($depth));
79 | }
80 |
81 | }
82 | }
--------------------------------------------------------------------------------
/tests/unit/arrays/JsArrayTest.php:
--------------------------------------------------------------------------------
1 | 1, 'two' => 2, 'three' => 3], 3]
17 | ];
18 | }
19 |
20 | public function arrayKeysProvider()
21 | {
22 | return [
23 | [[1, 2, 3, 4, 5], [0, 1, 2, 3, 4]],
24 | [['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], [0, 1, 2, 3, 4, 5]],
25 | [[], []],
26 | [['one' => 1, 'two' => 2, 'three' => 3], ['one', 'two', 'three']],
27 | [['one' => 1, 'two' => 2, 'three' => 3, 4, 5, 'six' => 6, 'seven' => 7], ['one', 'two', 'three', 0, 1, 'six', 'seven']],
28 | ];
29 | }
30 |
31 | public function arrayValuesProvider()
32 | {
33 | return [
34 | [[1, 2, 3, 4, 5], [1, 2, 3, 4, 5]],
35 | [['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']],
36 | [[], []],
37 | [['one' => 1, 'two' => 2, 'three' => 3], [1, 2, 3]],
38 | [['one' => 1, 'two' => 2, 'three' => 3, 4, 5, 'six' => 6, 'seven' => 7], [1, 2, 3, 4, 5, 6, 7]],
39 | ];
40 | }
41 |
42 | public function assocArrayProvider()
43 | {
44 | return [
45 | [[1, 2, 3, 4, 5], false],
46 | [['Jan', 'Feb', 'Mar', 'Apr'], false],
47 | [[], false],
48 | [['one' => 1, 'two' => 2, 'three' => 3], true],
49 | [['one' => 1, 'two' => 2, 'three' => 3, 4, 5, 'six' => 6, 'seven' => 7], true],
50 | [['1' => 'Earth', '4' => 'Jupiter', '2' => 'Mars', '3' => 'Sun'], false],
51 | [['days' => [1, 2, 3], 'months' => ['Jan', 'Feb', 'Mar'], 'year' => [2020, 2019, 2018]], true]
52 | ];
53 | }
54 |
55 | public function setOffsetProvider()
56 | {
57 | return [
58 | [[], null, 5],
59 | [[1, 2, 3], 3, 10],
60 | [['name' => 'John Doe', 'email' => 'jon@doe.com'], 'age', 24],
61 | [['Jan', 'Feb', 'Mar'], null, 'Apr']
62 | ];
63 | }
64 |
65 | /** --------------------Test Functions from here-------------------- */
66 |
67 | public function testBindIsAnInstanceOfJsArray()
68 | {
69 | $array = new JsArray;
70 |
71 | $this->assertInstanceOf(JsArray::class, $array->bind([1, 2, 3, 4, 5]));
72 | }
73 |
74 | public function testBindMakesANewInstanceIfMakeMutableIsFalseOtherwiseItMutateTheCurrentInstance()
75 | {
76 | $array = new JsArray;
77 |
78 | $this->assertSame($array, $array->bind([1, 2, 3, 4, 5]));
79 | $this->assertNotSame($array, $array->bind([1, 2, 3, 4, 5], false));
80 | }
81 |
82 | /**
83 | * @dataProvider lengthProvider()
84 | */
85 | public function testLengthProperty($data, $result)
86 | {
87 | $array = new JsArray($data);
88 | $this->assertEquals($result, $array->length);
89 | }
90 |
91 | /**
92 | * @dataProvider lengthProvider()
93 | */
94 | public function testLengthMethod($data, $result)
95 | {
96 | $array = new JsArray($data);
97 | $this->assertEquals($result, $array->length());
98 | }
99 |
100 | /**
101 | * @dataProvider arrayKeysProvider()
102 | */
103 | public function testArrayKeys($data, $result)
104 | {
105 | $array = new JsArray($data);
106 | $this->assertEquals($result, $array->keys()->get());
107 | }
108 |
109 | /**
110 | * @dataProvider arrayValuesProvider()()
111 | */
112 | public function testArrayValues($data, $result)
113 | {
114 | $array = new JsArray($data);
115 | $this->assertEquals($result, $array->values());
116 | }
117 |
118 | /**
119 | * @dataProvider assocArrayProvider()
120 | */
121 | public function testIsAssociativeArray($data, $result)
122 | {
123 | $array = new JsArray($data);
124 |
125 | if ($result)
126 | {
127 | $this->assertTrue(JsArray::isAssociativeArray($data));
128 | $this->assertTrue(JsArray::isAssociativeArray($array));
129 | }
130 | else
131 | {
132 | $this->assertFalse(JsArray::isAssociativeArray($data));
133 | $this->assertFalse(JsArray::isAssociativeArray($array));
134 | }
135 | }
136 |
137 | /**
138 | * @dataProvider assocArrayProvider()
139 | */
140 | public function testIsArray($data)
141 | {
142 | $array = new JsArray($data);
143 | $this->assertTrue(JsArray::isArray($data));
144 | $this->assertTrue(JsArray::isArray($array));
145 | }
146 |
147 | public function testArrayIsIteratable()
148 | {
149 | $array = new JsArray([1, 2, 3, 4, 5]);
150 |
151 | $this->assertInstanceOf(\Traversable::class, $array);
152 | }
153 |
154 | public function testOffsetExistsReturnsBoolean()
155 | {
156 | $array = new JsArray(['one' => 1, 'two' => 2, 'three' => 3]);
157 |
158 | $this->assertIsBool(isset($array['one']));
159 | $this->assertIsBool(isset($array['four']));
160 | }
161 | /**
162 | * @dataProvider setOffsetProvider()
163 | */
164 | public function testOffsetSet($data, $key, $value)
165 | {
166 | $array = new JsArray($data);
167 |
168 | if (\is_null($key))
169 | {
170 | $array[] = $value;
171 | $key = $array->length - 1;
172 | }
173 | else
174 | {
175 | $array[$key] = $value;
176 | }
177 |
178 | $this->assertEquals($array[$key], $value);
179 | }
180 |
181 | public function testOffsetGet()
182 | {
183 | $array = new JsArray(['first_name' => 'Jon', 'last_name' => 'Snow', 'email' => 'jon@winterfell.com', 'age' => 34]);
184 |
185 | $this->assertEquals($array['first_name'], 'Jon');
186 | $this->assertEquals($array['email'], 'jon@winterfell.com');
187 | }
188 |
189 | public function testOffsetGetUndefinedIndex()
190 | {
191 | $array = new JsArray(['first_name' => 'Jon', 'last_name' => 'Snow', 'email' => 'jon@winterfell.com', 'age' => 34]);
192 |
193 | $this->expectNotice();
194 | $this->expectNoticeMessage('Undefined index: five');
195 | $value = $array['five'];
196 | }
197 |
198 | /**
199 | * @dataProvider lengthProvider()
200 | */
201 | public function testCountInstance($data, $result)
202 | {
203 | $array = new JsArray($data);
204 |
205 | $this->assertEquals(count($array), $result);
206 | }
207 |
208 | /**
209 | *
210 | */
211 | public function testArrayFrom()
212 | {
213 | $array = JsArray::from([1, 2, 3]);
214 | $this->assertEquals([1, 2, 3], $array->get());
215 |
216 | $array = JsArray::from(['length' => 5]);
217 | $this->assertEquals([null, null, null, null, null], $array->get());
218 |
219 | $array = JsArray::from(['length' => 4], function($_, $index) {
220 | return $index;
221 | });
222 | $this->assertEquals([0, 1, 2, 3], $array->get());
223 |
224 | $array = JsArray::from([1, 2, 3, 4, 5], function($item) {
225 | return $item * $item;
226 | });
227 | $this->assertEquals([1, 4, 9, 16, 25], $array->get());
228 |
229 | $array = JsArray::from(['name' => 'John Doe', 'age' => 24]);
230 | $this->assertEquals([], $array->get());
231 |
232 | $array = JsArray::from('I love to play dota 2');
233 | $this->assertEquals(["I", " ", "l", "o", "v", "e", " ", "t", "o", " ", "p", "l", "a", "y", " ", "d", "o", "t", "a", " ", "2"], $array->get());
234 |
235 | $array = JsArray::from(9);
236 | $this->assertEquals([], $array->get());
237 |
238 | $array = JsArray::from(5, function($x, $i) {return $i;});
239 | $this->assertEquals([], $array->get());
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/tests/unit/objects/ObjectStaticTest.php:
--------------------------------------------------------------------------------
1 | 1, 'two' => 2, 'three' => 3], ['one' => 1, 'two' => 2, 'three' => 3]],
15 | [['four' => 4, 'five' => 5], ['one' => 1, 'two' => 2, 'three' => 3], ['four' => 4, 'five' => 5, 'one' => 1, 'two' => 2, 'three' => 3]],
16 | [['one' => 4, 'five' => 5], ['one' => 1, 'two' => 2, 'three' => 3], ['one' => 1, 'five' => 5, 'two' => 2, 'three' => 3]],
17 | [['one' => 1, 2, 3], [5, 6, 7], ['one' => 1, 5, 6, 7]]
18 | ];
19 | }
20 |
21 | public function objectFromEntriesProvider()
22 | {
23 | return [
24 | [[[1, 2], [3, 4]], new JsObject([1 => 2, 3 => 4])],
25 | [[['name', 'john doe'], ['age', 24]], new JsObject(['name' => 'john doe', 'age' => 24])],
26 | [[], new JsObject()]
27 | ];
28 | }
29 |
30 | public function objectKeysProvider()
31 | {
32 | $stdObj = new stdClass;
33 | $stdObj->name = 'John Doe';
34 | $stdObj->email = 'john@example.com';
35 | $stdObj->age = 24;
36 |
37 | return [
38 | [[], []],
39 | [[1, 2 ,3], [0, 1, 2]],
40 | [['day' => 1, 'month' => 6, 'year' => 2020], ['day', 'month', 'year']],
41 | [['one' => 1, 'two' => 2, 'three' => 3], ['one', 'two', 'three']],
42 | [$stdObj, ['name', 'email', 'age']]
43 | ];
44 | }
45 |
46 | public function objectValuesProvider()
47 | {
48 | $stdObj = new stdClass;
49 | $stdObj->name = 'John Doe';
50 | $stdObj->email = 'john@example.com';
51 | $stdObj->age = 24;
52 |
53 | return [
54 | [[], []],
55 | [[1, 2 ,3], [1, 2, 3]],
56 | [['day' => 1, 'month' => 6, 'year' => 2020], [1, 6, 2020]],
57 | [['one' => 1, 'two' => 2, 'three' => 3], [1, 2, 3]],
58 | [$stdObj, ['John Doe', 'john@example.com', 24]]
59 | ];
60 | }
61 |
62 | public function objectEntriesProvider()
63 | {
64 | $stdObj = new stdClass;
65 | $stdObj->name = 'John Doe';
66 | $stdObj->email = 'john@example.com';
67 | $stdObj->age = 24;
68 |
69 | return [
70 | [[], []],
71 | [[1, 2 ,3], [[0, 1], [1, 2], [2, 3]]],
72 | [['day' => 1, 'month' => 6, 'year' => 2020], [['day', 1], ['month', 6], ['year', 2020]]],
73 | [['one' => 1, 'two' => 2, 'three' => 3], [['one', 1], ['two', 2], ['three', 3]]],
74 | [$stdObj, [['name', 'John Doe'], ['email', 'john@example.com'], ['age', 24]]]
75 | ];
76 | }
77 |
78 | /**
79 | * @dataProvider objectAssignProvider()
80 | */
81 | public function testObjectAssign($target, $source, $result)
82 | {
83 | $merged = JsObject::assign($target, $source);
84 | $this->assertEquals(new JsObject($result), $merged);
85 |
86 | $this->expectException(InvalidArgumentException::class);
87 | JsObject::assign([2, 3], 'string');
88 |
89 | $this->expectException(InvalidArgumentException::class);
90 | JsObject::assign('string', [2, 3]);
91 |
92 | $merged = JsObject::assign([1, 2], [3, 4, 5], [5, 4]);
93 | $this->assertEquals(new JsObject([5, 4, 5]), $merged);
94 |
95 | $merged = JsObject::assign(['name' => 'orange'], ['name' => 'apple', 'color' => 'darkred'], ['name' => 'lemon']);
96 | $this->assertEquals(new JsObject(['name' => 'lemon', 'color' => 'darkred']), $merged);
97 |
98 | $object = new \stdClass;
99 | $object->name = new \stdClass;
100 | $object->name->first = 'John';
101 | $object->name->last = 'Doe';
102 | $object->age = 24;
103 |
104 | $merged = JsObject::assign($object, ['name' => 'Alice']);
105 | $this->assertEquals(new JsObject(['name' => 'Alice', 'age' => 24]), $merged);
106 | }
107 |
108 | /**
109 | * @dataProvider objectKeysProvider()
110 | */
111 | public function testObjectKeys($data, $result)
112 | {
113 | $keys = JsObject::keys($data);
114 | $this->assertEquals($keys, (new JsArray($result)));
115 | }
116 |
117 | /**
118 | * @dataProvider objectValuesProvider()
119 | */
120 | public function testObjectValues($data, $result)
121 | {
122 | $values = JsObject::values($data);
123 | $this->assertEquals($values, (new JsArray($result)));
124 | }
125 |
126 | /**
127 | * @dataProvider objectEntriesProvider()
128 | */
129 | public function testObjectEntries($data, $result)
130 | {
131 | $entries = JsObject::entries($data);
132 | $this->assertEquals($entries, (new JsArray($result)));
133 | }
134 |
135 | /**
136 | * @dataProvider objectFromEntriesProvider()
137 | */
138 | public function testObjectFromEntries($data, $result)
139 | {
140 | $object = JsObject::fromEntries($data);
141 | $this->assertEquals($result, $object);
142 | }
143 |
144 | public function testInvalidFromEntries()
145 | {
146 | $obj = new stdClass;
147 | $obj->name = 'john';
148 | $obj->age = 24;
149 |
150 | $arr = [$obj, 'string', 20, null, false];
151 |
152 | foreach ($arr as $v)
153 | {
154 | $this->expectException(InvalidArgumentException::class);
155 | $this->expectExceptionMessage('Invalid entries provided!');
156 | JsObject::fromEntries($v);
157 | }
158 | }
159 | }
--------------------------------------------------------------------------------