├── src ├── vendor │ ├── composer │ │ ├── installed.json │ │ ├── autoload_namespaces.php │ │ ├── autoload_psr4.php │ │ ├── autoload_classmap.php │ │ ├── installed.php │ │ ├── LICENSE │ │ ├── autoload_static.php │ │ ├── autoload_real.php │ │ ├── InstalledVersions.php │ │ └── ClassLoader.php │ └── autoload.php └── vennv │ └── vapm │ ├── StatusPromise.php │ ├── Settings.php │ ├── AssumptionFailedError.php │ ├── ThreadException.php │ ├── InternetException.php │ ├── DeferredException.php │ ├── FiberManager.php │ ├── GeneratorManager.php │ ├── PromiseResult.php │ ├── utils │ ├── Property.php │ └── Utils.php │ ├── InternetRequestResult.php │ ├── DescriptorSpec.php │ ├── Error.php │ ├── MicroTask.php │ ├── GarbageCollection.php │ ├── Async.php │ ├── StatusThread.php │ ├── io │ └── functions.php │ ├── ChildCoroutine.php │ ├── MacroTask.php │ ├── Mutex.php │ ├── ct │ └── functions.php │ ├── AwaitGroup.php │ ├── ClosureThread.php │ ├── SampleMacro.php │ ├── Channel.php │ ├── Work.php │ ├── Deferred.php │ ├── CoroutineGen.php │ ├── EventLoop.php │ ├── PHPUtils.php │ ├── Internet.php │ ├── Worker.php │ ├── Stream.php │ ├── System.php │ └── Thread.php ├── phpstan.neon.dist ├── some_tests ├── thread-worker │ ├── RunSimpleThread.php │ ├── SimpleThread.php │ ├── AsyncWithThread.php │ ├── TestWorker.php │ └── ChildWorker.php ├── system │ ├── TimeAndEndTime.php │ └── TimeoutAndInterval.php ├── promise-async-await │ ├── SimpleProcessSocket.php │ ├── BetterAsyncAwait.php │ ├── PromiseAll.php │ └── ChainingPromise.php └── coroutines │ └── CoroutineDeferred.php ├── .gitignore ├── README.md ├── .github └── workflows │ └── Jobs.yml └── composer.json /src/vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [], 3 | "dev": true, 4 | "dev-package-names": [] 5 | } 6 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 9 3 | paths: 4 | - src\vennv 5 | checkGenericClassInNonGenericObjectType: false 6 | treatPhpDocTypesAsCertain: false 7 | -------------------------------------------------------------------------------- /some_tests/thread-worker/RunSimpleThread.php: -------------------------------------------------------------------------------- 1 | start(); -------------------------------------------------------------------------------- /src/vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/vennv/vapm'), 10 | ); 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | /vendor/ 3 | 4 | # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control 5 | # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file 6 | # composer.lock 7 | -------------------------------------------------------------------------------- /src/vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /some_tests/thread-worker/SimpleThread.php: -------------------------------------------------------------------------------- 1 | start()); 12 | var_dump($data); 13 | }); 14 | } 15 | 16 | start(); 17 | -------------------------------------------------------------------------------- /some_tests/thread-worker/TestWorker.php: -------------------------------------------------------------------------------- 1 | add(function () : string { 11 | // In this will work with thread. 12 | return 'Hello World! 1'; 13 | }); 14 | 15 | $work->add(function () : string { 16 | // In this will work with thread. 17 | return 'Hello World! 2'; 18 | }); 19 | 20 | $worker = new Worker($work, ['threads' => 1]); 21 | 22 | $worker->run(function (array $result, Worker $worker) : void { 23 | var_dump($result); 24 | $worker->done(); // This will void memory leaks 25 | }); 26 | -------------------------------------------------------------------------------- /some_tests/promise-async-await/SimpleProcessSocket.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'nam/src', 4 | 'pretty_version' => '1.0.0+no-version-set', 5 | 'version' => '1.0.0.0', 6 | 'reference' => NULL, 7 | 'type' => 'library', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'nam/src' => array( 14 | 'pretty_version' => '1.0.0+no-version-set', 15 | 'version' => '1.0.0.0', 16 | 'reference' => NULL, 17 | 'type' => 'library', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | ), 23 | ); 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vapm 2 | - A library support for PHP about Async, Promise, Coroutine, Thread, GreenThread and other non-blocking methods. 3 | - The method is based on Fibers & Generator & Processes, requires you to have php version from >= 8.1 4 | 5 | # Institutes supported by this library 6 | - [VapmExpress](https://github.com/VennDev/VapmExpress) 7 | 8 | # Composer 9 | ```composer require vennv/vapm``` 10 | 11 | # Next update? 12 | - Simply add some other asynchronous features so that this library is as similar to Javascript & Kotlin as possible. 13 | - If you have any features you'd like to contribute or have any ideas, please give me feedback. I will always update this project in the near future. 14 | 15 | # How to use my lib? 16 | - [Click Here](https://venndev.gitbook.io/vapm/) 17 | 18 | # Credits 19 | - Email: pnam5005@gmail.com 20 | - Paypal: lifeboat909@gmail.com 21 | -------------------------------------------------------------------------------- /some_tests/coroutines/CoroutineDeferred.php: -------------------------------------------------------------------------------- 1 | then(fn($data) => [$data, null])->catch(fn($error) => [null, $error]); 30 | } 31 | 32 | getData(); 33 | -------------------------------------------------------------------------------- /some_tests/promise-async-await/PromiseAll.php: -------------------------------------------------------------------------------- 1 | then(function ($values) { 39 | var_dump($values); 40 | }) 41 | ->catch(function ($reason) { 42 | var_dump($reason); 43 | }); 44 | } 45 | 46 | main(); 47 | -------------------------------------------------------------------------------- /.github/workflows/Jobs.yml: -------------------------------------------------------------------------------- 1 | name: PHP Jobs 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | build: 14 | name: Jobs 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | php: 20 | - "8.2" 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | 25 | - name: Setup PHP 26 | uses: shivammathur/setup-php@2.21.1 27 | with: 28 | php-version: ${{ matrix.php }} 29 | 30 | - name: Restore Composer package cache 31 | id: composer-cache 32 | uses: actions/cache@v3 33 | with: 34 | path: "~/.cache/composer" 35 | key: "php-${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }}" 36 | restore-keys: "php-${{ matrix.php }}-composer-" 37 | 38 | - name: Install PHPStan Composer dependencies 39 | run: composer install --prefer-dist --no-interaction --ignore-platform-reqs 40 | 41 | - name: Run PHPStan 42 | run: vendor/bin/phpstan analyze 43 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vennv/vapm", 3 | "keywords": ["vapm", "asynchronous", "Vapm"], 4 | "license": "LGPL-2.0-only", 5 | "version": "2.5.6", 6 | "autoload": { 7 | "psr-4": { 8 | "vennv\\vapm\\": "src/vennv/vapm/" 9 | }, 10 | "files": [ 11 | "src/vennv/vapm/io/functions.php", 12 | "src/vennv/vapm/ct/functions.php" 13 | ] 14 | }, 15 | "authors": [ 16 | { 17 | "name": "VennDev", 18 | "email": "1050080275@sv.hcmunre.edu.vn" 19 | } 20 | ], 21 | "config": { 22 | "allow-plugins": { 23 | "phpstan/extension-installer": true 24 | } 25 | }, 26 | "require": { 27 | "php": ">=8.2", 28 | "php-64bit": "*", 29 | "composer-runtime-api": "^2.0" 30 | }, 31 | "require-dev": { 32 | "phpstan/phpstan": "*" 33 | }, 34 | "scripts": { 35 | "analyse-src": "./vendor/bin/phpstan analyse -c phpstan.neon.dist", 36 | "make-server": [ 37 | "@composer install --no-dev --classmap-authoritative --ignore-platform-reqs", 38 | "@php -dphar.readonly=0 build/server-phar.php" 39 | ] 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /src/vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'vennv\\vapm\\' => 11, 13 | ), 14 | ); 15 | 16 | public static $prefixDirsPsr4 = array ( 17 | 'vennv\\vapm\\' => 18 | array ( 19 | 0 => __DIR__ . '/../..' . '/vennv/vapm', 20 | ), 21 | ); 22 | 23 | public static $classMap = array ( 24 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 25 | ); 26 | 27 | public static function getInitializer(ClassLoader $loader) 28 | { 29 | return \Closure::bind(function () use ($loader) { 30 | $loader->prefixLengthsPsr4 = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$prefixLengthsPsr4; 31 | $loader->prefixDirsPsr4 = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$prefixDirsPsr4; 32 | $loader->classMap = ComposerStaticInitfd8e931fecab81cb0db0789466a481c5::$classMap; 33 | 34 | }, null, ClassLoader::class); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 33 | 34 | return $loader; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/vennv/vapm/StatusPromise.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class StatusPromise 27 | { 28 | 29 | public const PENDING = "pending"; 30 | public const FULFILLED = "fulfilled"; 31 | public const REJECTED = "rejected"; 32 | 33 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Settings.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class Settings 27 | { 28 | 29 | /** 30 | * The time in seconds to check should drop the promise if the promise is not resolved or rejected 31 | * in the specified time. 32 | */ 33 | public const TIME_DROP = 7; 34 | 35 | } -------------------------------------------------------------------------------- /some_tests/thread-worker/ChildWorker.php: -------------------------------------------------------------------------------- 1 | add(function () : string { 12 | // In this will work with thread. 13 | return 'Hello World! 1'; 14 | }); 15 | 16 | $work1->add(function () : string { 17 | // In this will work with thread. 18 | return 'Hello World! 2'; 19 | }); 20 | 21 | $work2->add(function () : string { 22 | // In this will work with thread. 23 | return 'Hello World! 3'; 24 | }); 25 | 26 | $work2->add(function () : string { 27 | // In this will work with thread. 28 | return 'Hello World! 4'; 29 | }); 30 | 31 | $options = ['threads' => 1]; 32 | 33 | $worker = new Worker($work1, $options); 34 | $childWorker = new Worker($work2, $options); 35 | 36 | // This will add a child worker to the main worker. 37 | $worker->addWorker($childWorker, function (array $result, Worker $childWorker) : void { 38 | // Do something with the results of this child worker 39 | $childWorker->done(); // This will void memory leaks 40 | }); 41 | 42 | $worker->run(function (array $result, Worker $worker) : void { 43 | var_dump($result); 44 | $worker->done(); // This will void memory leaks 45 | }); 46 | -------------------------------------------------------------------------------- /src/vennv/vapm/AssumptionFailedError.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class AssumptionFailedError extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/vennv/vapm/ThreadException.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class ThreadException extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/vennv/vapm/InternetException.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class InternetException extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/vennv/vapm/DeferredException.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use TypeError; 27 | 28 | final class DeferredException extends TypeError 29 | { 30 | 31 | public function __construct( 32 | protected string $errorMessage, 33 | protected int $errorCode = 0 34 | ) 35 | { 36 | parent::__construct($this->errorMessage, $this->errorCode); 37 | } 38 | 39 | public function __toString(): string 40 | { 41 | return __CLASS__ . ": [$this->errorCode]: $this->errorMessage\n"; 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/vennv/vapm/FiberManager.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Fiber; 27 | use Throwable; 28 | use function is_null; 29 | 30 | interface FiberManagerInterface 31 | { 32 | 33 | /** 34 | * @throws Throwable 35 | * 36 | * This is a function that waits for the current fiber to finish. 37 | */ 38 | public static function wait(): void; 39 | 40 | } 41 | 42 | final class FiberManager implements FiberManagerInterface 43 | { 44 | 45 | /** 46 | * @throws Throwable 47 | */ 48 | public static function wait(): void 49 | { 50 | if (!is_null(Fiber::getCurrent())) Fiber::suspend(); 51 | } 52 | 53 | } -------------------------------------------------------------------------------- /src/vennv/vapm/GeneratorManager.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | interface GeneratorManagerInterface 27 | { 28 | 29 | /** 30 | * @param int $milliseconds 31 | * @return int 32 | * 33 | * This is a function that calculates the seconds from milliseconds for Generator vapm. 34 | * For example, if you run a function with multiple yields, this calculates the time spent on each of them in seconds. 35 | */ 36 | public static function calculateSeconds(int $milliseconds): int; 37 | 38 | } 39 | 40 | final class GeneratorManager implements GeneratorManagerInterface 41 | { 42 | 43 | public static function calculateSeconds(int $milliseconds): int 44 | { 45 | return $milliseconds * 2000; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/vennv/vapm/PromiseResult.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | interface PromiseResultInterface 27 | { 28 | 29 | public function getStatus(): string; 30 | 31 | public function getResult(): mixed; 32 | 33 | } 34 | 35 | final class PromiseResult implements PromiseResultInterface 36 | { 37 | 38 | private string $status; 39 | 40 | private mixed $result; 41 | 42 | public function __construct(string $status, mixed $result) 43 | { 44 | $this->status = $status; 45 | $this->result = $result; 46 | } 47 | 48 | public function getStatus(): string 49 | { 50 | return $this->status; 51 | } 52 | 53 | public function getResult(): mixed 54 | { 55 | return $this->result; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /src/vennv/vapm/utils/Property.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\utils; 25 | 26 | use Exception; 27 | use function property_exists; 28 | 29 | // This is trait for JsonData|StaticData class 30 | trait Property 31 | { 32 | 33 | /** 34 | * @param object $data 35 | * @param array $options 36 | * @return object 37 | * @throws Exception 38 | */ 39 | public function update(object $data, array $options): object 40 | { 41 | /** 42 | * @var string $key 43 | * @var mixed $value 44 | */ 45 | foreach ($options as $key => $value) { 46 | if (property_exists($data, $key)) $data->{$key} = $value; 47 | /* @phpstan-ignore-line */ 48 | } 49 | 50 | return $data; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /some_tests/promise-async-await/ChainingPromise.php: -------------------------------------------------------------------------------- 1 | then(function ($value) { 53 | var_dump($value); 54 | return testPromise2(); 55 | }) 56 | ->then(function ($value) { 57 | var_dump($value); 58 | return testPromise3(); 59 | }) 60 | ->then(function ($value) { 61 | var_dump($value); 62 | return testPromise4(); 63 | }) 64 | ->then(function ($value) { 65 | var_dump($value); 66 | }) 67 | ->catch(function ($value) { 68 | var_dump($value); 69 | }) 70 | ->finally(function() { 71 | var_dump("Complete!"); 72 | }); 73 | -------------------------------------------------------------------------------- /src/vennv/vapm/InternetRequestResult.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | interface InternetRequestResultInterface 27 | { 28 | 29 | /** 30 | * @return string[][] 31 | */ 32 | public function getHeaders(): array; 33 | 34 | public function getBody(): string; 35 | 36 | public function getCode(): int; 37 | 38 | } 39 | 40 | final class InternetRequestResult implements InternetRequestResultInterface 41 | { 42 | 43 | /** 44 | * @var string[][] $headers 45 | */ 46 | private array $headers; 47 | 48 | private string $body; 49 | 50 | private int $code; 51 | 52 | /** 53 | * @param string[][] $headers 54 | * @param string $body 55 | * @param int $code 56 | */ 57 | public function __construct(array $headers, string $body, int $code) 58 | { 59 | $this->headers = $headers; 60 | $this->body = $body; 61 | $this->code = $code; 62 | } 63 | 64 | /** 65 | * @return string[][] 66 | */ 67 | public function getHeaders(): array 68 | { 69 | return $this->headers; 70 | } 71 | 72 | public function getBody(): string 73 | { 74 | return $this->body; 75 | } 76 | 77 | public function getCode(): int 78 | { 79 | return $this->code; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/vennv/vapm/DescriptorSpec.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class DescriptorSpec 27 | { 28 | 29 | public const BASIC = [ 30 | 0 => ['pipe', 'r'], 31 | 1 => ['pipe', 'w'], 32 | 2 => ['pipe', 'w'] 33 | ]; 34 | 35 | public const IGNORE_STDIN = [ 36 | 0 => ['file', '/dev/null', 'r'], 37 | 1 => ['pipe', 'w'], 38 | 2 => ['pipe', 'w'] 39 | ]; 40 | 41 | public const IGNORE_STDOUT = [ 42 | 0 => ['pipe', 'r'], 43 | 1 => ['file', '/dev/null', 'w'], 44 | 2 => ['pipe', 'w'] 45 | ]; 46 | 47 | public const IGNORE_STDERR = [ 48 | 0 => ['pipe', 'r'], 49 | 1 => ['pipe', 'w'], 50 | 2 => ['file', '/dev/null', 'w'] 51 | ]; 52 | 53 | public const IGNORE_STDOUT_AND_STDERR = [ 54 | 0 => ['pipe', 'r'], 55 | 1 => ['file', '/dev/null', 'w'], 56 | 2 => ['file', '/dev/null', 'w'] 57 | ]; 58 | 59 | public const IGNORE_STDIN_AND_STDERR = [ 60 | 0 => ['file', '/dev/null', 'r'], 61 | 1 => ['pipe', 'w'], 62 | 2 => ['file', '/dev/null', 'w'] 63 | ]; 64 | 65 | public const IGNORE_STDIN_AND_STDOUT = [ 66 | 0 => ['file', '/dev/null', 'r'], 67 | 1 => ['file', '/dev/null', 'w'], 68 | 2 => ['pipe', 'w'] 69 | ]; 70 | 71 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Error.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | final class Error 27 | { 28 | 29 | public const FAILED_IN_FETCHING_DATA = "Error in fetching data"; 30 | 31 | public const WRONG_TYPE_WHEN_USE_CURL_EXEC = "curl_exec() should return string|false when CURL-OPT_RETURN-TRANSFER is set"; 32 | 33 | public const UNABLE_START_THREAD = "Unable to start thread"; 34 | 35 | public const DEFERRED_CALLBACK_MUST_RETURN_GENERATOR = "Deferred callback must return a Generator"; 36 | 37 | public const UNABLE_TO_OPEN_FILE = "Error: Unable to open file!"; 38 | 39 | public const FILE_DOES_NOT_EXIST = "Error: File does not exist!"; 40 | 41 | public const FILE_ALREADY_EXISTS = "Error: File already exists!"; 42 | 43 | public const CANNOT_FIND_FUNCTION_KEYWORD = "Cannot find function or fn keyword in closure"; 44 | 45 | public const CANNOT_READ_FILE = "Cannot read file"; 46 | 47 | public const INPUT_MUST_BE_STRING_OR_CALLABLE = "Input must be string or callable"; 48 | 49 | public const ERROR_TO_CREATE_SOCKET = "Error to create socket"; 50 | 51 | public const PAYLOAD_TOO_LARGE = "Payload too large"; 52 | 53 | public const INVALID_ARRAY = "Invalid array"; 54 | 55 | public const ASYNC_AWAIT_MUST_CALL_IN_ASYNC_FUNCTION = "Async::await() must call in async function"; 56 | 57 | public const CHANNEL_IS_CLOSED = "Channel is closed"; 58 | 59 | } -------------------------------------------------------------------------------- /src/vennv/vapm/MicroTask.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | use Generator; 28 | use function microtime; 29 | 30 | final class MicroTask 31 | { 32 | 33 | /** 34 | * @var array 35 | */ 36 | private static array $tasks = []; 37 | 38 | public static function addTask(int $id, Promise $promise): void 39 | { 40 | self::$tasks[$id] = $promise; 41 | } 42 | 43 | public static function removeTask(int $id): void 44 | { 45 | unset(self::$tasks[$id]); 46 | } 47 | 48 | public static function getTask(int $id): ?Promise 49 | { 50 | return self::$tasks[$id] ?? null; 51 | } 52 | 53 | /** 54 | * @return Generator 55 | */ 56 | public static function getTasks(): Generator 57 | { 58 | foreach (self::$tasks as $id => $promise) yield $id => $promise; 59 | } 60 | 61 | public static function isPrepare(): bool 62 | { 63 | return !empty(self::$tasks); 64 | } 65 | 66 | /** 67 | * @throws Throwable 68 | */ 69 | public static function run(): void 70 | { 71 | $gc = new GarbageCollection(); 72 | foreach (self::getTasks() as $id => $promise) { 73 | /** @var Promise $promise */ 74 | $promise->useCallbacks(); 75 | $promise->setTimeEnd(microtime(true)); 76 | EventLoop::addReturn($promise); 77 | /** @var int $id */ 78 | self::removeTask($id); 79 | $gc->collectWL(); 80 | } 81 | } 82 | 83 | } -------------------------------------------------------------------------------- /src/vennv/vapm/GarbageCollection.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use function gc_collect_cycles; 27 | 28 | interface GarbageCollectionInterface 29 | { 30 | /** 31 | * Set the limit of the loop to collect garbage 32 | * 33 | * @param int $limit 34 | * @return void 35 | */ 36 | public function setLimitLoop(int $limit): void; 37 | 38 | /** 39 | * Collect garbage with limit 40 | * 41 | * @return void 42 | */ 43 | public function collectWL(): void; 44 | 45 | /** 46 | * Collect garbage 47 | * 48 | * @return void 49 | */ 50 | public static function collect(): void; 51 | 52 | } 53 | 54 | final class GarbageCollection implements GarbageCollectionInterface 55 | { 56 | 57 | 58 | private int $countLoop = 0; 59 | 60 | /** 61 | * @param int $limitLoop The limit of the loop to collect garbage 62 | */ 63 | public function __construct(private int $limitLoop = 1000) 64 | { 65 | // TODO: Implement __construct() method. 66 | } 67 | 68 | public function setLimitLoop(int $limit): void 69 | { 70 | $this->limitLoop = $limit; 71 | } 72 | 73 | public function collectWL(): void 74 | { 75 | if ($this->countLoop >= $this->limitLoop) { 76 | gc_collect_cycles(); 77 | $this->countLoop = 0; 78 | } else { 79 | ++$this->countLoop; 80 | } 81 | } 82 | 83 | public static function collect(): void 84 | { 85 | gc_collect_cycles(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/vennv/vapm/Async.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use vennv\vapm\utils\Utils; 27 | use Throwable; 28 | use RuntimeException; 29 | use function is_callable; 30 | 31 | interface AsyncInterface 32 | { 33 | 34 | public function getId(): int; 35 | 36 | /** 37 | * @throws Throwable 38 | */ 39 | public static function await(mixed $await): mixed; 40 | 41 | } 42 | 43 | final class Async implements AsyncInterface 44 | { 45 | 46 | private Promise $promise; 47 | 48 | /** 49 | * @throws Throwable 50 | */ 51 | public function __construct(callable $callback) 52 | { 53 | $promise = new Promise($callback, true); 54 | $this->promise = $promise; 55 | } 56 | 57 | public function getId(): int 58 | { 59 | return $this->promise->getId(); 60 | } 61 | 62 | /** 63 | * @throws Throwable 64 | */ 65 | public static function await(mixed $await): mixed 66 | { 67 | if (!$await instanceof Promise && !$await instanceof Async) { 68 | if (is_callable($await)) { 69 | $await = new Async($await); 70 | } else { 71 | if (!Utils::isClass(Async::class)) { 72 | throw new RuntimeException(Error::ASYNC_AWAIT_MUST_CALL_IN_ASYNC_FUNCTION); 73 | } 74 | return $await; 75 | } 76 | } 77 | 78 | do { 79 | $return = EventLoop::getReturn($await->getId()); 80 | if ($return === null) { 81 | FiberManager::wait(); 82 | } 83 | } while ($return === null); 84 | 85 | return $return->getResult(); 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/vennv/vapm/StatusThread.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use function microtime; 27 | 28 | interface StatusThreadInterface 29 | { 30 | 31 | /** 32 | * @return int|float 33 | * 34 | * This method is used to get the time sleeping. 35 | */ 36 | public function getTimeSleeping(): int|float; 37 | 38 | /** 39 | * @return int|float 40 | * 41 | * This method is used to get the sleep start time. 42 | */ 43 | public function getSleepStartTime(): int|float; 44 | 45 | /** 46 | * @param int|float $seconds 47 | * 48 | * This method is used to sleep the thread. 49 | */ 50 | public function sleep(int|float $seconds): void; 51 | 52 | /** 53 | * @return bool 54 | * 55 | * This method is used to check if the thread can wake up. 56 | */ 57 | public function canWakeUp(): bool; 58 | 59 | } 60 | 61 | final class StatusThread implements StatusThreadInterface 62 | { 63 | 64 | private int|float $timeSleeping = 0; 65 | 66 | private int|float $sleepStartTime; 67 | 68 | public function __construct() 69 | { 70 | $this->sleepStartTime = microtime(true); 71 | } 72 | 73 | public function getTimeSleeping(): int|float 74 | { 75 | return $this->timeSleeping; 76 | } 77 | 78 | public function getSleepStartTime(): int|float 79 | { 80 | return $this->sleepStartTime; 81 | } 82 | 83 | public function sleep(int|float $seconds): void 84 | { 85 | $this->timeSleeping += $seconds; 86 | } 87 | 88 | public function canWakeUp(): bool 89 | { 90 | return microtime(true) - $this->sleepStartTime >= $this->timeSleeping; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /src/vennv/vapm/io/functions.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\io; 25 | 26 | use vennv\vapm\Async; 27 | use vennv\vapm\Promise; 28 | use vennv\vapm\System; 29 | 30 | /** 31 | * This functions is used to handle the IO operations 32 | */ 33 | 34 | /** 35 | * @param callable $callback 36 | * @return Async 37 | * 38 | * This function is used to run asynchronous callbacks 39 | */ 40 | function async(callable $callback): Async 41 | { 42 | return new Async($callback); 43 | } 44 | 45 | /** 46 | * @param mixed $await 47 | * @return mixed 48 | * 49 | * This function is used to wait for a callback to be executed 50 | */ 51 | function await(mixed $await): mixed 52 | { 53 | return Async::await($await); 54 | } 55 | 56 | /** 57 | * @param int $milliseconds 58 | * @return Promise 59 | * 60 | * This function is used to delay the execution of a callback 61 | */ 62 | function delay(int $milliseconds): Promise 63 | { 64 | return new Promise(function($resolve) use ($milliseconds) { 65 | System::setTimeout(function() use ($resolve) { 66 | $resolve(); 67 | }, $milliseconds); 68 | }); 69 | } 70 | 71 | /** 72 | * @param callable $callback 73 | * @param int $milliseconds 74 | * @return void 75 | * 76 | * This function is used to set a timeout for a callback 77 | */ 78 | function setTimeout(callable $callback, int $milliseconds): void 79 | { 80 | System::setTimeout($callback, $milliseconds); 81 | } 82 | 83 | /** 84 | * @param callable $callback 85 | * @param int $milliseconds 86 | * @return void 87 | * 88 | * This function is used to set an interval for a callback 89 | */ 90 | function setInterval(callable $callback, int $milliseconds): void 91 | { 92 | System::setInterval($callback, $milliseconds); 93 | } -------------------------------------------------------------------------------- /src/vennv/vapm/ChildCoroutine.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use Exception; 28 | 29 | interface ChildCoroutineInterface 30 | { 31 | 32 | /** 33 | * @param Exception $exception 34 | * @return void 35 | * 36 | * This function sets the exception. 37 | */ 38 | public function setException(Exception $exception): void; 39 | 40 | /** 41 | * @return ChildCoroutine 42 | * 43 | * This function runs the coroutine. 44 | */ 45 | 46 | public function run(): ChildCoroutine; 47 | 48 | /** 49 | * @return bool 50 | * 51 | * This function checks if the coroutine is finished. 52 | */ 53 | public function isFinished(): bool; 54 | 55 | /** 56 | * @return mixed 57 | * 58 | * This function returns the return value of the coroutine. 59 | */ 60 | public function getReturn(): mixed; 61 | 62 | } 63 | 64 | final class ChildCoroutine implements ChildCoroutineInterface 65 | { 66 | 67 | protected Generator $coroutine; 68 | 69 | protected Exception $exception; 70 | 71 | public function __construct(Generator $coroutine) 72 | { 73 | $this->coroutine = $coroutine; 74 | } 75 | 76 | public function setException(Exception $exception): void 77 | { 78 | $this->exception = $exception; 79 | } 80 | 81 | public function run(): ChildCoroutine 82 | { 83 | $this->coroutine->send($this->coroutine->current()); 84 | return $this; 85 | } 86 | 87 | public function isFinished(): bool 88 | { 89 | return !$this->coroutine->valid(); 90 | } 91 | 92 | public function getReturn(): mixed 93 | { 94 | return $this->coroutine->getReturn(); 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/vennv/vapm/MacroTask.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use const PHP_INT_MAX; 28 | 29 | final class MacroTask 30 | { 31 | 32 | private static int $nextId = 0; 33 | 34 | /** 35 | * @var array 36 | */ 37 | private static array $tasks = []; 38 | 39 | public static function generateId(): int 40 | { 41 | if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; 42 | return self::$nextId++; 43 | } 44 | 45 | public static function addTask(SampleMacro $sampleMacro): void 46 | { 47 | self::$tasks[$sampleMacro->getId()] = $sampleMacro; 48 | } 49 | 50 | public static function removeTask(SampleMacro $sampleMacro): void 51 | { 52 | $id = $sampleMacro->getId(); 53 | if (isset(self::$tasks[$id])) unset(self::$tasks[$id]); 54 | } 55 | 56 | public static function getTask(int $id): ?SampleMacro 57 | { 58 | return self::$tasks[$id] ?? null; 59 | } 60 | 61 | /** 62 | * @return Generator 63 | */ 64 | public static function getTasks(): Generator 65 | { 66 | foreach (self::$tasks as $id => $task) yield $id => $task; 67 | } 68 | 69 | public static function isPrepare(): bool 70 | { 71 | return !empty(self::$tasks); 72 | } 73 | 74 | public static function run(): void 75 | { 76 | $gc = new GarbageCollection(); 77 | foreach (self::getTasks() as $task) { 78 | /** @var SampleMacro $task */ 79 | if ($task->checkTimeOut()) { 80 | $task->run(); 81 | !$task->isRepeat() ? self::removeTask($task) : $task->resetTimeOut(); 82 | } 83 | $gc->collectWL(); 84 | } 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Mutex.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | 28 | /** 29 | * @author VennDev 30 | * @package vennv\vapm 31 | * 32 | * This class is used to create a mutex object that can be used to synchronize access to shared resources. 33 | * Note: this just for coroutine, if you want to use it in other places, you need to implement it yourself. 34 | */ 35 | interface MutexInterface 36 | { 37 | 38 | /** 39 | * @return bool 40 | * 41 | * This function returns the lock status. 42 | */ 43 | public function isLocked(): bool; 44 | 45 | /** 46 | * @return Generator 47 | * 48 | * This function locks the mutex. 49 | */ 50 | public function lock(): Generator; 51 | 52 | /** 53 | * @return Generator 54 | * 55 | * This function unlocks the mutex. 56 | */ 57 | public function unlock(): Generator; 58 | 59 | } 60 | 61 | final class Mutex implements MutexInterface 62 | { 63 | 64 | private bool $locked = false; 65 | 66 | /** 67 | * @return bool 68 | * 69 | * This function returns the lock status. 70 | */ 71 | public function isLocked(): bool 72 | { 73 | return $this->locked; 74 | } 75 | 76 | /** 77 | * @return Generator 78 | * 79 | * This function locks the mutex. 80 | */ 81 | public function lock(): Generator 82 | { 83 | while ($this->locked) { 84 | yield; 85 | } 86 | $this->locked = true; 87 | } 88 | 89 | /** 90 | * @return Generator 91 | * 92 | * This function unlocks the mutex. 93 | */ 94 | public function unlock(): Generator 95 | { 96 | yield $this->locked = false; 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /src/vennv/vapm/ct/functions.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\ct; 25 | 26 | use Generator; 27 | use Closure; 28 | use vennv\vapm\CoroutineGen; 29 | use vennv\vapm\Deferred; 30 | use vennv\vapm\Channel; 31 | use vennv\vapm\AwaitGroup; 32 | use vennv\vapm\Mutex; 33 | 34 | /** 35 | * This file is used to create a coroutine with non-blocking mode 36 | */ 37 | 38 | /** 39 | * @param callable ...$callbacks 40 | * @return void 41 | * 42 | * This function is used to create a coroutine with non-blocking mode 43 | */ 44 | function c(callable ...$callbacks): void 45 | { 46 | CoroutineGen::runNonBlocking(...$callbacks); 47 | } 48 | 49 | /** 50 | * @param callable ...$callbacks 51 | * @return void 52 | * 53 | * This function is used to create a coroutine with blocking mode 54 | */ 55 | function cBlock(callable ...$callbacks): void 56 | { 57 | CoroutineGen::runBlocking(...$callbacks); 58 | } 59 | 60 | /** 61 | * @param int $milliseconds 62 | * @return Generator 63 | * 64 | * This function is used to delay the execution of a coroutine 65 | */ 66 | function cDelay(int $milliseconds): Generator 67 | { 68 | return CoroutineGen::delay($milliseconds); 69 | } 70 | 71 | /** 72 | * @param callable $callback 73 | * @param int $times 74 | * @return Closure 75 | * 76 | * This function is used to repeat the execution of a coroutine 77 | */ 78 | function cRepeat(callable $callback, int $times): Closure 79 | { 80 | return CoroutineGen::repeat($callback, $times); 81 | } 82 | 83 | /** 84 | * @return Channel 85 | * 86 | * This function is used to create a channel 87 | */ 88 | function channel(): Channel 89 | { 90 | return new Channel(); 91 | } 92 | 93 | /** 94 | * @return AwaitGroup 95 | * 96 | * This function is used to create a await group 97 | */ 98 | function awaitGroup(): AwaitGroup 99 | { 100 | return new AwaitGroup(); 101 | } 102 | 103 | /** 104 | * @return Mutex 105 | * 106 | * This function is used to create a mutex 107 | */ 108 | function mutex(): Mutex 109 | { 110 | return new Mutex(); 111 | } 112 | 113 | /** 114 | * @param callable $callback 115 | * @return Deferred 116 | * 117 | * This function is used to create a deferred 118 | */ 119 | function deferred(callable $callback): Deferred 120 | { 121 | return new Deferred($callback); 122 | } -------------------------------------------------------------------------------- /src/vennv/vapm/AwaitGroup.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | 28 | /** 29 | * @author VennDev 30 | * @package vennv\vapm 31 | * 32 | * This interface is used to create a await group object that can be used to wait for a group of coroutines to complete. 33 | */ 34 | interface AwaitGroupInterface 35 | { 36 | 37 | /** 38 | * @param int $count 39 | * @return void 40 | * 41 | * This function is used to add the count to the group 42 | */ 43 | public function add(int $count): void; 44 | 45 | /** 46 | * @return Generator 47 | * 48 | * This function is used to decrement the count 49 | */ 50 | public function done(): Generator; 51 | 52 | /** 53 | * @return bool 54 | * 55 | * This function is used to check if the count is zero 56 | */ 57 | public function isDone(): bool; 58 | 59 | /** 60 | * @return int 61 | * 62 | * This function is used to get the count 63 | */ 64 | public function getCount(): int; 65 | 66 | /** 67 | * @return void 68 | * 69 | * This function is used to reset the count 70 | */ 71 | public function reset(): void; 72 | 73 | /** 74 | * @return void 75 | * 76 | * This function is used to wait for the count to be zero 77 | */ 78 | public function wait(): void; 79 | 80 | } 81 | 82 | final class AwaitGroup implements AwaitGroupInterface 83 | { 84 | 85 | private int $count = 0; 86 | 87 | public function add(int $count): void 88 | { 89 | $this->count += $count; 90 | } 91 | 92 | public function done(): Generator 93 | { 94 | $this->count--; 95 | yield; 96 | } 97 | 98 | public function isDone(): bool 99 | { 100 | return $this->count === 0; 101 | } 102 | 103 | public function getCount(): int 104 | { 105 | return $this->count; 106 | } 107 | 108 | public function reset(): void 109 | { 110 | $this->count = 0; 111 | } 112 | 113 | public function wait(): void 114 | { 115 | $gc = new GarbageCollection(); 116 | while ($this->count > 0) { 117 | CoroutineGen::run(); 118 | $gc->collectWL(); 119 | } 120 | } 121 | 122 | public function __destruct() 123 | { 124 | $this->reset(); 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/vennv/vapm/ClosureThread.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use function call_user_func; 28 | use function is_array; 29 | use function is_bool; 30 | use function is_callable; 31 | use function is_null; 32 | use function is_object; 33 | use function json_encode; 34 | use function iterator_to_array; 35 | 36 | interface ClosureThreadInterface 37 | { 38 | 39 | /** 40 | * @return void 41 | * 42 | * This function runs the callback function for the thread. 43 | */ 44 | public function onRun(): void; 45 | 46 | } 47 | 48 | final class ClosureThread extends Thread implements ClosureThreadInterface 49 | { 50 | 51 | private mixed $callback; 52 | 53 | /** 54 | * @var array 55 | * @phpstan-var array 56 | */ 57 | private array $argsCallback = []; 58 | 59 | /** 60 | * @param callable $callback 61 | * @param array $args 62 | */ 63 | public function __construct(callable $callback, array $args = []) 64 | { 65 | $this->callback = $callback; 66 | $this->argsCallback = $args; 67 | parent::__construct($callback, $args); 68 | } 69 | 70 | public function onRun(): void 71 | { 72 | if (is_callable($this->callback)) { 73 | $callback = call_user_func($this->callback, ...$this->argsCallback); 74 | if ($callback instanceof Generator) { 75 | $callback = function () use ($callback): Generator { 76 | yield from $callback; 77 | }; 78 | $callback = call_user_func($callback, ...$this->argsCallback); 79 | } 80 | if (is_array($callback)) { 81 | $callback = json_encode($callback); 82 | } elseif (is_object($callback) && !$callback instanceof Generator) { 83 | $callback = json_encode($callback); 84 | } elseif (is_bool($callback)) { 85 | $callback = $callback ? 'true' : 'false'; 86 | } elseif (is_null($callback)) { 87 | $callback = 'null'; 88 | } elseif ($callback instanceof Generator) { 89 | $callback = json_encode(iterator_to_array($callback)); 90 | } else { 91 | $callback = (string)$callback; 92 | } 93 | if (is_bool($callback)) $callback = (string)$callback; 94 | self::post($callback); 95 | } 96 | } 97 | 98 | } -------------------------------------------------------------------------------- /src/vennv/vapm/SampleMacro.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use vennv\vapm\utils\Utils; 27 | use function call_user_func; 28 | use function microtime; 29 | 30 | interface SampleMacroInterface 31 | { 32 | 33 | public function isRepeat(): bool; 34 | 35 | public function getTimeOut(): float; 36 | 37 | public function getTimeStart(): float; 38 | 39 | public function getCallback(): callable; 40 | 41 | public function getId(): int; 42 | 43 | public function checkTimeOut(): bool; 44 | 45 | public function resetTimeOut(): void; 46 | 47 | public function isRunning(): bool; 48 | 49 | public function run(): void; 50 | 51 | public function stop(): void; 52 | 53 | } 54 | 55 | final class SampleMacro implements SampleMacroInterface 56 | { 57 | 58 | private float $timeOut; 59 | 60 | private float $timeStart; 61 | 62 | private bool $isRepeat; 63 | 64 | /** @var callable $callback */ 65 | private mixed $callback; 66 | 67 | private int $id; 68 | 69 | public function __construct(callable $callback, int $timeOut = 0, bool $isRepeat = false) 70 | { 71 | $this->id = MacroTask::generateId(); 72 | $this->timeOut = Utils::milliSecsToSecs($timeOut); 73 | $this->isRepeat = $isRepeat; 74 | $this->timeStart = microtime(true); 75 | $this->callback = $callback; 76 | } 77 | 78 | public function isRepeat(): bool 79 | { 80 | return $this->isRepeat; 81 | } 82 | 83 | public function getTimeOut(): float 84 | { 85 | return $this->timeOut; 86 | } 87 | 88 | public function getTimeStart(): float 89 | { 90 | return $this->timeStart; 91 | } 92 | 93 | public function getCallback(): callable 94 | { 95 | return $this->callback; 96 | } 97 | 98 | public function getId(): int 99 | { 100 | return $this->id; 101 | } 102 | 103 | public function checkTimeOut(): bool 104 | { 105 | return microtime(true) - $this->timeStart >= $this->timeOut; 106 | } 107 | 108 | public function resetTimeOut(): void 109 | { 110 | $this->timeStart = microtime(true); 111 | } 112 | 113 | public function isRunning(): bool 114 | { 115 | return MacroTask::getTask($this->id) !== null; 116 | } 117 | 118 | public function run(): void 119 | { 120 | call_user_func($this->callback); 121 | } 122 | 123 | public function stop(): void 124 | { 125 | MacroTask::removeTask($this); 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Channel.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use RuntimeException; 28 | use function array_shift; 29 | 30 | interface ChannelInterface 31 | { 32 | 33 | /** 34 | * @param mixed $message 35 | * @return Generator 36 | * 37 | * This function is used to send a message to the channel. 38 | */ 39 | public function sendGen($message): Generator; 40 | 41 | /** 42 | * @param mixed $message 43 | * @return void 44 | * 45 | * This function is used to send a message to the channel. 46 | */ 47 | public function send($message): void; 48 | 49 | /** 50 | * @param callable $callback 51 | * @return Generator 52 | * 53 | * This function is used to receive a message from the channel. 54 | */ 55 | public function receiveGen(callable $callback): Generator; 56 | 57 | /** 58 | * @param callable $callback 59 | * @return void 60 | * 61 | * This function is used to receive a message from the channel. 62 | */ 63 | public function receive(callable $callback): void; 64 | 65 | /** 66 | * @return bool 67 | * 68 | * This function is used to check if the channel is empty. 69 | */ 70 | public function isEmpty(): bool; 71 | 72 | /** 73 | * @return void 74 | * 75 | * This function is used to close the channel. 76 | */ 77 | public function close(): void; 78 | 79 | /** 80 | * @return bool 81 | * 82 | * This function is used to check if the channel is closed. 83 | */ 84 | public function isClosed(): bool; 85 | 86 | } 87 | 88 | final class Channel implements ChannelInterface 89 | { 90 | 91 | /** 92 | * @var mixed[] 93 | */ 94 | private array $queue = []; 95 | 96 | private bool $locked = false; 97 | 98 | private bool $closed = false; 99 | 100 | public function sendGen($message): Generator 101 | { 102 | $this->exceptionIfClosed(); 103 | while ($this->locked) yield; 104 | $this->locked = true; 105 | $this->queue[] = $message; 106 | $this->locked = false; 107 | } 108 | 109 | public function send($message): void 110 | { 111 | $this->exceptionIfClosed(); 112 | while ($this->locked) { 113 | CoroutineGen::run(); 114 | } 115 | $this->locked = true; 116 | $this->queue[] = $message; 117 | $this->locked = false; 118 | } 119 | 120 | public function receiveGen(callable $callback): Generator 121 | { 122 | while (!$this->closed || !empty($this->queue)) { 123 | $message = array_shift($this->queue); 124 | if ($message !== null) $callback($message); 125 | yield; 126 | } 127 | } 128 | 129 | public function receive(callable $callback): void 130 | { 131 | while (!$this->closed || !empty($this->queue)) { 132 | $message = array_shift($this->queue); 133 | if ($message !== null) $callback($message); 134 | CoroutineGen::run(); 135 | } 136 | } 137 | 138 | public function isEmpty(): bool 139 | { 140 | return empty($this->queue); 141 | } 142 | 143 | public function close(): void 144 | { 145 | $this->closed = true; 146 | } 147 | 148 | public function isClosed(): bool 149 | { 150 | return $this->closed; 151 | } 152 | 153 | private function exceptionIfClosed(): void 154 | { 155 | if ($this->closed) throw new RuntimeException('Channel is closed'); 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /src/vennv/vapm/Work.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | use SplQueue; 28 | 29 | interface WorkInterface 30 | { 31 | 32 | /** 33 | * @param callable $work 34 | * @param array $args 35 | * @return void 36 | * 37 | * The work is a function that will be executed when the work is run. 38 | */ 39 | public function add(callable $work, array $args = []): void; 40 | 41 | /** 42 | * @param int $index 43 | * @return void 44 | * 45 | * Remove the work from the work list. 46 | */ 47 | public function remove(int $index): void; 48 | 49 | /** 50 | * @return void 51 | * 52 | * Remove all works from the work list. 53 | */ 54 | public function clear(): void; 55 | 56 | /** 57 | * @return int 58 | * 59 | * Get the number of works in the work list. 60 | */ 61 | public function count(): int; 62 | 63 | /** 64 | * @return bool 65 | * 66 | * Check if the work list is empty. 67 | */ 68 | public function isEmpty(): bool; 69 | 70 | /** 71 | * @return mixed 72 | * 73 | * Get the first work in the work list. 74 | */ 75 | public function dequeue(): mixed; 76 | 77 | /** 78 | * @param int $number 79 | * @return Generator 80 | * 81 | * Get the work list by number. 82 | */ 83 | public function getArrayByNumber(int $number): Generator; 84 | 85 | /** 86 | * @return Generator 87 | * 88 | * Get all works in the work list. 89 | */ 90 | public function getAll(): Generator; 91 | 92 | /** 93 | * @return void 94 | * 95 | * Run all works in the work list. 96 | */ 97 | public function run(): void; 98 | 99 | } 100 | 101 | final class Work implements WorkInterface 102 | { 103 | 104 | private SplQueue $queue; 105 | 106 | public function __construct() 107 | { 108 | $this->queue = new SplQueue(); 109 | } 110 | 111 | /** 112 | * @param callable $work 113 | * @param array $args 114 | * @return void 115 | * 116 | * Add a work to the work list. 117 | */ 118 | public function add(callable $work, array $args = []): void 119 | { 120 | $this->queue->enqueue(new ClosureThread($work, $args)); 121 | } 122 | 123 | public function remove(int $index): void 124 | { 125 | $this->queue->offsetUnset($index); 126 | } 127 | 128 | public function clear(): void 129 | { 130 | $this->queue = new SplQueue(); 131 | } 132 | 133 | public function count(): int 134 | { 135 | return $this->queue->count(); 136 | } 137 | 138 | public function isEmpty(): bool 139 | { 140 | return $this->queue->isEmpty(); 141 | } 142 | 143 | public function dequeue(): mixed 144 | { 145 | return $this->queue->dequeue(); 146 | } 147 | 148 | public function getArrayByNumber(int $number): Generator 149 | { 150 | for ($i = 0; $i < $number; $i++) yield $this->queue->dequeue(); 151 | } 152 | 153 | public function getAll(): Generator 154 | { 155 | while (!$this->queue->isEmpty()) yield $this->queue->dequeue(); 156 | } 157 | 158 | public function run(): void 159 | { 160 | $gc = new GarbageCollection(); 161 | while (!$this->queue->isEmpty()) { 162 | /** @var ClosureThread $work */ 163 | $work = $this->queue->dequeue(); 164 | $work->start(); 165 | $gc->collectWL(); 166 | } 167 | } 168 | 169 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Deferred.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Generator; 27 | 28 | /** 29 | * @author VennDev 30 | * @package vennv\vapm 31 | * 32 | * This interface is used to create a deferred object that can be used to get the result of a coroutine. 33 | */ 34 | interface DeferredInterface 35 | { 36 | 37 | /** 38 | * This method is used to get the result of the deferred. 39 | */ 40 | public function await(): Generator; 41 | 42 | /** 43 | * @param DeferredInterface ...$deferreds 44 | * @return Generator 45 | * 46 | * This method is used to get all the results of the deferred. 47 | */ 48 | public static function awaitAll(DeferredInterface ...$deferreds): Generator; 49 | 50 | /** 51 | * @param DeferredInterface ...$deferreds 52 | * @return Generator 53 | * 54 | * This method is used to get the first result of the deferred. 55 | */ 56 | public static function awaitAny(DeferredInterface ...$deferreds): Generator; 57 | 58 | /** 59 | * This method is used to check if the deferred is finished. 60 | */ 61 | public function isFinished(): bool; 62 | 63 | /** 64 | * This method is used to get the child coroutine of the deferred. 65 | */ 66 | public function getChildCoroutine(): ChildCoroutine; 67 | 68 | /** 69 | * This method is used to get the result of the deferred. 70 | */ 71 | public function getComplete(): mixed; 72 | 73 | } 74 | 75 | final class Deferred implements DeferredInterface 76 | { 77 | 78 | protected mixed $return = null; 79 | 80 | protected ChildCoroutine $childCoroutine; 81 | 82 | public function __construct(callable $callback) 83 | { 84 | $generator = call_user_func($callback); 85 | $generator instanceof Generator ? $this->childCoroutine = new ChildCoroutine($generator) : throw new DeferredException(Error::DEFERRED_CALLBACK_MUST_RETURN_GENERATOR); 86 | } 87 | 88 | public function await(): Generator 89 | { 90 | while (!$this->childCoroutine->isFinished()) { 91 | $this->childCoroutine->run(); 92 | yield; 93 | } 94 | 95 | $this->return = $this->childCoroutine->getReturn(); 96 | 97 | return $this->return; 98 | } 99 | 100 | public static function awaitAll(DeferredInterface ...$deferreds): Generator 101 | { 102 | $result = []; 103 | 104 | while (count($result) <= count($deferreds)) { 105 | foreach ($deferreds as $index => $deferred) { 106 | $childCoroutine = $deferred->getChildCoroutine(); 107 | 108 | if ($childCoroutine->isFinished()) { 109 | $result[] = $childCoroutine->getReturn(); 110 | unset($deferreds[$index]); 111 | } else { 112 | $childCoroutine->run(); 113 | } 114 | yield; 115 | } 116 | 117 | yield; 118 | } 119 | 120 | return $result; 121 | } 122 | 123 | public static function awaitAny(DeferredInterface ...$deferreds): Generator 124 | { 125 | $result = []; 126 | 127 | while (count($result) <= count($deferreds)) { 128 | foreach ($deferreds as $deferred) { 129 | $childCoroutine = $deferred->getChildCoroutine(); 130 | 131 | if ($childCoroutine->isFinished()) { 132 | $result[] = $childCoroutine->getReturn(); 133 | $deferreds = []; 134 | break; 135 | } else { 136 | $childCoroutine->run(); 137 | } 138 | yield; 139 | } 140 | 141 | yield; 142 | } 143 | 144 | return $result; 145 | } 146 | 147 | public function isFinished(): bool 148 | { 149 | return $this->childCoroutine->isFinished(); 150 | } 151 | 152 | public function getChildCoroutine(): ChildCoroutine 153 | { 154 | return $this->childCoroutine; 155 | } 156 | 157 | public function getComplete(): mixed 158 | { 159 | return $this->return; 160 | } 161 | 162 | } -------------------------------------------------------------------------------- /src/vennv/vapm/CoroutineGen.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Closure; 27 | use ReflectionException; 28 | use SplQueue; 29 | use Generator; 30 | use Throwable; 31 | 32 | interface CoroutineGenInterface 33 | { 34 | 35 | /** 36 | * @return SplQueue|null 37 | * 38 | * This function returns the task queue. 39 | */ 40 | public static function getTaskQueue(): ?SplQueue; 41 | 42 | /** 43 | * @param mixed ...$coroutines 44 | * @return void 45 | * 46 | * This is a blocking function that runs all the coroutines passed to it. 47 | */ 48 | public static function runNonBlocking(mixed ...$coroutines): void; 49 | 50 | /** 51 | * @param mixed ...$coroutines 52 | * @return void 53 | * 54 | * This is a blocking function that runs all the coroutines passed to it. 55 | */ 56 | public static function runBlocking(mixed ...$coroutines): void; 57 | 58 | /** 59 | * @param callable $callback 60 | * @param int $times 61 | * @return Closure 62 | * 63 | * This is a generator that runs a callback function a specified amount of times. 64 | */ 65 | public static function repeat(callable $callback, int $times): Closure; 66 | 67 | /** 68 | * @param int $milliseconds 69 | * @return Generator 70 | * 71 | * This is a generator that yields for a specified amount of milliseconds. 72 | */ 73 | public static function delay(int $milliseconds): Generator; 74 | 75 | /** 76 | * @return void 77 | * 78 | * This function runs the task queue. 79 | */ 80 | public static function run(): void; 81 | 82 | } 83 | 84 | final class CoroutineGen implements CoroutineGenInterface 85 | { 86 | 87 | private static ?SplQueue $taskQueue = null; 88 | 89 | public static function getTaskQueue(): ?SplQueue 90 | { 91 | return self::$taskQueue; 92 | } 93 | 94 | /** 95 | * @param mixed ...$coroutines 96 | * @return void 97 | * @throws Throwable 98 | */ 99 | public static function runNonBlocking(mixed ...$coroutines): void 100 | { 101 | System::init(); 102 | self::$taskQueue ??= new SplQueue(); 103 | foreach ($coroutines as $coroutine) { 104 | $result = is_callable($coroutine) ? $coroutine() : $coroutine; 105 | $result instanceof Generator 106 | ? self::schedule(new ChildCoroutine($result)) 107 | : $result; 108 | } 109 | self::run(); 110 | } 111 | 112 | /** 113 | * @param mixed ...$coroutines 114 | * @return void 115 | * @throws Throwable 116 | */ 117 | public static function runBlocking(mixed ...$coroutines): void 118 | { 119 | self::runNonBlocking(...$coroutines); 120 | $gc = new GarbageCollection(); 121 | while (!self::$taskQueue?->isEmpty()) { 122 | self::run(); 123 | $gc->collectWL(); 124 | } 125 | } 126 | 127 | /** 128 | * @param mixed ...$coroutines 129 | * @return Closure 130 | */ 131 | private static function processCoroutine(mixed ...$coroutines): Closure 132 | { 133 | return function () use ($coroutines): void { 134 | foreach ($coroutines as $coroutine) { 135 | $result = is_callable($coroutine) ? $coroutine() : $coroutine; 136 | $result instanceof Generator 137 | ? self::schedule(new ChildCoroutine($result)) 138 | : $result; 139 | } 140 | self::run(); 141 | }; 142 | } 143 | 144 | public static function repeat(callable $callback, int $times): Closure 145 | { 146 | $gc = new GarbageCollection(); 147 | for ($i = 0; $i < $times; $i++) { 148 | $result = $callback(); 149 | if ($result instanceof Generator) { 150 | $callback = self::processCoroutine($result); 151 | } 152 | $gc->collectWL(); 153 | } 154 | return fn() => null; 155 | } 156 | 157 | public static function delay(int $milliseconds): Generator 158 | { 159 | for ($i = 0; $i < GeneratorManager::calculateSeconds($milliseconds); $i++) yield; 160 | } 161 | 162 | private static function schedule(ChildCoroutine $childCoroutine): void 163 | { 164 | self::$taskQueue?->enqueue($childCoroutine); 165 | } 166 | 167 | /** 168 | * @throws ReflectionException 169 | * @throws Throwable 170 | */ 171 | public static function run(): void 172 | { 173 | if (!self::$taskQueue?->isEmpty()) { 174 | $coroutine = self::$taskQueue?->dequeue(); 175 | if ($coroutine instanceof ChildCoroutine && !$coroutine->isFinished()) { 176 | self::schedule($coroutine->run()); 177 | } 178 | } 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /src/vennv/vapm/EventLoop.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use SplQueue; 27 | use Generator; 28 | use Throwable; 29 | use const PHP_INT_MAX; 30 | 31 | interface EventLoopInterface 32 | { 33 | 34 | public static function init(): void; 35 | 36 | public static function generateId(): int; 37 | 38 | public static function addQueue(Promise $promise): void; 39 | 40 | public static function getQueue(int $id): ?Promise; 41 | 42 | public static function addReturn(Promise $promise): void; 43 | 44 | public static function removeReturn(int $id): void; 45 | 46 | public static function isReturn(int $id): bool; 47 | 48 | public static function getReturn(int $id): ?Promise; 49 | 50 | /** 51 | * @return Generator 52 | */ 53 | public static function getReturns(): Generator; 54 | 55 | } 56 | 57 | class EventLoop implements EventLoopInterface 58 | { 59 | 60 | protected const LIMIT = 20; // 20 times run 61 | 62 | protected static int $nextId = 0; 63 | 64 | /** 65 | * @var SplQueue 66 | */ 67 | protected static SplQueue $queues; 68 | 69 | /** 70 | * @var array 71 | */ 72 | protected static array $returns = []; 73 | 74 | public static function init(): void 75 | { 76 | self::$queues ??= new SplQueue(); 77 | } 78 | 79 | public static function generateId(): int 80 | { 81 | if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; 82 | return self::$nextId++; 83 | } 84 | 85 | public static function addQueue(Promise $promise): void 86 | { 87 | self::$queues->enqueue($promise); 88 | } 89 | 90 | public static function getQueue(int $id): ?Promise 91 | { 92 | while (!self::$queues->isEmpty()) { 93 | /** 94 | * @var Promise $promise 95 | */ 96 | $promise = self::$queues->dequeue(); 97 | if ($promise->getId() === $id) return $promise; 98 | self::$queues->enqueue($promise); 99 | } 100 | return null; 101 | } 102 | 103 | public static function addReturn(Promise $promise): void 104 | { 105 | if (!isset(self::$returns[$promise->getId()])) self::$returns[$promise->getId()] = $promise; 106 | } 107 | 108 | public static function isReturn(int $id): bool 109 | { 110 | return isset(self::$returns[$id]); 111 | } 112 | 113 | public static function removeReturn(int $id): void 114 | { 115 | if (self::isReturn($id)) unset(self::$returns[$id]); 116 | } 117 | 118 | public static function getReturn(int $id): ?Promise 119 | { 120 | return self::$returns[$id] ?? null; 121 | } 122 | 123 | /** 124 | * @return Generator 125 | */ 126 | public static function getReturns(): Generator 127 | { 128 | foreach (self::$returns as $id => $promise) yield $id => $promise; 129 | } 130 | 131 | /** 132 | * @throws Throwable 133 | */ 134 | private static function clearGarbage(): void 135 | { 136 | $gc = new GarbageCollection(); 137 | foreach (self::getReturns() as $id => $promise) { 138 | if ($promise instanceof Promise && $promise->canDrop()) unset(self::$returns[$id]); 139 | $gc->collectWL(); 140 | } 141 | } 142 | 143 | /** 144 | * @throws Throwable 145 | */ 146 | protected static function run(): void 147 | { 148 | CoroutineGen::run(); 149 | 150 | $i = 0; 151 | while (!self::$queues->isEmpty() && $i++ < self::LIMIT) { 152 | /** @var Promise $promise */ 153 | $promise = self::$queues->dequeue(); 154 | $fiber = $promise->getFiber(); 155 | if ($fiber->isSuspended()) $fiber->resume(); 156 | if ( 157 | $fiber->isTerminated() && 158 | ($promise->getStatus() !== StatusPromise::PENDING || $promise->isJustGetResult()) 159 | ) { 160 | try { 161 | $promise->isJustGetResult() && $promise->setResult($fiber->getReturn()); 162 | } catch (Throwable $e) { 163 | echo $e->getMessage(); 164 | } 165 | MicroTask::addTask($promise->getId(), $promise); 166 | } else { 167 | self::$queues->enqueue($promise); 168 | } 169 | } 170 | 171 | MicroTask::isPrepare() && MicroTask::run(); 172 | MacroTask::isPrepare() && MacroTask::run(); 173 | 174 | self::clearGarbage(); 175 | } 176 | 177 | /** 178 | * @throws Throwable 179 | */ 180 | protected static function runSingle(): void 181 | { 182 | $gc = new GarbageCollection(); 183 | while ( 184 | !self::$queues->isEmpty() || 185 | (CoroutineGen::getTaskQueue() !== null && !CoroutineGen::getTaskQueue()->isEmpty()) || 186 | MicroTask::isPrepare() || MacroTask::isPrepare() 187 | ) { 188 | self::run(); 189 | $gc->collectWL(); 190 | } 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/vennv/vapm/PHPUtils.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | 28 | interface PHPUtilsInterface 29 | { 30 | 31 | /** 32 | * @param array $array 33 | * @param callable $callback 34 | * @return Async 35 | * 36 | * @phpstan-param array $array 37 | * @throws Throwable 38 | * 39 | * This function is used to iterate over an array and call a callback function for each element. 40 | */ 41 | public static function forEach(array $array, callable $callback): Async; 42 | 43 | /** 44 | * @param array $array 45 | * @param callable $callback 46 | * @return Async 47 | * 48 | * @phpstan-param array $array 49 | * @throws Throwable 50 | * 51 | * This function is used to map over an array and apply a callback function to each element. 52 | */ 53 | public static function arrayMap(array $array, callable $callback): Async; 54 | 55 | /** 56 | * @param array $array 57 | * @param callable $callback 58 | * @return Async 59 | * 60 | * @phpstan-param array $array 61 | * @throws Throwable 62 | */ 63 | public static function arrayFilter(array $array, callable $callback): Async; 64 | 65 | /** 66 | * @param array $array 67 | * @param callable $callback 68 | * @param mixed $initialValue 69 | * @return Async 70 | * 71 | * @throws Throwable 72 | * 73 | * This function is used to reduce an array to a single value by applying a callback function to each element. 74 | */ 75 | public static function arrayReduce(array $array, callable $callback, mixed $initialValue): Async; 76 | 77 | /** 78 | * @param array $array 79 | * @param string $className 80 | * @return Async 81 | * 82 | * @throws Throwable 83 | * 84 | * This function is used to check if all elements in an array are instances of a specific class. 85 | */ 86 | public static function instanceOfAll(array $array, string $className): Async; 87 | 88 | /** 89 | * @param array $array 90 | * @param string $className 91 | * @return Async 92 | * 93 | * @throws Throwable 94 | * 95 | * This function is used to check if any element in an array is an instance of a specific class. 96 | */ 97 | public static function instanceOfAny(array $array, string $className): Async; 98 | 99 | } 100 | 101 | final class PHPUtils implements PHPUtilsInterface 102 | { 103 | 104 | public static function forEach(array $array, callable $callback): Async 105 | { 106 | return new Async(function () use ($array, $callback) { 107 | foreach ($array as $key => $value) { 108 | $callback($key, $value); 109 | FiberManager::wait(); 110 | } 111 | }); 112 | } 113 | 114 | public static function arrayMap(array $array, callable $callback): Async 115 | { 116 | return new Async(function () use ($array, $callback) { 117 | $result = []; 118 | foreach ($array as $key => $value) { 119 | $result[$key] = $callback($key, $value); 120 | FiberManager::wait(); 121 | } 122 | return $result; 123 | }); 124 | } 125 | 126 | public static function arrayFilter(array $array, callable $callback): Async 127 | { 128 | return new Async(function () use ($array, $callback) { 129 | $result = []; 130 | foreach ($array as $key => $value) { 131 | if ($callback($key, $value)) { 132 | $result[$key] = $value; 133 | } 134 | FiberManager::wait(); 135 | } 136 | return $result; 137 | }); 138 | } 139 | 140 | public static function arrayReduce(array $array, callable $callback, mixed $initialValue): Async 141 | { 142 | return new Async(function () use ($array, $callback, $initialValue) { 143 | $accumulator = $initialValue; 144 | foreach ($array as $key => $value) { 145 | $accumulator = $callback($accumulator, $value, $key); 146 | FiberManager::wait(); 147 | } 148 | return $accumulator; 149 | }); 150 | } 151 | 152 | public static function instanceOfAll(array $array, string $className): Async 153 | { 154 | return new Async(function () use ($array, $className) { 155 | foreach ($array as $value) { 156 | if (!($value instanceof $className)) return false; 157 | FiberManager::wait(); 158 | } 159 | return true; 160 | }); 161 | } 162 | 163 | public static function instanceOfAny(array $array, string $className): Async 164 | { 165 | return new Async(function () use ($array, $className) { 166 | foreach ($array as $value) { 167 | if ($value instanceof $className) return true; 168 | FiberManager::wait(); 169 | } 170 | return false; 171 | }); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/vennv/vapm/Internet.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Closure; 27 | use CurlHandle; 28 | use function array_merge; 29 | use function curl_close; 30 | use function curl_error; 31 | use function curl_exec; 32 | use function curl_getinfo; 33 | use function curl_init; 34 | use function curl_setopt_array; 35 | use function explode; 36 | use function is_string; 37 | use function strtolower; 38 | use function substr; 39 | use function trim; 40 | use const CURLINFO_HEADER_SIZE; 41 | use const CURLINFO_HTTP_CODE; 42 | use const CURLOPT_AUTOREFERER; 43 | use const CURLOPT_CONNECTTIMEOUT_MS; 44 | use const CURLOPT_FOLLOWLOCATION; 45 | use const CURLOPT_FORBID_REUSE; 46 | use const CURLOPT_FRESH_CONNECT; 47 | use const CURLOPT_HEADER; 48 | use const CURLOPT_HTTPHEADER; 49 | use const CURLOPT_POST; 50 | use const CURLOPT_POSTFIELDS; 51 | use const CURLOPT_RETURNTRANSFER; 52 | use const CURLOPT_SSL_VERIFYHOST; 53 | use const CURLOPT_SSL_VERIFYPEER; 54 | use const CURLOPT_TIMEOUT_MS; 55 | 56 | final class Internet 57 | { 58 | 59 | /** 60 | * GETs a URL using cURL 61 | * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. 62 | * 63 | * @param int $timeout default 10 64 | * @param string[] $extraHeaders 65 | * @param string|null $error reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation. 66 | */ 67 | public static function getURL( 68 | string $page, 69 | int $timeout = 10, 70 | array $extraHeaders = [], 71 | string &$error = null 72 | ): ?InternetRequestResult 73 | { 74 | try { 75 | return self::simpleCurl( 76 | $page, 77 | $timeout, 78 | $extraHeaders 79 | ); 80 | } catch (InternetException $exception) { 81 | $error = $exception->getMessage(); 82 | return null; 83 | } 84 | } 85 | 86 | /** 87 | * POSTs data to a URL 88 | * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. 89 | * 90 | * @param string[]|string $args 91 | * @param string[] $extraHeaders 92 | * @param string|null $error reference parameter, will be set to the output of curl_error(). Use this to retrieve errors that occurred during the operation. 93 | */ 94 | public static function postURL( 95 | string $page, 96 | array|string $args, 97 | int $timeout = 10, 98 | array $extraHeaders = [], 99 | string &$error = null 100 | ): ?InternetRequestResult 101 | { 102 | try { 103 | return self::simpleCurl($page, $timeout, $extraHeaders, [ 104 | CURLOPT_POST => 1, 105 | CURLOPT_POSTFIELDS => $args 106 | ]); 107 | } catch (InternetException $ex) { 108 | $error = $ex->getMessage(); 109 | return null; 110 | } 111 | } 112 | 113 | /** 114 | * General cURL shorthand function. 115 | * NOTE: This is a blocking operation and can take a significant amount of time. It is inadvisable to use this method on the main thread. 116 | * 117 | * @param float $timeout The maximum connect timeout and timeout in seconds, correct to ms. 118 | * @param string[] $extraHeaders extra headers to send as a plain string array 119 | * @param array $extraOpts extra CURL-OPT_* to set as an [opt => value] map 120 | * @param Closure|null $onSuccess function to be called if there is no error. Accepts a resource argument as the cURL handle. 121 | * @phpstan-param array $extraOpts 122 | * @phpstan-param list $extraHeaders 123 | * @phpstan-param (Closure(CurlHandle) : void)|null $onSuccess 124 | * 125 | * @throws InternetException if a cURL error occurs 126 | */ 127 | public static function simpleCurl( 128 | string $page, 129 | float $timeout = 10, 130 | array $extraHeaders = [], 131 | array $extraOpts = [], 132 | ?Closure $onSuccess = null 133 | ): InternetRequestResult 134 | { 135 | 136 | $time = (int)($timeout * 1000); 137 | 138 | $curlHandle = curl_init($page); 139 | 140 | if ($curlHandle === false) throw new InternetException("Unable to create new cURL session"); 141 | 142 | curl_setopt_array($curlHandle, $extraOpts + 143 | [ 144 | CURLOPT_SSL_VERIFYPEER => false, 145 | CURLOPT_SSL_VERIFYHOST => 2, 146 | CURLOPT_FORBID_REUSE => 1, 147 | CURLOPT_FRESH_CONNECT => 1, 148 | CURLOPT_AUTOREFERER => true, 149 | CURLOPT_FOLLOWLOCATION => true, 150 | CURLOPT_RETURNTRANSFER => true, 151 | CURLOPT_CONNECTTIMEOUT_MS => $time, 152 | CURLOPT_TIMEOUT_MS => $time, 153 | CURLOPT_HTTPHEADER => array_merge( 154 | ["User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"], 155 | $extraHeaders 156 | ), 157 | CURLOPT_HEADER => true 158 | ]); 159 | 160 | try { 161 | $raw = curl_exec($curlHandle); 162 | 163 | if ($raw === false) throw new InternetException(curl_error($curlHandle)); 164 | if (!is_string($raw)) throw new AssumptionFailedError(Error::WRONG_TYPE_WHEN_USE_CURL_EXEC); 165 | 166 | $httpCode = curl_getinfo($curlHandle, CURLINFO_HTTP_CODE); 167 | $headerSize = curl_getinfo($curlHandle, CURLINFO_HEADER_SIZE); 168 | $rawHeaders = substr($raw, 0, $headerSize); 169 | $body = substr($raw, $headerSize); 170 | $headers = []; 171 | 172 | foreach (explode("\r\n\r\n", $rawHeaders) as $rawHeaderGroup) { 173 | $headerGroup = []; 174 | foreach (explode("\r\n", $rawHeaderGroup) as $line) { 175 | $nameValue = explode(":", $line, 2); 176 | if (isset($nameValue[1])) $headerGroup[trim(strtolower($nameValue[0]))] = trim($nameValue[1]); 177 | } 178 | 179 | $headers[] = $headerGroup; 180 | } 181 | 182 | if (!is_null($onSuccess)) $onSuccess($curlHandle); 183 | 184 | return new InternetRequestResult($headers, $body, $httpCode); 185 | } finally { 186 | curl_close($curlHandle); 187 | } 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /src/vennv/vapm/Worker.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | 28 | /** 29 | * Class Worker 30 | * @package vennv\vapm 31 | * 32 | * This class is used to create a worker to run the work. 33 | * All asynchronous methods are based on this class. 34 | */ 35 | interface WorkerInterface 36 | { 37 | 38 | /** 39 | * @return bool 40 | * 41 | * Check the worker is started. 42 | */ 43 | public function isStarted(): bool; 44 | 45 | /** 46 | * @return Work 47 | * 48 | * Get the work. 49 | */ 50 | public function getWork(): Work; 51 | 52 | /** 53 | * @return void 54 | * 55 | * This is method help you to remove the worker from the worker list. 56 | * You should call this method when the work is done to avoid memory leaks. 57 | */ 58 | public function done(): void; 59 | 60 | /** 61 | * @param mixed $result 62 | * @return void 63 | * 64 | * Collect the result of the work. 65 | */ 66 | public function collect(mixed $result): void; 67 | 68 | /** 69 | * @return array 70 | * 71 | * Get the result of the work. 72 | */ 73 | public function get(): array; 74 | 75 | /** 76 | * @return bool 77 | * 78 | * Check the worker is locked. 79 | */ 80 | public function isLocked(): bool; 81 | 82 | /** 83 | * @return void 84 | * 85 | * Lock the worker. 86 | */ 87 | public function lock(): void; 88 | 89 | /** 90 | * @return void 91 | * 92 | * Unlock the worker. 93 | */ 94 | public function unlock(): void; 95 | 96 | /** 97 | * @param Worker $worker 98 | * @param callable $callback 99 | * @return void 100 | * 101 | * Add a child worker to the parent worker. 102 | */ 103 | public function addWorker(Worker $worker, callable $callback): void; 104 | 105 | /** 106 | * @return Async 107 | * 108 | * Run the work. 109 | * @throws Throwable 110 | */ 111 | public function run(callable $callback): Async; 112 | 113 | } 114 | 115 | final class Worker implements WorkerInterface 116 | { 117 | 118 | private const LOCKED = "locked"; 119 | 120 | public bool $isStarted = false; 121 | 122 | public bool $isChild = false; 123 | 124 | protected static int $nextId = 0; 125 | 126 | public int $id; 127 | 128 | /** 129 | * @var array 130 | */ 131 | private array $options; 132 | 133 | /** 134 | * @var array> 135 | */ 136 | private array $childWorkers = []; 137 | 138 | /** 139 | * @var array> 140 | */ 141 | private static array $workers = []; 142 | 143 | private Work $work; 144 | 145 | /** 146 | * @param Work $work 147 | * @param array $options 148 | */ 149 | public function __construct(Work $work, array $options = ["threads" => 4]) 150 | { 151 | $this->work = $work; 152 | $this->options = $options; 153 | $this->id = $this->generateId(); 154 | self::$workers[$this->id] = []; 155 | } 156 | 157 | private function generateId(): int 158 | { 159 | if (self::$nextId >= PHP_INT_MAX) self::$nextId = 0; 160 | return self::$nextId++; 161 | } 162 | 163 | public function isStarted(): bool 164 | { 165 | return $this->isStarted; 166 | } 167 | 168 | public function getWork(): Work 169 | { 170 | return $this->work; 171 | } 172 | 173 | public function done(): void 174 | { 175 | if ($this->isChild) return; 176 | unset(self::$workers[$this->id]); 177 | } 178 | 179 | public function collect(mixed $result): void 180 | { 181 | self::$workers[$this->id][] = $result; 182 | } 183 | 184 | /** 185 | * @return array 186 | */ 187 | public function get(): array 188 | { 189 | return self::$workers[$this->id]; 190 | } 191 | 192 | public function isLocked(): bool 193 | { 194 | return isset(self::$workers[$this->id][self::LOCKED]); 195 | } 196 | 197 | public function lock(): void 198 | { 199 | self::$workers[$this->id][self::LOCKED] = true; 200 | } 201 | 202 | public function unlock(): void 203 | { 204 | unset(self::$workers[$this->id][self::LOCKED]); 205 | } 206 | 207 | public function addWorker(Worker $worker, callable $callback): void 208 | { 209 | $worker->isChild = true; 210 | $this->childWorkers[] = [$worker, $callback]; 211 | } 212 | 213 | /** 214 | * @throws Throwable 215 | */ 216 | public function run(callable $callback): Async 217 | { 218 | $this->isStarted = true; 219 | $work = $this->getWork(); 220 | 221 | return new Async(function () use ($work, $callback): void { 222 | $threads = $this->options["threads"]; 223 | 224 | if ($threads >= 1) { 225 | $promises = []; 226 | $totalCountWorks = $work->count(); 227 | 228 | $gc = new GarbageCollection(); 229 | while ($this->isLocked() || $totalCountWorks > 0) { 230 | if (!$this->isLocked()) { 231 | if (count($promises) < $threads && $work->count() > 0) { 232 | /** @var ClosureThread $callbackQueue */ 233 | $callbackQueue = $work->dequeue(); 234 | $promises[] = $callbackQueue->start(); 235 | } else { 236 | /** @var Promise $promise */ 237 | foreach ($promises as $index => $promise) { 238 | $result = EventLoop::getReturn($promise->getId()); 239 | if ($result !== null) { 240 | $result = $promise->getResult(); 241 | $this->collect($result); 242 | unset($promises[$index]); 243 | $totalCountWorks--; 244 | } 245 | } 246 | } 247 | } 248 | $gc->collectWL(); 249 | FiberManager::wait(); 250 | } 251 | 252 | while (count($this->childWorkers) > 0) { 253 | $childWorker = array_shift($this->childWorkers); 254 | if ($childWorker !== null) { 255 | /** @var WorkerInterface $worker */ 256 | $worker = $childWorker[0]; 257 | 258 | /** @var callable $workerCallback */ 259 | $workerCallback = $childWorker[1]; 260 | 261 | Async::await($worker->run($workerCallback)); 262 | 263 | $this->collect($worker->get()); 264 | $worker->done(); 265 | } 266 | FiberManager::wait(); 267 | $gc->collectWL(); 268 | } 269 | 270 | $data = Async::await(Stream::flattenArray($this->get())); 271 | call_user_func($callback, $data, $this); 272 | } 273 | }); 274 | } 275 | 276 | } -------------------------------------------------------------------------------- /src/vennv/vapm/Stream.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | use function fclose; 28 | use function fgets; 29 | use function fopen; 30 | use function fwrite; 31 | use function is_array; 32 | use function touch; 33 | use function unlink; 34 | use function file_exists; 35 | use function call_user_func; 36 | use function stream_set_blocking; 37 | 38 | interface StreamInterface 39 | { 40 | 41 | /** 42 | * @return Promise 43 | * @throws Throwable 44 | * 45 | * Use this to read a file or url. 46 | */ 47 | public static function read(string $path): Promise; 48 | 49 | /** 50 | * @return Promise 51 | * @throws Throwable 52 | * 53 | * Use this to write to a file. 54 | */ 55 | public static function write(string $path, string $data): Promise; 56 | 57 | /** 58 | * @return Promise 59 | * @throws Throwable 60 | * 61 | * Use this to append to a file. 62 | */ 63 | public static function append(string $path, string $data): Promise; 64 | 65 | /** 66 | * @return Promise 67 | * @throws Throwable 68 | * 69 | * Use this to delete a file. 70 | */ 71 | public static function delete(string $path): Promise; 72 | 73 | /** 74 | * @return Promise 75 | * @throws Throwable 76 | * 77 | * Use this to create a file. 78 | */ 79 | public static function create(string $path): Promise; 80 | 81 | /** 82 | * @return Promise 83 | * @throws Throwable 84 | * 85 | * Use this to create a file or overwrite a file. 86 | */ 87 | public static function overWrite(string $path, string $data): Promise; 88 | 89 | /** 90 | * @param array $array 91 | * @return Promise 92 | * @throws Throwable 93 | * 94 | * Use this to flatten an array. 95 | */ 96 | public static function flattenArray(array $array): Promise; 97 | 98 | } 99 | 100 | final class Stream implements StreamInterface 101 | { 102 | 103 | /** 104 | * @throws Throwable 105 | */ 106 | public static function read(string $path): Promise 107 | { 108 | return new Promise(function ($resolve, $reject) use ($path): void { 109 | $lines = ''; 110 | $handle = fopen($path, 'r'); 111 | 112 | if ($handle === false) { 113 | $reject(Error::UNABLE_TO_OPEN_FILE); 114 | } else { 115 | stream_set_blocking($handle, false); 116 | 117 | while (($line = fgets($handle)) !== false) { 118 | $lines .= $line; 119 | FiberManager::wait(); 120 | } 121 | 122 | fclose($handle); 123 | } 124 | 125 | $resolve($lines); 126 | }); 127 | } 128 | 129 | /** 130 | * @throws Throwable 131 | */ 132 | public static function write(string $path, string $data): Promise 133 | { 134 | return new Promise(function ($resolve, $reject) use ($path, $data): void { 135 | System::setTimeout(function () use ($resolve, $reject, $path, $data): void { 136 | $callback = function ($path, $data) use ($reject): void { 137 | $handle = fopen($path, 'w'); 138 | 139 | if ($handle === false) { 140 | $reject(Error::UNABLE_TO_OPEN_FILE); 141 | } else { 142 | stream_set_blocking($handle, false); 143 | fwrite($handle, $data); 144 | fclose($handle); 145 | } 146 | }; 147 | 148 | call_user_func($callback, $path, $data); 149 | $resolve(); 150 | }, 0); 151 | }); 152 | } 153 | 154 | /** 155 | * @throws Throwable 156 | */ 157 | public static function append(string $path, string $data): Promise 158 | { 159 | return new Promise(function ($resolve, $reject) use ($path, $data): void { 160 | System::setTimeout(function () use ($resolve, $reject, $path, $data): void { 161 | $callback = function ($path, $data) use ($reject): void { 162 | $handle = fopen($path, 'a'); 163 | 164 | if ($handle === false) { 165 | $reject(Error::UNABLE_TO_OPEN_FILE); 166 | } else { 167 | stream_set_blocking($handle, false); 168 | fwrite($handle, $data); 169 | fclose($handle); 170 | } 171 | }; 172 | 173 | call_user_func($callback, $path, $data); 174 | $resolve(); 175 | }, 0); 176 | }); 177 | } 178 | 179 | /** 180 | * @throws Throwable 181 | */ 182 | public static function delete(string $path): Promise 183 | { 184 | return new Promise(function ($resolve, $reject) use ($path): void { 185 | System::setTimeout(function () use ($resolve, $reject, $path): void { 186 | $callback = function ($path) use ($reject): void { 187 | file_exists($path) ? unlink($path) : $reject(Error::FILE_DOES_NOT_EXIST); 188 | }; 189 | call_user_func($callback, $path); 190 | $resolve(); 191 | }, 0); 192 | }); 193 | } 194 | 195 | /** 196 | * @throws Throwable 197 | */ 198 | public static function create(string $path): Promise 199 | { 200 | return new Promise(function ($resolve, $reject) use ($path): void { 201 | System::setTimeout(function () use ($resolve, $reject, $path): void { 202 | $callback = function ($path) use ($reject): void { 203 | !file_exists($path) ? touch($path) : $reject(Error::FILE_ALREADY_EXISTS); 204 | }; 205 | call_user_func($callback, $path); 206 | $resolve(); 207 | }, 0); 208 | }); 209 | } 210 | 211 | /** 212 | * @throws Throwable 213 | */ 214 | public static function overWrite(string $path, string $data): Promise 215 | { 216 | return new Promise(function ($resolve, $reject) use ($path, $data): void { 217 | System::setTimeout(function () use ($resolve, $reject, $path, $data): void { 218 | $callback = function ($path, $data) use ($reject): void { 219 | $handle = fopen($path, 'w+'); 220 | if ($handle === false) { 221 | $reject(Error::UNABLE_TO_OPEN_FILE); 222 | } else { 223 | stream_set_blocking($handle, false); 224 | fwrite($handle, $data); 225 | fclose($handle); 226 | } 227 | }; 228 | 229 | call_user_func($callback, $path, $data); 230 | $resolve(); 231 | }, 0); 232 | }); 233 | } 234 | 235 | /** 236 | * @param array $array 237 | * @throws Throwable 238 | */ 239 | public static function flattenArray(array $array): Promise 240 | { 241 | return new Promise(function ($resolve, $reject) use ($array) { 242 | $result = []; 243 | $stack = [$array]; 244 | 245 | while (!empty($stack)) { 246 | $element = array_shift($stack); 247 | if ($element === null) { 248 | $reject(Error::INVALID_ARRAY); 249 | break; 250 | } 251 | 252 | foreach ($element as $value) { 253 | is_array($value) ? array_unshift($stack, $value) : $result[] = $value; 254 | FiberManager::wait(); 255 | } 256 | FiberManager::wait(); 257 | } 258 | 259 | $resolve($result); 260 | }); 261 | } 262 | 263 | } -------------------------------------------------------------------------------- /src/vennv/vapm/System.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use Throwable; 27 | use function curl_init; 28 | use function curl_multi_add_handle; 29 | use function curl_multi_close; 30 | use function curl_multi_exec; 31 | use function curl_multi_getcontent; 32 | use function curl_multi_init; 33 | use function curl_multi_remove_handle; 34 | use function file_get_contents; 35 | use const CURLM_OK; 36 | use const CURLOPT_RETURNTRANSFER; 37 | 38 | interface SystemInterface 39 | { 40 | 41 | /** 42 | * @throws Throwable 43 | * 44 | * This function is used to run the event loop with multiple event loops 45 | */ 46 | public static function runEventLoop(): void; 47 | 48 | /** 49 | * @throws Throwable 50 | * 51 | * This function is used to run the event loop with single event loop 52 | */ 53 | public static function runSingleEventLoop(): void; 54 | 55 | /** 56 | * @throws Throwable 57 | * 58 | * This function is used to initialize the event loop 59 | */ 60 | public static function init(): void; 61 | 62 | /** 63 | * This function is used to run a callback in the event loop with timeout 64 | */ 65 | public static function setTimeout(callable $callback, int $timeout): SampleMacro; 66 | 67 | /** 68 | * This function is used to clear the timeout 69 | */ 70 | public static function clearTimeout(SampleMacro $sampleMacro): void; 71 | 72 | /** 73 | * This function is used to run a callback in the event loop with interval 74 | */ 75 | public static function setInterval(callable $callback, int $interval): SampleMacro; 76 | 77 | /** 78 | * This function is used to clear the interval 79 | */ 80 | public static function clearInterval(SampleMacro $sampleMacro): void; 81 | 82 | /** 83 | * @param string $url 84 | * @param array $options 85 | * @return Promise when Promise resolve InternetRequestResult and when Promise reject Error 86 | * @throws Throwable 87 | * @phpstan-param array{method?: string, headers?: array, timeout?: int, body?: array} $options 88 | */ 89 | public static function fetch(string $url, array $options = []): Promise; 90 | 91 | /** 92 | * @param string ...$curls 93 | * @return Promise 94 | * @throws Throwable 95 | * 96 | * Use this to curl multiple addresses at once 97 | */ 98 | public static function fetchAll(string ...$curls): Promise; 99 | 100 | /** 101 | * @throws Throwable 102 | * 103 | * This is a function used only to retrieve results from an address or file path via the file_get_contents method 104 | */ 105 | public static function read(string $path): Promise; 106 | 107 | /** 108 | * @param string $name 109 | * @return void 110 | * 111 | * This function is used to start a timer 112 | */ 113 | public static function time(string $name = 'Console'): void; 114 | 115 | /** 116 | * @param string $name 117 | * @return void 118 | * 119 | * This function is used to end a timer 120 | */ 121 | public static function timeEnd(string $name = 'Console'): void; 122 | 123 | } 124 | 125 | final class System extends EventLoop implements SystemInterface 126 | { 127 | 128 | /** 129 | * @var array 130 | */ 131 | private static array $timings = []; 132 | 133 | private static bool $hasInit = false; 134 | 135 | /** 136 | * @throws Throwable 137 | */ 138 | public static function runEventLoop(): void 139 | { 140 | self::init(); 141 | parent::run(); 142 | } 143 | 144 | /** 145 | * @throws Throwable 146 | */ 147 | public static function runSingleEventLoop(): void 148 | { 149 | self::init(); 150 | parent::runSingle(); 151 | } 152 | 153 | public static function init(): void 154 | { 155 | if (!self::$hasInit) { 156 | self::$hasInit = true; 157 | register_shutdown_function(fn() => self::runSingleEventLoop()); 158 | register_tick_function(fn() => CoroutineGen::run()); 159 | } 160 | 161 | parent::init(); 162 | } 163 | 164 | /** 165 | * @throws Throwable 166 | */ 167 | public static function setTimeout(callable $callback, int $timeout): SampleMacro 168 | { 169 | self::init(); 170 | $sampleMacro = new SampleMacro($callback, $timeout); 171 | MacroTask::addTask($sampleMacro); 172 | return $sampleMacro; 173 | } 174 | 175 | public static function clearTimeout(SampleMacro $sampleMacro): void 176 | { 177 | if ($sampleMacro->isRunning() && !$sampleMacro->isRepeat()) $sampleMacro->stop(); 178 | } 179 | 180 | /** 181 | * @throws Throwable 182 | */ 183 | public static function setInterval(callable $callback, int $interval): SampleMacro 184 | { 185 | self::init(); 186 | $sampleMacro = new SampleMacro($callback, $interval, true); 187 | MacroTask::addTask($sampleMacro); 188 | return $sampleMacro; 189 | } 190 | 191 | public static function clearInterval(SampleMacro $sampleMacro): void 192 | { 193 | if ($sampleMacro->isRunning() && $sampleMacro->isRepeat()) $sampleMacro->stop(); 194 | } 195 | 196 | /** 197 | * @param string $url 198 | * @param array $options 199 | * @return Promise when Promise resolve InternetRequestResult and when Promise reject Error 200 | * @throws Throwable 201 | * @phpstan-param array{method?: string, headers?: array, timeout?: int, body?: array} $options 202 | */ 203 | public static function fetch(string $url, array $options = []): Promise 204 | { 205 | return new Promise(function ($resolve, $reject) use ($url, $options) { 206 | self::setTimeout(function () use ($resolve, $reject, $url, $options) { 207 | $method = $options["method"] ?? "GET"; 208 | 209 | /** @var array $headers */ 210 | $headers = $options["headers"] ?? []; 211 | 212 | /** @var int $timeout */ 213 | $timeout = $options["timeout"] ?? 10; 214 | 215 | /** @var array $body */ 216 | $body = $options["body"] ?? []; 217 | 218 | $method === "GET" ? $result = Internet::getURL($url, $timeout, $headers) : $result = Internet::postURL($url, $body, $timeout, $headers); 219 | $result === null ? $reject(Error::FAILED_IN_FETCHING_DATA) : $resolve($result); 220 | }, 0); 221 | }); 222 | } 223 | 224 | /** 225 | * @param string ...$curls 226 | * @return Promise 227 | * @throws Throwable 228 | * 229 | * Use this to curl multiple addresses at once 230 | */ 231 | public static function fetchAll(string ...$curls): Promise 232 | { 233 | return new Promise(function ($resolve, $reject) use ($curls): void { 234 | $multiHandle = curl_multi_init(); 235 | $handles = []; 236 | foreach ($curls as $url) { 237 | $handle = curl_init($url); 238 | if ($handle === false) { 239 | $reject(Error::FAILED_IN_FETCHING_DATA); 240 | } else { 241 | curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); 242 | curl_multi_add_handle($multiHandle, $handle); 243 | $handles[] = $handle; 244 | } 245 | } 246 | 247 | $running = 0; 248 | 249 | do { 250 | $status = curl_multi_exec($multiHandle, $running); 251 | if ($status !== CURLM_OK) $reject(Error::FAILED_IN_FETCHING_DATA); 252 | FiberManager::wait(); 253 | } while ($running > 0); 254 | 255 | $results = []; 256 | foreach ($handles as $handle) { 257 | $results[] = curl_multi_getcontent($handle); 258 | curl_multi_remove_handle($multiHandle, $handle); 259 | } 260 | 261 | curl_multi_close($multiHandle); 262 | $resolve($results); 263 | }); 264 | } 265 | 266 | /** 267 | * @throws Throwable 268 | */ 269 | public static function read(string $path): Promise 270 | { 271 | return new Promise(function ($resolve, $reject) use ($path) { 272 | self::setTimeout(function () use ($resolve, $reject, $path) { 273 | $ch = file_get_contents($path); 274 | $ch === false ? $reject(Error::FAILED_IN_FETCHING_DATA) : $resolve($ch); 275 | }, 0); 276 | }); 277 | } 278 | 279 | public static function time(string $name = 'Console'): void 280 | { 281 | self::$timings[$name] = microtime(true); 282 | } 283 | 284 | public static function timeEnd(string $name = 'Console'): void 285 | { 286 | if (!isset(self::$timings[$name])) return; 287 | $time = microtime(true) - self::$timings[$name]; 288 | echo "Time for $name: $time\n"; 289 | unset(self::$timings[$name]); 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /src/vennv/vapm/utils/Utils.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm\utils; 25 | 26 | use vennv\vapm\Error; 27 | use Closure; 28 | use Generator; 29 | use RecursiveDirectoryIterator; 30 | use RecursiveIteratorIterator; 31 | use ReflectionException; 32 | use ReflectionFunction; 33 | use SplFileInfo; 34 | use RuntimeException; 35 | use function array_slice; 36 | use function file; 37 | use function implode; 38 | use function is_array; 39 | use function is_object; 40 | use function is_string; 41 | use function preg_match; 42 | use function serialize; 43 | use function strlen; 44 | use function strpos; 45 | use function substr; 46 | 47 | interface UtilsInterface 48 | { 49 | 50 | /** 51 | * Transform milliseconds to seconds 52 | */ 53 | public static function milliSecsToSecs(float $milliSecs): float; 54 | 55 | /** 56 | * @throws ReflectionException 57 | * 58 | * Transform a closure or callable to string 59 | */ 60 | public static function closureToString(Closure $closure): string; 61 | 62 | /** 63 | * @throws RuntimeException 64 | * @return string 65 | * 66 | * Transform a closure or callable to string 67 | */ 68 | public static function closureToStringSafe(Closure $closure): string; 69 | 70 | /** 71 | * Get all Dot files in a directory 72 | */ 73 | public static function getAllByDotFile(string $path, string $dotFile): Generator; 74 | 75 | /** 76 | * @return array|string 77 | * 78 | * Transform a string to inline 79 | */ 80 | public static function outlineToInline(string $text): array|string; 81 | 82 | /** 83 | * @return array|string 84 | * 85 | * Fix input command 86 | */ 87 | public static function fixInputCommand(string $text): array|string; 88 | 89 | /** 90 | * @return null|string|array 91 | * 92 | * Remove comments from a string 93 | */ 94 | public static function removeComments(string $text): null|string|array; 95 | 96 | /** 97 | * @param mixed $data 98 | * 99 | * Get bytes of a string or object or array 100 | */ 101 | public static function getBytes(mixed $data): int; 102 | 103 | /** 104 | * @return Generator 105 | * 106 | * Split a string by slash 107 | */ 108 | public static function splitStringBySlash(string $string): Generator; 109 | 110 | /** 111 | * @return false|string 112 | * 113 | * Replace path 114 | */ 115 | public static function replacePath(string $path, string $segment): false|string; 116 | 117 | /** 118 | * @return array|string|null 119 | * 120 | * Replace advanced 121 | */ 122 | public static function replaceAdvanced(string $text, string $search, string $replace): array|string|null; 123 | 124 | /** 125 | * @return Generator 126 | * 127 | * Evenly divide a number 128 | */ 129 | public static function evenlyDivide(int $number, int $parts): Generator; 130 | 131 | /** 132 | * @param array $array 133 | * @param int $size 134 | * @return Generator 135 | */ 136 | public static function splitArray(array $array, int $size): Generator; 137 | 138 | /** 139 | * @param string $class 140 | * @return bool 141 | * 142 | * This method is used to check if the current class is the same as the class passed in 143 | */ 144 | public static function isClass(string $class): bool; 145 | 146 | 147 | /** 148 | * @return string 149 | * 150 | * Get string after sign 151 | */ 152 | public static function getStringAfterSign(string $string, string $sign): string; 153 | 154 | /** 155 | * @param mixed $data 156 | * @return array 157 | * 158 | * Convert data to string 159 | */ 160 | public static function toStringAny(mixed $data): array; 161 | 162 | /** 163 | * @param array $data 164 | * @return mixed 165 | * 166 | * Convert data to real it's type 167 | */ 168 | public static function fromStringToAny(array $data): mixed; 169 | 170 | } 171 | 172 | final class Utils implements UtilsInterface 173 | { 174 | 175 | public static function milliSecsToSecs(float $milliSecs): float 176 | { 177 | return $milliSecs / 1000; 178 | } 179 | 180 | /** 181 | * @throws ReflectionException 182 | */ 183 | public static function closureToString(Closure $closure): string 184 | { 185 | $reflection = new ReflectionFunction($closure); 186 | $startLine = $reflection->getStartLine(); 187 | $endLine = $reflection->getEndLine(); 188 | $filename = $reflection->getFileName(); 189 | 190 | if ($filename === false || $startLine === false || $endLine === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); 191 | 192 | $lines = file($filename); 193 | if ($lines === false) throw new ReflectionException(Error::CANNOT_READ_FILE); 194 | 195 | $result = implode("", array_slice($lines, $startLine - 1, $endLine - $startLine + 1)); 196 | $startPos = strpos($result, 'function'); 197 | if ($startPos === false) { 198 | $startPos = strpos($result, 'fn'); 199 | if ($startPos === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); 200 | } 201 | 202 | $endBracketPos = strrpos($result, '}'); 203 | if ($endBracketPos === false) throw new ReflectionException(Error::CANNOT_FIND_FUNCTION_KEYWORD); 204 | 205 | return substr($result, $startPos, $endBracketPos - $startPos + 1); 206 | } 207 | 208 | /** 209 | * @throws RuntimeException 210 | * @return string 211 | */ 212 | public static function closureToStringSafe(Closure $closure): string 213 | { 214 | $input = self::closureToString($closure); 215 | $input = self::removeComments($input); 216 | 217 | if (!is_string($input)) throw new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE); 218 | 219 | $input = self::outlineToInline($input); 220 | 221 | if (!is_string($input)) throw new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE); 222 | 223 | $input = self::fixInputCommand($input); 224 | 225 | if (!is_string($input)) throw new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE); 226 | 227 | return $input; 228 | } 229 | 230 | public static function getAllByDotFile(string $path, string $dotFile): Generator 231 | { 232 | $dir = new RecursiveDirectoryIterator($path); 233 | $iterator = new RecursiveIteratorIterator($dir); 234 | 235 | foreach ($iterator as $file) { 236 | if ($file instanceof SplFileInfo && preg_match('%' . $dotFile . '$%', $file->getFilename()) === 1) yield $file->getPathname(); 237 | } 238 | } 239 | 240 | /** 241 | * @return array|string 242 | */ 243 | public static function outlineToInline(string $text): array|string 244 | { 245 | return str_replace(array("\r", "\n", "\t", ' '), '', $text); 246 | } 247 | 248 | /** 249 | * @return array|string 250 | */ 251 | public static function fixInputCommand(string $text): array|string 252 | { 253 | return str_replace('"', '\'', $text); 254 | } 255 | 256 | /** 257 | * @return null|string|array 258 | * 259 | * Remove comments from a string 260 | */ 261 | public static function removeComments(string $text): null|string|array 262 | { 263 | $text = preg_replace('/(?|string|null 309 | * 310 | * Replace advanced 311 | */ 312 | public static function replaceAdvanced(string $text, string $search, string $replace): array|string|null 313 | { 314 | return preg_replace('/(? 0 ? 1 : 0); 324 | $remainder--; 325 | } 326 | } 327 | 328 | /** 329 | * @param array $array 330 | * @param int $size 331 | * @return Generator 332 | */ 333 | public static function splitArray(array $array, int $size): Generator 334 | { 335 | $totalItems = count($array); 336 | $quotient = intdiv($totalItems, $size); 337 | $remainder = $totalItems % $size; 338 | 339 | $offset = 0; 340 | for ($i = 0; $i < $size; $i++) { 341 | $length = $quotient + ($remainder > 0 ? 1 : 0); 342 | 343 | yield array_slice($array, $offset, $length); 344 | 345 | $offset += $length; 346 | $remainder--; 347 | } 348 | } 349 | 350 | /** 351 | * @param string $class 352 | * @return bool 353 | * @throws ReflectionException 354 | */ 355 | public static function isClass(string $class): bool 356 | { 357 | $trace = debug_backtrace(); 358 | if (isset($trace[2])) { 359 | if (!empty($trace[2]['args'])) { 360 | $args = $trace[2]['args']; 361 | /** @var Closure $closure */ 362 | $closure = $args[0]; 363 | $reflectionFunction = new ReflectionFunction($closure); 364 | $scopeClass = $reflectionFunction->getClosureScopeClass(); 365 | if ($scopeClass === null) return false; 366 | return $scopeClass->getName() === $class; 367 | } else { 368 | return true; // This is a class 369 | } 370 | } 371 | return false; 372 | } 373 | 374 | /** 375 | * @return string 376 | * 377 | * Get string after sign 378 | */ 379 | public static function getStringAfterSign(string $string, string $sign): string 380 | { 381 | if (preg_match('/' . preg_quote($sign, '/') . '(.*)/s', $string, $matches)) return $matches[1]; 382 | return ''; 383 | } 384 | 385 | /** 386 | * @param mixed $data 387 | * @return array 388 | * 389 | * Convert data to string 390 | */ 391 | public static function toStringAny(mixed $data): array 392 | { 393 | $type = gettype($data); 394 | if (!is_callable($data) && (is_array($data) || is_object($data))) { 395 | return [$type => json_encode($data)]; 396 | } elseif (is_bool($data)) { 397 | $data = $data ? 'true' : 'false'; 398 | return [$type => $data]; 399 | } elseif (is_resource($data)) { 400 | return [$type => get_resource_type($data)]; 401 | } elseif (is_null($data)) { 402 | return [$type => 'null']; 403 | } elseif (is_callable($data)) { 404 | /** @phpstan-ignore-next-line */ 405 | return ['callable' => self::closureToStringSafe($data)]; 406 | } elseif (is_string($data)) { 407 | return [$type => '\'' . $data . '\'']; 408 | } 409 | /** @phpstan-ignore-next-line */ 410 | return [$type => (string) $data]; 411 | } 412 | 413 | /** 414 | * @param array $data 415 | * @return mixed 416 | * 417 | * Convert data to real it's type 418 | */ 419 | public static function fromStringToAny(array $data): mixed 420 | { 421 | $type = array_keys($data)[0]; 422 | $value = array_values($data)[0]; 423 | return match ($type) { 424 | 'boolean' => $value === 'true', 425 | 'integer' => (int) $value, 426 | 'float' => (float) $value, 427 | 'double' => (float) $value, 428 | 'string' => $value, 429 | 'array' => json_decode($value, true), 430 | 'object' => json_decode($value), 431 | 'callable' => eval('return ' . $value . ';'), 432 | 'null' => null, 433 | default => $value, 434 | }; 435 | } 436 | 437 | } -------------------------------------------------------------------------------- /src/vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer; 14 | 15 | use Composer\Autoload\ClassLoader; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This class is copied in every Composer installed project and available to all 20 | * 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22 | * 23 | * To require its presence, you can require `composer-runtime-api ^2.0` 24 | * 25 | * @final 26 | */ 27 | class InstalledVersions 28 | { 29 | /** 30 | * @var mixed[]|null 31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null 32 | */ 33 | private static $installed; 34 | 35 | /** 36 | * @var bool|null 37 | */ 38 | private static $canGetVendors; 39 | 40 | /** 41 | * @var array[] 42 | * @psalm-var array}> 43 | */ 44 | private static $installedByVendor = array(); 45 | 46 | /** 47 | * Returns a list of all package names which are present, either by being installed, replaced or provided 48 | * 49 | * @return string[] 50 | * @psalm-return list 51 | */ 52 | public static function getInstalledPackages() 53 | { 54 | $packages = array(); 55 | foreach (self::getInstalled() as $installed) { 56 | $packages[] = array_keys($installed['versions']); 57 | } 58 | 59 | if (1 === \count($packages)) { 60 | return $packages[0]; 61 | } 62 | 63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 64 | } 65 | 66 | /** 67 | * Returns a list of all package names with a specific type e.g. 'library' 68 | * 69 | * @param string $type 70 | * @return string[] 71 | * @psalm-return list 72 | */ 73 | public static function getInstalledPackagesByType($type) 74 | { 75 | $packagesByType = array(); 76 | 77 | foreach (self::getInstalled() as $installed) { 78 | foreach ($installed['versions'] as $name => $package) { 79 | if (isset($package['type']) && $package['type'] === $type) { 80 | $packagesByType[] = $name; 81 | } 82 | } 83 | } 84 | 85 | return $packagesByType; 86 | } 87 | 88 | /** 89 | * Checks whether the given package is installed 90 | * 91 | * This also returns true if the package name is provided or replaced by another package 92 | * 93 | * @param string $packageName 94 | * @param bool $includeDevRequirements 95 | * @return bool 96 | */ 97 | public static function isInstalled($packageName, $includeDevRequirements = true) 98 | { 99 | foreach (self::getInstalled() as $installed) { 100 | if (isset($installed['versions'][$packageName])) { 101 | return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Checks whether the given package satisfies a version constraint 110 | * 111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112 | * 113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114 | * 115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116 | * @param string $packageName 117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 118 | * @return bool 119 | */ 120 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 121 | { 122 | $constraint = $parser->parseConstraints((string) $constraint); 123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 124 | 125 | return $provided->matches($constraint); 126 | } 127 | 128 | /** 129 | * Returns a version constraint representing all the range(s) which are installed for a given package 130 | * 131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132 | * whether a given version of a package is installed, and not just whether it exists 133 | * 134 | * @param string $packageName 135 | * @return string Version constraint usable with composer/semver 136 | */ 137 | public static function getVersionRanges($packageName) 138 | { 139 | foreach (self::getInstalled() as $installed) { 140 | if (!isset($installed['versions'][$packageName])) { 141 | continue; 142 | } 143 | 144 | $ranges = array(); 145 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 146 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 147 | } 148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 150 | } 151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 153 | } 154 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 156 | } 157 | 158 | return implode(' || ', $ranges); 159 | } 160 | 161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 162 | } 163 | 164 | /** 165 | * @param string $packageName 166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 167 | */ 168 | public static function getVersion($packageName) 169 | { 170 | foreach (self::getInstalled() as $installed) { 171 | if (!isset($installed['versions'][$packageName])) { 172 | continue; 173 | } 174 | 175 | if (!isset($installed['versions'][$packageName]['version'])) { 176 | return null; 177 | } 178 | 179 | return $installed['versions'][$packageName]['version']; 180 | } 181 | 182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 183 | } 184 | 185 | /** 186 | * @param string $packageName 187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 188 | */ 189 | public static function getPrettyVersion($packageName) 190 | { 191 | foreach (self::getInstalled() as $installed) { 192 | if (!isset($installed['versions'][$packageName])) { 193 | continue; 194 | } 195 | 196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 197 | return null; 198 | } 199 | 200 | return $installed['versions'][$packageName]['pretty_version']; 201 | } 202 | 203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 204 | } 205 | 206 | /** 207 | * @param string $packageName 208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209 | */ 210 | public static function getReference($packageName) 211 | { 212 | foreach (self::getInstalled() as $installed) { 213 | if (!isset($installed['versions'][$packageName])) { 214 | continue; 215 | } 216 | 217 | if (!isset($installed['versions'][$packageName]['reference'])) { 218 | return null; 219 | } 220 | 221 | return $installed['versions'][$packageName]['reference']; 222 | } 223 | 224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 225 | } 226 | 227 | /** 228 | * @param string $packageName 229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 230 | */ 231 | public static function getInstallPath($packageName) 232 | { 233 | foreach (self::getInstalled() as $installed) { 234 | if (!isset($installed['versions'][$packageName])) { 235 | continue; 236 | } 237 | 238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239 | } 240 | 241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242 | } 243 | 244 | /** 245 | * @return array 246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247 | */ 248 | public static function getRootPackage() 249 | { 250 | $installed = self::getInstalled(); 251 | 252 | return $installed[0]['root']; 253 | } 254 | 255 | /** 256 | * Returns the raw installed.php data for custom implementations 257 | * 258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 259 | * @return array[] 260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} 261 | */ 262 | public static function getRawData() 263 | { 264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 265 | 266 | if (null === self::$installed) { 267 | // only require the installed.php file if this file is loaded from its dumped location, 268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269 | if (substr(__DIR__, -8, 1) !== 'C') { 270 | self::$installed = include __DIR__ . '/installed.php'; 271 | } else { 272 | self::$installed = array(); 273 | } 274 | } 275 | 276 | return self::$installed; 277 | } 278 | 279 | /** 280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations 281 | * 282 | * @return array[] 283 | * @psalm-return list}> 284 | */ 285 | public static function getAllRawData() 286 | { 287 | return self::getInstalled(); 288 | } 289 | 290 | /** 291 | * Lets you reload the static array from another file 292 | * 293 | * This is only useful for complex integrations in which a project needs to use 294 | * this class but then also needs to execute another project's autoloader in process, 295 | * and wants to ensure both projects have access to their version of installed.php. 296 | * 297 | * A typical case would be PHPUnit, where it would need to make sure it reads all 298 | * the data it needs from this class, then call reload() with 299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300 | * the project in which it runs can then also use this class safely, without 301 | * interference between PHPUnit's dependencies and the project's dependencies. 302 | * 303 | * @param array[] $data A vendor/composer/installed.php data set 304 | * @return void 305 | * 306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data 307 | */ 308 | public static function reload($data) 309 | { 310 | self::$installed = $data; 311 | self::$installedByVendor = array(); 312 | } 313 | 314 | /** 315 | * @return array[] 316 | * @psalm-return list}> 317 | */ 318 | private static function getInstalled() 319 | { 320 | if (null === self::$canGetVendors) { 321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 322 | } 323 | 324 | $installed = array(); 325 | 326 | if (self::$canGetVendors) { 327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 328 | if (isset(self::$installedByVendor[$vendorDir])) { 329 | $installed[] = self::$installedByVendor[$vendorDir]; 330 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 331 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 332 | $required = require $vendorDir.'/composer/installed.php'; 333 | $installed[] = self::$installedByVendor[$vendorDir] = $required; 334 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 335 | self::$installed = $installed[count($installed) - 1]; 336 | } 337 | } 338 | } 339 | } 340 | 341 | if (null === self::$installed) { 342 | // only require the installed.php file if this file is loaded from its dumped location, 343 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 344 | if (substr(__DIR__, -8, 1) !== 'C') { 345 | /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ 346 | $required = require __DIR__ . '/installed.php'; 347 | self::$installed = $required; 348 | } else { 349 | self::$installed = array(); 350 | } 351 | } 352 | 353 | if (self::$installed !== array()) { 354 | $installed[] = self::$installed; 355 | } 356 | 357 | return $installed; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/vennv/vapm/Thread.php: -------------------------------------------------------------------------------- 1 | = 8.1 8 | * 9 | * Copyright (C) 2023 VennDev 10 | * 11 | * This program is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 2 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | */ 21 | 22 | declare(strict_types=1); 23 | 24 | namespace vennv\vapm; 25 | 26 | use vennv\vapm\utils\Utils; 27 | use Closure; 28 | use ReflectionClass; 29 | use ReflectionException; 30 | use RuntimeException; 31 | use InvalidArgumentException; 32 | use Throwable; 33 | use function strlen; 34 | use function rtrim; 35 | use function fclose; 36 | use function fwrite; 37 | use function get_called_class; 38 | use function is_array; 39 | use function is_callable; 40 | use function is_resource; 41 | use function is_string; 42 | use function json_decode; 43 | use function json_encode; 44 | use function proc_get_status; 45 | use function proc_open; 46 | use function str_replace; 47 | use function stream_get_contents; 48 | use function stream_set_blocking; 49 | use const PHP_BINARY; 50 | use const PHP_EOL; 51 | use const STDIN; 52 | use const STDOUT; 53 | 54 | interface ThreadInterface 55 | { 56 | 57 | /** 58 | * This abstract method use to run the thread 59 | */ 60 | public function onRun(): void; 61 | 62 | /** 63 | * @param array> $mode 64 | * @throws ReflectionException 65 | * @throws Throwable 66 | * @phpstan-param array> $mode 67 | * 68 | * This method use to start the thread 69 | */ 70 | public function start(array $mode = DescriptorSpec::BASIC): Promise; 71 | 72 | } 73 | 74 | interface ThreadedInterface 75 | { 76 | 77 | /** 78 | * @return mixed 79 | */ 80 | public function getInput(): mixed; 81 | 82 | /** 83 | * This method use to get the pid of the thread 84 | */ 85 | public function getPid(): int; 86 | 87 | /** 88 | * This method use to get the exit code of the thread 89 | */ 90 | public function getExitCode(): int; 91 | 92 | /* 93 | * This method use to get the running status of the thread 94 | */ 95 | public function isRunning(): bool; 96 | 97 | /** 98 | * This method use to get the signaled status of the thread 99 | */ 100 | public function isSignaled(): bool; 101 | 102 | /** 103 | * This method use to get the stopped status of the thread 104 | */ 105 | public function isStopped(): bool; 106 | 107 | /** 108 | * @return array 109 | * @phpstan-return array 110 | * 111 | * This method use to get the shared data of the main thread 112 | */ 113 | public static function getDataMainThread(): array; 114 | 115 | /** 116 | * @param array $shared 117 | * @phpstan-param array $shared 118 | * 119 | * This method use to set the shared data of the main thread 120 | */ 121 | public static function setShared(array $shared): void; 122 | 123 | /** 124 | * @param string $key 125 | * @param mixed $value 126 | * @phpstan-param mixed $value 127 | * 128 | * This method use to add the shared data of the MAIN-THREAD 129 | */ 130 | public static function addShared(string $key, mixed $value): void; 131 | 132 | /** 133 | * @return array 134 | * 135 | * This method use to get the shared data of the child thread 136 | */ 137 | public static function getSharedData(): array; 138 | 139 | /** 140 | * @param array $data 141 | * @return void 142 | * @phpstan-param array $data 143 | * 144 | * This method use to post all data the main thread 145 | */ 146 | public static function postMainThread(array $data): void; 147 | 148 | /** 149 | * @param string $data 150 | * @return void 151 | * 152 | * This method use to load the shared data from the main thread 153 | */ 154 | public static function loadSharedData(string $data): void; 155 | 156 | /** 157 | * @param string $data 158 | * @return void 159 | * 160 | * This method use to post the data on the thread 161 | */ 162 | public static function post(string $data): void; 163 | 164 | /** 165 | * @param int $pid 166 | * @return bool 167 | * 168 | * This method use to check the thread is running or not 169 | */ 170 | public static function threadIsRunning(int $pid): bool; 171 | 172 | /** 173 | * @param int $pid 174 | * @return bool 175 | * 176 | * This method use to kill the thread 177 | */ 178 | public static function killThread(int $pid): bool; 179 | 180 | } 181 | 182 | abstract class Thread implements ThreadInterface, ThreadedInterface 183 | { 184 | 185 | private const POST_MAIN_THREAD = 'postMainThread'; // example: postMainThread=>{data} 186 | 187 | private const POST_THREAD = 'postThread'; // example: postAlertThread=>{data} 188 | 189 | private int $pid = -1; 190 | 191 | private int $exitCode = -1; 192 | 193 | private bool $isRunning = false; 194 | 195 | private bool $signaled = false; 196 | 197 | private bool $stopped = false; 198 | 199 | /** 200 | * @var array 201 | * @phpstan-var array 202 | */ 203 | private static array $shared = []; 204 | 205 | /** 206 | * @var array 207 | * @phpstan-var array 208 | */ 209 | private static array $threads = []; 210 | 211 | /** 212 | * @var array 213 | * @phpstan-var array 214 | */ 215 | private static array $inputs = []; 216 | 217 | /** 218 | * @var array 219 | * @phpstan-var array 220 | */ 221 | private static array $args = []; 222 | 223 | /** 224 | * @param mixed $input 225 | * @param array $args 226 | * @phpstan-param array $args 227 | */ 228 | public function __construct(mixed $input = '', array $args = []) 229 | { 230 | self::$inputs[$this->getCalledClassId()] = $input; 231 | self::$args[$this->getCalledClassId()] = $args; 232 | } 233 | 234 | private function getCalledClassId(): int 235 | { 236 | return spl_object_id($this); 237 | } 238 | 239 | public function getInput(): mixed 240 | { 241 | return self::$inputs[$this->getCalledClassId()]; 242 | } 243 | 244 | public function getPid(): int 245 | { 246 | return $this->pid; 247 | } 248 | 249 | public function setPid(int $pid): void 250 | { 251 | $this->pid = $pid; 252 | } 253 | 254 | public function getExitCode(): int 255 | { 256 | return $this->exitCode; 257 | } 258 | 259 | protected function setExitCode(int $exitCode): void 260 | { 261 | $this->exitCode = $exitCode; 262 | } 263 | 264 | public function isRunning(): bool 265 | { 266 | return $this->isRunning; 267 | } 268 | 269 | protected function setRunning(bool $isRunning): void 270 | { 271 | $this->isRunning = $isRunning; 272 | } 273 | 274 | public function isSignaled(): bool 275 | { 276 | return $this->signaled; 277 | } 278 | 279 | protected function setSignaled(bool $signaled): void 280 | { 281 | $this->signaled = $signaled; 282 | } 283 | 284 | public function isStopped(): bool 285 | { 286 | return $this->stopped; 287 | } 288 | 289 | protected function setStopped(bool $stopped): void 290 | { 291 | $this->stopped = $stopped; 292 | } 293 | 294 | /** 295 | * @return array 296 | * @phpstan-return array 297 | */ 298 | public static function getDataMainThread(): array 299 | { 300 | return self::$shared; 301 | } 302 | 303 | /** 304 | * @param array $shared 305 | * @phpstan-param array $shared 306 | */ 307 | public static function setShared(array $shared): void 308 | { 309 | self::$shared = $shared; 310 | } 311 | 312 | public static function addShared(string $key, mixed $value): void 313 | { 314 | self::$shared[$key] = $value; 315 | } 316 | 317 | /** 318 | * @return array 319 | * @phpstan-return array 320 | */ 321 | public static function getSharedData(): array 322 | { 323 | $data = fgets(STDIN); 324 | 325 | if (is_string($data)) { 326 | $data = json_decode($data, true); 327 | if (is_array($data)) return $data; 328 | } 329 | 330 | return []; 331 | } 332 | 333 | /** 334 | * @param array $data 335 | * @phpstan-param array $data 336 | */ 337 | public static function postMainThread(array $data): void 338 | { 339 | fwrite(STDOUT, self::POST_MAIN_THREAD . '=>' . json_encode($data) . PHP_EOL); 340 | } 341 | 342 | private static function isPostMainThread(string $data): bool 343 | { 344 | return strlen(Utils::getStringAfterSign($data, self::POST_MAIN_THREAD . '=>')) > 0; 345 | } 346 | 347 | private static function isPostThread(string $data): bool 348 | { 349 | return strlen(Utils::getStringAfterSign($data, self::POST_THREAD . '=>')) > 0; 350 | } 351 | 352 | public static function loadSharedData(string $data): void 353 | { 354 | if (self::isPostMainThread($data)) { 355 | $result = json_decode(Utils::getStringAfterSign($data, self::POST_MAIN_THREAD . '=>'), true); 356 | if (is_array($result)) self::setShared(array_merge(self::$shared, $result)); 357 | } 358 | } 359 | 360 | public static function post(string $data): void 361 | { 362 | fwrite(STDOUT, self::POST_THREAD . '=>' . $data . PHP_EOL); 363 | } 364 | 365 | public static function threadIsRunning(int $pid): bool 366 | { 367 | return isset(self::$threads[$pid]); 368 | } 369 | 370 | public static function killThread(int $pid): bool 371 | { 372 | if (isset(self::$threads[$pid])) { 373 | $thread = self::$threads[$pid]; 374 | 375 | if ($thread->isRunning()) { 376 | $thread->setStopped(true); 377 | return true; 378 | } 379 | } 380 | 381 | return false; 382 | } 383 | 384 | abstract public function onRun(): void; 385 | 386 | /** 387 | * @param array> $mode 388 | * @return Promise 389 | * @throws ReflectionException 390 | * @throws Throwable 391 | * @phpstan-param array> $mode 392 | * @phpstan-return Promise 393 | */ 394 | public function start(array $mode = DescriptorSpec::BASIC): Promise 395 | { 396 | return new Promise(function ($resolve, $reject) use ($mode): mixed { 397 | $idCall = $this->getCalledClassId(); 398 | 399 | $className = get_called_class(); 400 | 401 | $reflection = new ReflectionClass($className); 402 | 403 | $class = $reflection->getFileName(); 404 | 405 | $pathAutoLoad = __FILE__; 406 | $pathAutoLoad = str_replace( 407 | 'src\vennv\vapm\Thread.php', 408 | 'src\vendor\autoload.php', 409 | $pathAutoLoad 410 | ); 411 | 412 | $input = self::$inputs[$idCall]; 413 | 414 | if (is_string($input)) $input = '\'' . self::$inputs[$idCall] . '\''; 415 | 416 | if (is_callable($input) && $input instanceof Closure) { 417 | try { 418 | $input = Utils::closureToStringSafe($input); 419 | } catch (Throwable $e) { 420 | return $reject(new ThreadException($e->getMessage())); 421 | } 422 | } 423 | 424 | if (!is_string($input)) return $reject(new RuntimeException(Error::INPUT_MUST_BE_STRING_OR_CALLABLE)); 425 | 426 | $args = self::$args[$idCall]; 427 | 428 | if (is_array($args)) { 429 | foreach ($args as $key => $arg) { 430 | $tryToString = Utils::toStringAny($arg); 431 | $args[$key] = array_values($tryToString)[0]; 432 | FiberManager::wait(); 433 | } 434 | } else { 435 | throw new InvalidArgumentException('Expected $args to be an array or Traversable.'); 436 | } 437 | 438 | $args = '[' . implode(', ', array_map(function($item) { return '' . $item . ''; }, $args)) . ']'; 439 | $args = str_replace('"', '\'', $args); 440 | 441 | $command = PHP_BINARY . ' -r "require_once \'' . $pathAutoLoad . '\'; include \'' . $class . '\'; $input = ' . $input . '; $args = ' . $args . '; $class = new ' . static::class . '($input, $args); $class->onRun();"'; 442 | 443 | unset(self::$inputs[$idCall]); 444 | unset(self::$args[$idCall]); 445 | 446 | $process = proc_open($command, $mode, $pipes); 447 | 448 | $timeStart = microtime(true); 449 | while (microtime(true) - $timeStart <= 1) { 450 | FiberManager::wait(); // wait for 1 second 451 | } 452 | 453 | $output = ''; 454 | $error = ''; 455 | 456 | if (is_resource($process)) { 457 | stream_set_blocking($pipes[0], false); 458 | stream_set_blocking($pipes[1], false); 459 | stream_set_blocking($pipes[2], false); 460 | 461 | stream_set_write_buffer($pipes[0], 0); 462 | stream_set_write_buffer($pipes[1], 0); 463 | stream_set_write_buffer($pipes[2], 0); 464 | 465 | $data = json_encode(self::getDataMainThread()); 466 | 467 | if (is_string($data)) @fwrite($pipes[0], $data); 468 | @fclose($pipes[0]); 469 | 470 | $status = proc_get_status($process); 471 | $pid = $status['pid']; 472 | if (!isset(self::$threads[$pid])) { 473 | $this->setPid($pid); 474 | self::$threads[$pid] = $this; 475 | } 476 | 477 | $thread = self::$threads[$pid]; 478 | $thread->setExitCode($status['exitcode']); 479 | $thread->setRunning($status['running']); 480 | $thread->setSignaled($status['signaled']); 481 | $thread->setStopped($status['stopped']); 482 | while ($thread->isRunning()) { 483 | $status = proc_get_status($process); 484 | $thread->setExitCode($status['exitcode']); 485 | $thread->setRunning($status['running']); 486 | $thread->setSignaled($status['signaled']); 487 | $thread->setStopped($status['stopped']); 488 | 489 | if ($thread->isRunning()) { 490 | $read = [$pipes[1], $pipes[2]]; 491 | $write = null; 492 | $except = null; 493 | $timeout = 0; 494 | $n = stream_select($read, $write, $except, $timeout); 495 | if ($n === false) break; 496 | if ($n > 0) { 497 | foreach ($read as $stream) { 498 | if (!feof($stream)) { 499 | $data = stream_get_contents($stream); 500 | if ($data !== '') { 501 | $stream === $pipes[1] ? $output .= $data : $error .= $data; 502 | } 503 | } 504 | FiberManager::wait(); 505 | } 506 | } 507 | } elseif ($thread->isStopped() || $thread->isSignaled()) { 508 | proc_terminate($process); 509 | break; 510 | } else { 511 | proc_terminate($process); 512 | break; 513 | } 514 | FiberManager::wait(); 515 | } 516 | 517 | $outputStream = stream_get_contents($pipes[1]); 518 | $errorStream = stream_get_contents($pipes[2]); 519 | if (!is_bool($outputStream)) $output .= str_contains($output, $outputStream) ? '' : $outputStream; 520 | if (!is_bool($errorStream)) $error .= str_contains($error, $errorStream) ? '' : $errorStream; 521 | 522 | fclose($pipes[1]); 523 | fclose($pipes[2]); 524 | 525 | if ($error !== '') { 526 | return $reject(new ThreadException($error)); 527 | } else { 528 | if ($output !== '' && self::isPostMainThread($output)) self::loadSharedData($output); 529 | elseif ($output !== '' && self::isPostThread($output)) { 530 | $output = rtrim(Utils::getStringAfterSign($output, self::POST_THREAD . '=>')); 531 | } 532 | } 533 | } else { 534 | return $reject(new ThreadException(Error::UNABLE_START_THREAD)); 535 | } 536 | 537 | proc_close($process); 538 | unset(self::$threads[$this->getPid()]); 539 | return $resolve($output); 540 | }); 541 | } 542 | 543 | } -------------------------------------------------------------------------------- /src/vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var \Closure(string):void */ 46 | private static $includeFile; 47 | 48 | /** @var ?string */ 49 | private $vendorDir; 50 | 51 | // PSR-4 52 | /** 53 | * @var array[] 54 | * @psalm-var array> 55 | */ 56 | private $prefixLengthsPsr4 = array(); 57 | /** 58 | * @var array[] 59 | * @psalm-var array> 60 | */ 61 | private $prefixDirsPsr4 = array(); 62 | /** 63 | * @var array[] 64 | * @psalm-var array 65 | */ 66 | private $fallbackDirsPsr4 = array(); 67 | 68 | // PSR-0 69 | /** 70 | * @var array[] 71 | * @psalm-var array> 72 | */ 73 | private $prefixesPsr0 = array(); 74 | /** 75 | * @var array[] 76 | * @psalm-var array 77 | */ 78 | private $fallbackDirsPsr0 = array(); 79 | 80 | /** @var bool */ 81 | private $useIncludePath = false; 82 | 83 | /** 84 | * @var string[] 85 | * @psalm-var array 86 | */ 87 | private $classMap = array(); 88 | 89 | /** @var bool */ 90 | private $classMapAuthoritative = false; 91 | 92 | /** 93 | * @var bool[] 94 | * @psalm-var array 95 | */ 96 | private $missingClasses = array(); 97 | 98 | /** @var ?string */ 99 | private $apcuPrefix; 100 | 101 | /** 102 | * @var self[] 103 | */ 104 | private static $registeredLoaders = array(); 105 | 106 | /** 107 | * @param ?string $vendorDir 108 | */ 109 | public function __construct($vendorDir = null) 110 | { 111 | $this->vendorDir = $vendorDir; 112 | self::initializeIncludeClosure(); 113 | } 114 | 115 | /** 116 | * @return string[] 117 | */ 118 | public function getPrefixes() 119 | { 120 | if (!empty($this->prefixesPsr0)) { 121 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 122 | } 123 | 124 | return array(); 125 | } 126 | 127 | /** 128 | * @return array[] 129 | * @psalm-return array> 130 | */ 131 | public function getPrefixesPsr4() 132 | { 133 | return $this->prefixDirsPsr4; 134 | } 135 | 136 | /** 137 | * @return array[] 138 | * @psalm-return array 139 | */ 140 | public function getFallbackDirs() 141 | { 142 | return $this->fallbackDirsPsr0; 143 | } 144 | 145 | /** 146 | * @return array[] 147 | * @psalm-return array 148 | */ 149 | public function getFallbackDirsPsr4() 150 | { 151 | return $this->fallbackDirsPsr4; 152 | } 153 | 154 | /** 155 | * @return string[] Array of classname => path 156 | * @psalm-return array 157 | */ 158 | public function getClassMap() 159 | { 160 | return $this->classMap; 161 | } 162 | 163 | /** 164 | * @param string[] $classMap Class to filename map 165 | * @psalm-param array $classMap 166 | * 167 | * @return void 168 | */ 169 | public function addClassMap(array $classMap) 170 | { 171 | if ($this->classMap) { 172 | $this->classMap = array_merge($this->classMap, $classMap); 173 | } else { 174 | $this->classMap = $classMap; 175 | } 176 | } 177 | 178 | /** 179 | * Registers a set of PSR-0 directories for a given prefix, either 180 | * appending or prepending to the ones previously set for this prefix. 181 | * 182 | * @param string $prefix The prefix 183 | * @param string[]|string $paths The PSR-0 root directories 184 | * @param bool $prepend Whether to prepend the directories 185 | * 186 | * @return void 187 | */ 188 | public function add($prefix, $paths, $prepend = false) 189 | { 190 | if (!$prefix) { 191 | if ($prepend) { 192 | $this->fallbackDirsPsr0 = array_merge( 193 | (array) $paths, 194 | $this->fallbackDirsPsr0 195 | ); 196 | } else { 197 | $this->fallbackDirsPsr0 = array_merge( 198 | $this->fallbackDirsPsr0, 199 | (array) $paths 200 | ); 201 | } 202 | 203 | return; 204 | } 205 | 206 | $first = $prefix[0]; 207 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 208 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 209 | 210 | return; 211 | } 212 | if ($prepend) { 213 | $this->prefixesPsr0[$first][$prefix] = array_merge( 214 | (array) $paths, 215 | $this->prefixesPsr0[$first][$prefix] 216 | ); 217 | } else { 218 | $this->prefixesPsr0[$first][$prefix] = array_merge( 219 | $this->prefixesPsr0[$first][$prefix], 220 | (array) $paths 221 | ); 222 | } 223 | } 224 | 225 | /** 226 | * Registers a set of PSR-4 directories for a given namespace, either 227 | * appending or prepending to the ones previously set for this namespace. 228 | * 229 | * @param string $prefix The prefix/namespace, with trailing '\\' 230 | * @param string[]|string $paths The PSR-4 base directories 231 | * @param bool $prepend Whether to prepend the directories 232 | * 233 | * @throws \InvalidArgumentException 234 | * 235 | * @return void 236 | */ 237 | public function addPsr4($prefix, $paths, $prepend = false) 238 | { 239 | if (!$prefix) { 240 | // Register directories for the root namespace. 241 | if ($prepend) { 242 | $this->fallbackDirsPsr4 = array_merge( 243 | (array) $paths, 244 | $this->fallbackDirsPsr4 245 | ); 246 | } else { 247 | $this->fallbackDirsPsr4 = array_merge( 248 | $this->fallbackDirsPsr4, 249 | (array) $paths 250 | ); 251 | } 252 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 253 | // Register directories for a new namespace. 254 | $length = strlen($prefix); 255 | if ('\\' !== $prefix[$length - 1]) { 256 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 257 | } 258 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 259 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 260 | } elseif ($prepend) { 261 | // Prepend directories for an already registered namespace. 262 | $this->prefixDirsPsr4[$prefix] = array_merge( 263 | (array) $paths, 264 | $this->prefixDirsPsr4[$prefix] 265 | ); 266 | } else { 267 | // Append directories for an already registered namespace. 268 | $this->prefixDirsPsr4[$prefix] = array_merge( 269 | $this->prefixDirsPsr4[$prefix], 270 | (array) $paths 271 | ); 272 | } 273 | } 274 | 275 | /** 276 | * Registers a set of PSR-0 directories for a given prefix, 277 | * replacing any others previously set for this prefix. 278 | * 279 | * @param string $prefix The prefix 280 | * @param string[]|string $paths The PSR-0 base directories 281 | * 282 | * @return void 283 | */ 284 | public function set($prefix, $paths) 285 | { 286 | if (!$prefix) { 287 | $this->fallbackDirsPsr0 = (array) $paths; 288 | } else { 289 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 290 | } 291 | } 292 | 293 | /** 294 | * Registers a set of PSR-4 directories for a given namespace, 295 | * replacing any others previously set for this namespace. 296 | * 297 | * @param string $prefix The prefix/namespace, with trailing '\\' 298 | * @param string[]|string $paths The PSR-4 base directories 299 | * 300 | * @throws \InvalidArgumentException 301 | * 302 | * @return void 303 | */ 304 | public function setPsr4($prefix, $paths) 305 | { 306 | if (!$prefix) { 307 | $this->fallbackDirsPsr4 = (array) $paths; 308 | } else { 309 | $length = strlen($prefix); 310 | if ('\\' !== $prefix[$length - 1]) { 311 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 312 | } 313 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 314 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 315 | } 316 | } 317 | 318 | /** 319 | * Turns on searching the include path for class files. 320 | * 321 | * @param bool $useIncludePath 322 | * 323 | * @return void 324 | */ 325 | public function setUseIncludePath($useIncludePath) 326 | { 327 | $this->useIncludePath = $useIncludePath; 328 | } 329 | 330 | /** 331 | * Can be used to check if the autoloader uses the include path to check 332 | * for classes. 333 | * 334 | * @return bool 335 | */ 336 | public function getUseIncludePath() 337 | { 338 | return $this->useIncludePath; 339 | } 340 | 341 | /** 342 | * Turns off searching the prefix and fallback directories for classes 343 | * that have not been registered with the class map. 344 | * 345 | * @param bool $classMapAuthoritative 346 | * 347 | * @return void 348 | */ 349 | public function setClassMapAuthoritative($classMapAuthoritative) 350 | { 351 | $this->classMapAuthoritative = $classMapAuthoritative; 352 | } 353 | 354 | /** 355 | * Should class lookup fail if not found in the current class map? 356 | * 357 | * @return bool 358 | */ 359 | public function isClassMapAuthoritative() 360 | { 361 | return $this->classMapAuthoritative; 362 | } 363 | 364 | /** 365 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 366 | * 367 | * @param string|null $apcuPrefix 368 | * 369 | * @return void 370 | */ 371 | public function setApcuPrefix($apcuPrefix) 372 | { 373 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 374 | } 375 | 376 | /** 377 | * The APCu prefix in use, or null if APCu caching is not enabled. 378 | * 379 | * @return string|null 380 | */ 381 | public function getApcuPrefix() 382 | { 383 | return $this->apcuPrefix; 384 | } 385 | 386 | /** 387 | * Registers this instance as an autoloader. 388 | * 389 | * @param bool $prepend Whether to prepend the autoloader or not 390 | * 391 | * @return void 392 | */ 393 | public function register($prepend = false) 394 | { 395 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 396 | 397 | if (null === $this->vendorDir) { 398 | return; 399 | } 400 | 401 | if ($prepend) { 402 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 403 | } else { 404 | unset(self::$registeredLoaders[$this->vendorDir]); 405 | self::$registeredLoaders[$this->vendorDir] = $this; 406 | } 407 | } 408 | 409 | /** 410 | * Unregisters this instance as an autoloader. 411 | * 412 | * @return void 413 | */ 414 | public function unregister() 415 | { 416 | spl_autoload_unregister(array($this, 'loadClass')); 417 | 418 | if (null !== $this->vendorDir) { 419 | unset(self::$registeredLoaders[$this->vendorDir]); 420 | } 421 | } 422 | 423 | /** 424 | * Loads the given class or interface. 425 | * 426 | * @param string $class The name of the class 427 | * @return true|null True if loaded, null otherwise 428 | */ 429 | public function loadClass($class) 430 | { 431 | if ($file = $this->findFile($class)) { 432 | $includeFile = self::$includeFile; 433 | $includeFile($file); 434 | 435 | return true; 436 | } 437 | 438 | return null; 439 | } 440 | 441 | /** 442 | * Finds the path to the file where the class is defined. 443 | * 444 | * @param string $class The name of the class 445 | * 446 | * @return string|false The path if found, false otherwise 447 | */ 448 | public function findFile($class) 449 | { 450 | // class map lookup 451 | if (isset($this->classMap[$class])) { 452 | return $this->classMap[$class]; 453 | } 454 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 455 | return false; 456 | } 457 | if (null !== $this->apcuPrefix) { 458 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 459 | if ($hit) { 460 | return $file; 461 | } 462 | } 463 | 464 | $file = $this->findFileWithExtension($class, '.php'); 465 | 466 | // Search for Hack files if we are running on HHVM 467 | if (false === $file && defined('HHVM_VERSION')) { 468 | $file = $this->findFileWithExtension($class, '.hh'); 469 | } 470 | 471 | if (null !== $this->apcuPrefix) { 472 | apcu_add($this->apcuPrefix.$class, $file); 473 | } 474 | 475 | if (false === $file) { 476 | // Remember that this class does not exist. 477 | $this->missingClasses[$class] = true; 478 | } 479 | 480 | return $file; 481 | } 482 | 483 | /** 484 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 485 | * 486 | * @return self[] 487 | */ 488 | public static function getRegisteredLoaders() 489 | { 490 | return self::$registeredLoaders; 491 | } 492 | 493 | /** 494 | * @param string $class 495 | * @param string $ext 496 | * @return string|false 497 | */ 498 | private function findFileWithExtension($class, $ext) 499 | { 500 | // PSR-4 lookup 501 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 502 | 503 | $first = $class[0]; 504 | if (isset($this->prefixLengthsPsr4[$first])) { 505 | $subPath = $class; 506 | while (false !== $lastPos = strrpos($subPath, '\\')) { 507 | $subPath = substr($subPath, 0, $lastPos); 508 | $search = $subPath . '\\'; 509 | if (isset($this->prefixDirsPsr4[$search])) { 510 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 511 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 512 | if (file_exists($file = $dir . $pathEnd)) { 513 | return $file; 514 | } 515 | } 516 | } 517 | } 518 | } 519 | 520 | // PSR-4 fallback dirs 521 | foreach ($this->fallbackDirsPsr4 as $dir) { 522 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 523 | return $file; 524 | } 525 | } 526 | 527 | // PSR-0 lookup 528 | if (false !== $pos = strrpos($class, '\\')) { 529 | // namespaced class name 530 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 531 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 532 | } else { 533 | // PEAR-like class name 534 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 535 | } 536 | 537 | if (isset($this->prefixesPsr0[$first])) { 538 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 539 | if (0 === strpos($class, $prefix)) { 540 | foreach ($dirs as $dir) { 541 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 542 | return $file; 543 | } 544 | } 545 | } 546 | } 547 | } 548 | 549 | // PSR-0 fallback dirs 550 | foreach ($this->fallbackDirsPsr0 as $dir) { 551 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 552 | return $file; 553 | } 554 | } 555 | 556 | // PSR-0 include paths. 557 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 558 | return $file; 559 | } 560 | 561 | return false; 562 | } 563 | 564 | /** 565 | * @return void 566 | */ 567 | private static function initializeIncludeClosure() 568 | { 569 | if (self::$includeFile !== null) { 570 | return; 571 | } 572 | 573 | /** 574 | * Scope isolated include. 575 | * 576 | * Prevents access to $this/self from included files. 577 | * 578 | * @param string $file 579 | * @return void 580 | */ 581 | self::$includeFile = \Closure::bind(static function($file) { 582 | include $file; 583 | }, null, null); 584 | } 585 | } 586 | --------------------------------------------------------------------------------