├── .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 | ![BannerWithImageAndText](https://user-images.githubusercontent.com/5783354/87884417-03402d80-ca30-11ea-8fdc-5df12ca0e0aa.png) 2 |
3 | 4 | 5 | ![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/ahamed/JsPhp?labelColor=black&color=4ec428) ![GitHub](https://img.shields.io/github/license/ahamed/JsPhp?labelColor=black&color=4fc529) ![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/ahamed/JsPhp/PHP%20Composer/master?labelColor=black) ![GitHub issues](https://img.shields.io/github/issues/ahamed/JsPhp?color=c50a16&labelColor=black) ![GitHub closed issues](https://img.shields.io/github/issues-closed/ahamed/JsPhp?labelColor=black) ![GitHub pull requests](https://img.shields.io/github/issues-pr/ahamed/JsPhp?color=c50a16&labelColor=black) ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed/ahamed/JsPhp?labelColor=black) ![GitHub language count](https://img.shields.io/github/languages/count/ahamed/JsPhp?labelColor=black) ![GitHub top language](https://img.shields.io/github/languages/top/ahamed/JsPhp?labelColor=black) ![GitHub repo size](https://img.shields.io/github/repo-size/ahamed/JsPhp?labelColor=black) ![GitHub All Releases](https://img.shields.io/github/downloads/ahamed/JsPhp/total?labelColor=black) ![GitHub contributors](https://img.shields.io/github/contributors/ahamed/JsPhp?labelColor=black) ![GitHub last commit](https://img.shields.io/github/last-commit/ahamed/JsPhp?labelColor=black) ![GitHub Release Date](https://img.shields.io/github/release-date/ahamed/JsPhp?labelColor=black) ![GitHub release (latest by date including pre-releases)](https://img.shields.io/github/v/release/ahamed/JsPhp?include_prereleases&labelColor=black) ![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/ahamed/JsPhp?labelColor=black) 6 | 7 | ![Packagist Version](https://img.shields.io/packagist/v/ahamed/JsPhp?labelColor=black) ![Packagist Downloads](https://img.shields.io/packagist/dm/ahamed/JsPhp?color=f18d1b&label=Packagist%20Downloads&labelColor=black) ![Packagist Stars](https://img.shields.io/packagist/stars/ahamed/JsPhp?labelColor=black) 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 | } --------------------------------------------------------------------------------