├── .github ├── init.sql ├── workflows │ ├── build.yml │ ├── release.yml │ └── test.yml └── swoole.install.sh ├── .dockerignore ├── .gitignore ├── config ├── autoload │ ├── aspects.php │ ├── commands.php │ ├── middlewares.php │ ├── processes.php │ ├── exceptions.php │ ├── listeners.php │ ├── cache.php │ ├── async_queue.php │ ├── annotations.php │ ├── dependencies.php │ ├── redis.php │ ├── logger.php │ ├── devtool.php │ ├── databases.php │ └── server.php ├── routes.php ├── container.php └── config.php ├── .env.example ├── .phpstorm.meta.php ├── app ├── Constants │ ├── ErrorCodeInterface.php │ └── ErrorCode.php ├── Model │ └── Model.php ├── Kernel │ ├── Log │ │ ├── LoggerFactory.php │ │ └── AppendRequestIdProcessor.php │ ├── Http │ │ ├── WorkerStartListener.php │ │ └── Response.php │ ├── Event │ │ └── EventDispatcherFactory.php │ ├── Functions.php │ └── Context │ │ └── Coroutine.php ├── Controller │ ├── IndexController.php │ └── Controller.php ├── Exception │ ├── BusinessException.php │ └── Handler │ │ ├── RPCExceptionHandler.php │ │ └── BusinessExceptionHandler.php └── Listener │ ├── FailToHandleListener.php │ ├── DbQueryExecutedListener.php │ └── QueueHandleListener.php ├── deploy.test.yml ├── phpunit.xml.dist ├── test ├── bootstrap.php ├── HttpTestCase.php └── Cases │ └── ExampleTest.php ├── LICENSE ├── docker-compose.yml ├── bin └── hyperf.php ├── phpstan.neon.dist ├── Dockerfile ├── README.md ├── .gitlab-ci.yml ├── composer.json ├── storage └── classes │ ├── ResolverDispatcher.php │ └── Coroutine.php └── .php-cs-fixer.php /.github/init.sql: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | runtime 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .buildpath 2 | .settings/ 3 | .project 4 | *.patch 5 | .idea/ 6 | .git/ 7 | runtime/ 8 | vendor/ 9 | .phpintel/ 10 | .env 11 | .DS_Store 12 | *.cache 13 | *.lock 14 | -------------------------------------------------------------------------------- /config/autoload/aspects.php: -------------------------------------------------------------------------------- 1 | [ 14 | ], 15 | ]; 16 | -------------------------------------------------------------------------------- /config/autoload/processes.php: -------------------------------------------------------------------------------- 1 | '@'])); 6 | override(\Hyperf\Context\Context::get(0), map(['' => '@'])); 7 | override(\Hyperf\Support\make(0), map(['' => '@'])); 8 | override(\Hyperf\Support\optional(0), type(0)); 9 | override(\Hyperf\Tappable\tap(0), type(0)); 10 | 11 | override(\di(0), map(['' => '@'])); 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout code 10 | uses: actions/checkout@v2 11 | - name: Build 12 | run: | 13 | docker build -t biz-skeleton . 14 | docker run --name unit -d biz-skeleton:latest 15 | docker exec unit composer install 16 | docker exec unit composer test 17 | -------------------------------------------------------------------------------- /app/Constants/ErrorCodeInterface.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'http' => [ 17 | BusinessExceptionHandler::class, 18 | ], 19 | ], 20 | ]; 21 | -------------------------------------------------------------------------------- /config/autoload/listeners.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'driver' => RedisDriver::class, 18 | 'packer' => PhpSerializerPacker::class, 19 | 'prefix' => 'c:', 20 | ], 21 | ]; 22 | -------------------------------------------------------------------------------- /app/Model/Model.php: -------------------------------------------------------------------------------- 1 | get(HyperfLoggerFactory::class)->make(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config/autoload/async_queue.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'driver' => RedisDriver::class, 17 | 'channel' => '{queue}', 18 | 'timeout' => 2, 19 | 'retry_seconds' => 5, 20 | 'handle_timeout' => 10, 21 | 'processes' => 1, 22 | 'concurrent' => [ 23 | 'limit' => 2, 24 | ], 25 | ], 26 | ]; 27 | -------------------------------------------------------------------------------- /.github/swoole.install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | sudo apt-get update 3 | sudo apt-get install libcurl4-openssl-dev libc-ares-dev libpq-dev 4 | wget https://github.com/swoole/swoole-src/archive/${SW_VERSION}.tar.gz -O swoole.tar.gz 5 | mkdir -p swoole 6 | tar -xf swoole.tar.gz -C swoole --strip-components=1 7 | rm swoole.tar.gz 8 | cd swoole 9 | phpize 10 | ./configure --enable-openssl --enable-swoole-curl --enable-cares --enable-swoole-pgsql 11 | make -j$(nproc) 12 | sudo make install 13 | sudo sh -c "echo extension=swoole > /etc/php/${PHP_VERSION}/cli/conf.d/swoole.ini" 14 | sudo sh -c "echo swoole.use_shortname='Off' >> /etc/php/${PHP_VERSION}/cli/conf.d/swoole.ini" 15 | php --ri swoole 16 | cd .. 17 | rm -rf swoole 18 | -------------------------------------------------------------------------------- /deploy.test.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | hyperf: 4 | image: $REGISTRY_URL/$PROJECT_NAME:test 5 | environment: 6 | - "APP_PROJECT=hyperf" 7 | - "APP_ENV=test" 8 | ports: 9 | - "9501:9501" 10 | deploy: 11 | replicas: 1 12 | restart_policy: 13 | condition: on-failure 14 | delay: 5s 15 | max_attempts: 5 16 | update_config: 17 | parallelism: 2 18 | delay: 5s 19 | order: start-first 20 | networks: 21 | - hyperf_net 22 | configs: 23 | - source: hyperf_v1.0 24 | target: /opt/www/.env 25 | configs: 26 | hyperf_v1.0: 27 | external: true 28 | networks: 29 | hyperf_net: 30 | external: true 31 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | # Sequence of patterns matched against refs/tags 4 | tags: 5 | - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 6 | 7 | name: Release 8 | 9 | jobs: 10 | release: 11 | name: Release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | - name: Create Release 17 | id: create_release 18 | uses: actions/create-release@v1 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | with: 22 | tag_name: ${{ github.ref }} 23 | release_name: Release ${{ github.ref }} 24 | draft: false 25 | prerelease: false 26 | -------------------------------------------------------------------------------- /app/Kernel/Http/WorkerStartListener.php: -------------------------------------------------------------------------------- 1 | get(StdoutLogger::class)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Controller/IndexController.php: -------------------------------------------------------------------------------- 1 | request->input('user', 'Hyperf'); 20 | $method = $this->request->getMethod(); 21 | return $this->response->success([ 22 | 'user' => $user, 23 | 'method' => $method, 24 | 'message' => 'Hello Hyperf.', 25 | ]); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Kernel/Event/EventDispatcherFactory.php: -------------------------------------------------------------------------------- 1 | get(ListenerProviderInterface::class); 24 | return new EventDispatcher($listeners); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | test 16 | 17 | 18 | 19 | 20 | app 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /config/autoload/annotations.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'paths' => [ 18 | BASE_PATH . '/app', 19 | ], 20 | 'ignore_annotations' => [ 21 | 'mixin', 22 | ], 23 | 'class_map' => [ 24 | Coroutine::class => BASE_PATH . '/storage/classes/Coroutine.php', 25 | ResolverDispatcher::class => BASE_PATH . '/storage/classes/ResolverDispatcher.php', 26 | ], 27 | ], 28 | ]; 29 | -------------------------------------------------------------------------------- /config/autoload/dependencies.php: -------------------------------------------------------------------------------- 1 | LoggerFactory::class, 21 | AfterWorkerStartListener::class => WorkerStartListener::class, 22 | EventDispatcherInterface::class => EventDispatcherFactory::class, 23 | ]; 24 | -------------------------------------------------------------------------------- /app/Exception/BusinessException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 25 | } 26 | 27 | parent::__construct($message, $code->value, $previous); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Controller/Controller.php: -------------------------------------------------------------------------------- 1 | response = $container->get(Response::class); 28 | $this->request = $container->get(RequestInterface::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Kernel/Log/AppendRequestIdProcessor.php: -------------------------------------------------------------------------------- 1 | getThrowable(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /config/autoload/redis.php: -------------------------------------------------------------------------------- 1 | [ 16 | 'host' => env('REDIS_HOST', 'localhost'), 17 | 'auth' => env('REDIS_AUTH', null), 18 | 'port' => (int) env('REDIS_PORT', 6379), 19 | 'db' => (int) env('REDIS_DB', 0), 20 | 'pool' => [ 21 | 'min_connections' => 1, 22 | 'max_connections' => 32, 23 | 'connect_timeout' => 10.0, 24 | 'wait_timeout' => 3.0, 25 | 'heartbeat' => -1, 26 | 'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60), 27 | ], 28 | ], 29 | ]; 30 | -------------------------------------------------------------------------------- /config/config.php: -------------------------------------------------------------------------------- 1 | env('APP_NAME', 'skeleton'), 19 | 'app_env' => env('APP_ENV', 'dev'), 20 | 'scan_cacheable' => env('SCAN_CACHEABLE', false), 21 | StdoutLoggerInterface::class => [ 22 | 'log_level' => [ 23 | LogLevel::ALERT, 24 | LogLevel::CRITICAL, 25 | LogLevel::EMERGENCY, 26 | LogLevel::ERROR, 27 | LogLevel::INFO, 28 | LogLevel::NOTICE, 29 | LogLevel::WARNING, 30 | ], 31 | ], 32 | ]; 33 | -------------------------------------------------------------------------------- /app/Constants/ErrorCode.php: -------------------------------------------------------------------------------- 1 | __call('getMessage', $arguments); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | get(ApplicationInterface::class); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Hyperf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/autoload/logger.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'handler' => [ 20 | 'class' => StreamHandler::class, 21 | 'constructor' => [ 22 | 'stream' => BASE_PATH . '/runtime/logs/hyperf.log', 23 | 'level' => Level::Info, 24 | ], 25 | ], 26 | 'formatter' => [ 27 | 'class' => LineFormatter::class, 28 | 'constructor' => [ 29 | 'format' => null, 30 | 'dateFormat' => 'Y-m-d H:i:s', 31 | 'allowInlineLineBreaks' => true, 32 | ], 33 | ], 34 | 'processors' => [ 35 | [ 36 | 'class' => AppendRequestIdProcessor::class, 37 | ], 38 | ], 39 | ], 40 | ]; 41 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | mysql: 4 | image: "mysql/mysql-server:8.0" 5 | environment: 6 | TZ: "Asia/Shanghai" 7 | MYSQL_ALLOW_EMPTY_PASSWORD: "true" 8 | MYSQL_ROOT_HOST: "%" 9 | MYSQL_DATABASE: "hyperf" 10 | networks: 11 | - net 12 | volumes: 13 | - mysql-data:/var/lib/mysql 14 | - ./.github/init.sql:/docker-entrypoint-initdb.d/init.sql 15 | restart: "always" 16 | redis: 17 | image: "redis" 18 | command: 19 | - "redis-server" 20 | - "--databases 256" 21 | networks: 22 | - net 23 | volumes: 24 | - redis-data:/data 25 | restart: "always" 26 | hyperf: 27 | image: "hyperf/biz-skeleton:latest" 28 | build: 29 | context: "." 30 | depends_on: 31 | - mysql 32 | - redis 33 | environment: 34 | APP_ENV: "${APP_ENV:-prod}" 35 | DB_HOST: "mysql" 36 | REDIS_HOST: "redis" 37 | ports: 38 | - "9501:9501" 39 | volumes: 40 | - "./.env.example:/opt/www/.env" 41 | networks: 42 | - net 43 | restart: "always" 44 | deploy: 45 | replicas: 1 46 | networks: 47 | net: 48 | volumes: 49 | mysql-data: 50 | redis-data: 51 | -------------------------------------------------------------------------------- /config/autoload/devtool.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'amqp' => [ 15 | 'consumer' => [ 16 | 'namespace' => 'App\Amqp\Consumer', 17 | ], 18 | 'producer' => [ 19 | 'namespace' => 'App\Amqp\Producer', 20 | ], 21 | ], 22 | 'aspect' => [ 23 | 'namespace' => 'App\Aspect', 24 | ], 25 | 'command' => [ 26 | 'namespace' => 'App\Command', 27 | ], 28 | 'controller' => [ 29 | 'namespace' => 'App\Controller', 30 | ], 31 | 'job' => [ 32 | 'namespace' => 'App\Job', 33 | ], 34 | 'listener' => [ 35 | 'namespace' => 'App\Listener', 36 | ], 37 | 'middleware' => [ 38 | 'namespace' => 'App\Middleware', 39 | ], 40 | 'Process' => [ 41 | 'namespace' => 'App\Processes', 42 | ], 43 | ], 44 | ]; 45 | -------------------------------------------------------------------------------- /bin/hyperf.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | get(ApplicationInterface::class); 36 | $application->run(); 37 | })(); 38 | -------------------------------------------------------------------------------- /app/Exception/Handler/RPCExceptionHandler.php: -------------------------------------------------------------------------------- 1 | logger->warning($this->formatter->format($throwable)); 32 | } else { 33 | $this->logger->error($this->formatter->format($throwable)); 34 | } 35 | 36 | return $response; 37 | } 38 | 39 | public function isValid(Throwable $throwable): bool 40 | { 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | # Magic behaviour with __get, __set, __call and __callStatic is not exactly static analyser-friendly :) 2 | # Fortunately, You can ignore it by the following config. 3 | # 4 | # vendor/bin/phpstan analyse app --memory-limit 200M -l 0 5 | # 6 | includes: 7 | # 需要执行 composer require phpstan/phpstan-deprecation-rules --dev 8 | # - vendor/phpstan/phpstan-deprecation-rules/rules.neon 9 | parameters: 10 | level: 0 11 | paths: 12 | - app 13 | - config 14 | reportUnmatchedIgnoredErrors: false 15 | parallel: 16 | jobSize: 20 17 | maximumNumberOfProcesses: 32 18 | minimumNumberOfJobsPerProcess: 2 19 | ignoreErrors: 20 | - '#Static call to instance method Hyperf\\HttpServer\\Router\\Router::[a-zA-Z0-9\\_]+\(\)#' 21 | - '#Static call to instance method Hyperf\\DbConnection\\Db::[a-zA-Z0-9\\_]+\(\)#' 22 | 23 | services: 24 | # When using `match` to match enum, the enumeration must be completely overwritten 25 | - class: PHPStan\Rules\Comparison\MatchExpressionRule 26 | arguments: 27 | checkAlwaysTrueStrictComparison: %checkAlwaysTrueStrictComparison% 28 | disableUnreachable: %featureToggles.disableUnreachableBranchesRules% 29 | reportAlwaysTrueInLastCondition: %reportAlwaysTrueInLastCondition% 30 | treatPhpDocTypesAsCertain: %treatPhpDocTypesAsCertain% 31 | tags: 32 | - phpstan.rules.rule 33 | -------------------------------------------------------------------------------- /test/HttpTestCase.php: -------------------------------------------------------------------------------- 1 | client = make(Testing\Client::class); 39 | // $this->client = make(Testing\HttpClient::class, ['baseUri' => 'http://127.0.0.1:9501']); 40 | } 41 | 42 | public function __call($name, $arguments) 43 | { 44 | return $this->client->{$name}(...$arguments); 45 | } 46 | 47 | protected function tearDown(): void 48 | { 49 | Mockery::close(); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Kernel/Functions.php: -------------------------------------------------------------------------------- 1 | get($id); 28 | } 29 | 30 | return $container; 31 | } 32 | } 33 | 34 | if (! function_exists('format_throwable')) { 35 | /** 36 | * Format a throwable to string. 37 | */ 38 | function format_throwable(Throwable $throwable): string 39 | { 40 | return di()->get(FormatterInterface::class)->format($throwable); 41 | } 42 | } 43 | 44 | if (! function_exists('queue_push')) { 45 | /** 46 | * Push a job to async queue. 47 | */ 48 | function queue_push(JobInterface $job, int $delay = 0, string $key = 'default'): bool 49 | { 50 | $driver = di()->get(DriverFactory::class)->get($key); 51 | return $driver->push($job, $delay); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Default Dockerfile 2 | # 3 | # @link https://www.hyperf.io 4 | # @document https://hyperf.wiki 5 | # @contact group@hyperf.io 6 | # @license https://github.com/hyperf/hyperf/blob/master/LICENSE 7 | 8 | FROM hyperf/hyperf:8.1-alpine-v3.18-swoole-slim 9 | LABEL maintainer="Hyperf Developers " version="1.0" license="MIT" app.name="Hyperf" 10 | 11 | ## 12 | # ---------- env settings ---------- 13 | ## 14 | # --build-arg timezone=Asia/Shanghai 15 | ARG timezone 16 | 17 | ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \ 18 | APP_ENV=prod \ 19 | SCAN_CACHEABLE=(true) 20 | 21 | # update 22 | RUN set -ex \ 23 | # show php version and extensions 24 | && php -v \ 25 | && php -m \ 26 | && php --ri swoole \ 27 | # ---------- some config ---------- 28 | && cd /etc/php* \ 29 | # - config PHP 30 | && { \ 31 | echo "upload_max_filesize=128M"; \ 32 | echo "post_max_size=128M"; \ 33 | echo "memory_limit=1G"; \ 34 | echo "date.timezone=${TIMEZONE}"; \ 35 | } | tee conf.d/99_overrides.ini \ 36 | # - config timezone 37 | && ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \ 38 | && echo "${TIMEZONE}" > /etc/timezone \ 39 | # ---------- clear works ---------- 40 | && rm -rf /var/cache/apk/* /tmp/* /usr/share/man \ 41 | && echo -e "\033[42;37m Build Completed :).\033[0m\n" 42 | 43 | WORKDIR /opt/www 44 | 45 | # Composer Cache 46 | # COPY ./composer.* /opt/www/ 47 | # RUN composer install --no-dev --no-scripts 48 | 49 | COPY . /opt/www 50 | RUN composer install --no-dev -o && php bin/hyperf.php 51 | 52 | EXPOSE 9501 53 | 54 | ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"] 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 介绍 2 | 3 | Hyperf 是基于 `Swoole 4.5+` 实现的高性能、高灵活性的 PHP 持久化框架,内置协程服务器及大量常用的组件,性能较传统基于 `PHP-FPM` 的框架有质的提升,提供超高性能的同时,也保持着极其灵活的可扩展性,标准组件均以最新的 [PSR 标准](https://www.php-fig.org/psr) 实现,基于强大的依赖注入设计可确保框架内的绝大部分组件或类都是可替换的。 4 | 5 | 框架组件库除了常见的协程版的 `MySQL 客户端`、`Redis 客户端`,还为您准备了协程版的 `Eloquent ORM`、`GRPC 服务端及客户端`、`Zipkin (OpenTracing) 客户端`、`Guzzle HTTP 客户端`、`Elasticsearch 客户端`、`Consul 客户端`、`ETCD 客户端`、`AMQP 组件`、`Apollo 配置中心`、`基于令牌桶算法的限流器`、`通用连接池` 等组件的提供也省去了自己去实现对应协程版本的麻烦,并提供了 `依赖注入`、`注解`、`AOP 面向切面编程`、`中间件`、`自定义进程`、`事件管理器`、`简易的 Redis 消息队列和全功能的 RabbitMQ 消息队列` 等非常便捷的功能,满足丰富的技术场景和业务场景,开箱即用。 6 | 7 | # 框架初衷 8 | 9 | 尽管现在基于 PHP 语言开发的框架处于一个百花争鸣的时代,但仍旧没能看到一个优雅的设计与超高性能的共存的完美框架,亦没有看到一个真正为 PHP 微服务铺路的框架,此为 Hyperf 及其团队成员的初衷,我们将持续投入并为此付出努力,也欢迎你加入我们参与开源建设。 10 | 11 | # 设计理念 12 | 13 | `Hyperspeed + Flexibility = Hyperf`,从名字上我们就将 `超高速` 和 `灵活性` 作为 Hyperf 的基因。 14 | 15 | - 对于超高速,我们基于 Swoole 协程并在框架设计上进行大量的优化以确保超高性能的输出。 16 | - 对于灵活性,我们基于 Hyperf 强大的依赖注入组件,组件均基于 [PSR 标准](https://www.php-fig.org/psr) 的契约和由 Hyperf 定义的契约实现,达到框架内的绝大部分的组件或类都是可替换的。 17 | 18 | 基于以上的特点,Hyperf 将存在丰富的可能性,如实现 Web 服务,网关服务,分布式中间件,微服务架构,游戏服务器,物联网(IOT)等。 19 | 20 | # 文档 21 | 22 | [https://hyperf.wiki/](https://hyperf.wiki/) 23 | 24 | ## Gitlab CI 25 | 26 | 如果需要使用 `Gitlab CI`,可以通过以下方式创建 `Gitlab Runner`。 27 | 28 | ```shell 29 | sudo gitlab-runner register -n \ 30 | --url https://gitlab.com/ \ 31 | --clone-url http://your-ip/ \ 32 | --registration-token REGISTRATION_TOKEN \ 33 | --executor docker \ 34 | --description "Unit Runner" \ 35 | --docker-image "hyperf/docker-ci:latest" \ 36 | --docker-volumes /var/run/docker.sock:/var/run/docker.sock \ 37 | --docker-volumes /builds:/builds:rw \ 38 | --docker-privileged \ 39 | --tag-list "unit" \ 40 | --docker-pull-policy "if-not-present" 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit for Hyperf 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | ci: 7 | name: Test on PHP ${{ matrix.php-version }} Swoole ${{ matrix.swoole-version }} 8 | runs-on: '${{ matrix.os }}' 9 | strategy: 10 | matrix: 11 | os: [ ubuntu-latest ] 12 | php-version: [ '8.1', '8.2', '8.3', '8.4' ] 13 | swoole-version: [ 'v5.1.7', 'v6.0.2', 'v6.1.1', 'master' ] 14 | max-parallel: 9 15 | env: 16 | SW_VERSION: ${{ matrix.swoole-version }} 17 | PHP_VERSION: ${{ matrix.php-version }} 18 | PHP_CS_FIXER_IGNORE_ENV: 1 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Setup PHP 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php-version }} 26 | tools: phpize 27 | ini-values: opcache.enable_cli=0 28 | coverage: none 29 | extensions: pdo, pdo_mysql, redis 30 | - name: Setup Swoole 31 | run: ./.github/swoole.install.sh 32 | - name: Setup MySQL ... 33 | run: | 34 | docker run -e TZ=Asia/Shanghai -e MYSQL_ALLOW_EMPTY_PASSWORD=true -e MYSQL_ROOT_HOST=% -e MYSQL_DATABASE=hyperf -v $PWD/.github/init.sql:/docker-entrypoint-initdb.d/init.sql -d -p 3306:3306 --name mysql mysql/mysql-server:8.0 35 | docker run -p 6379:6379 -d --name redis redis:latest 36 | - name: Show Environment 37 | run: | 38 | php -v 39 | php -m 40 | php -i 41 | - name: Setup Packages 42 | run: composer update -o 43 | - name: Run Test Cases 44 | run: | 45 | cp .env.example .env 46 | vendor/bin/php-cs-fixer fix --dry-run 47 | composer analyse 48 | composer test -- --exclude-group OpenSSL 49 | -------------------------------------------------------------------------------- /config/autoload/databases.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'driver' => env('DB_DRIVER', 'mysql'), 19 | 'host' => env('DB_HOST', 'localhost'), 20 | 'port' => env('DB_PORT', 3306), 21 | 'database' => env('DB_DATABASE', 'hyperf'), 22 | 'username' => env('DB_USERNAME', 'root'), 23 | 'password' => env('DB_PASSWORD', ''), 24 | 'charset' => env('DB_CHARSET', 'utf8mb4'), 25 | 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), 26 | 'prefix' => env('DB_PREFIX', ''), 27 | 'pool' => [ 28 | 'min_connections' => 1, 29 | 'max_connections' => 32, 30 | 'connect_timeout' => 10.0, 31 | 'wait_timeout' => 3.0, 32 | 'heartbeat' => -1, 33 | 'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60), 34 | ], 35 | 'cache' => [ 36 | 'handler' => RedisHandler::class, 37 | 'cache_key' => '{mc:%s:m:%s}:%s:%s', 38 | 'prefix' => 'default', 39 | 'ttl' => 3600 * 24, 40 | 'empty_model_ttl' => 600, 41 | 'load_script' => true, 42 | ], 43 | 'commands' => [ 44 | 'gen:model' => [ 45 | 'path' => 'app/Model', 46 | 'force_casts' => true, 47 | 'inheritance' => 'Model', 48 | 'uses' => '', 49 | 'refresh_fillable' => true, 50 | 'table_mapping' => [], 51 | ], 52 | ], 53 | ], 54 | ]; 55 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # usermod -aG docker gitlab-runner 2 | 3 | stages: 4 | - unit 5 | - build 6 | - deploy 7 | 8 | variables: 9 | PROJECT_NAME: hyperf 10 | REGISTRY_URL: registry-docker.org 11 | GIT_SUBMODULE_STRATEGY: recursive 12 | 13 | unit: 14 | stage: unit 15 | image: hyperf/docker-ci:latest 16 | resource_group: $CI_PROJECT_NAME 17 | variables: 18 | DOCKER_TLS_CERTDIR: "" 19 | DOCKER_DRIVER: overlay2 20 | script: 21 | - APP_ENV=test docker-compose up -d --remove-orphans --build 22 | - docker exec $(basename $(pwd))_hyperf_1 composer install 23 | - docker exec $(basename $(pwd))_hyperf_1 composer analyse 24 | - docker exec $(basename $(pwd))_hyperf_1 vendor/bin/php-cs-fixer fix --dry-run 25 | - docker exec $(basename $(pwd))_hyperf_1 composer test 26 | after_script: 27 | - docker-compose down 28 | tags: 29 | - unit 30 | 31 | build_test_docker: 32 | stage: build 33 | script: 34 | - docker build . -t $PROJECT_NAME 35 | - docker tag $PROJECT_NAME $REGISTRY_URL/$PROJECT_NAME:test 36 | - docker push $REGISTRY_URL/$PROJECT_NAME:test 37 | only: 38 | - test 39 | tags: 40 | - builder 41 | 42 | deploy_test_docker: 43 | stage: deploy 44 | script: 45 | - docker stack deploy -c deploy.test.yml --with-registry-auth $PROJECT_NAME 46 | only: 47 | - test 48 | tags: 49 | - test 50 | 51 | build_docker: 52 | stage: build 53 | script: 54 | - docker build . -t $PROJECT_NAME 55 | - docker tag $PROJECT_NAME $REGISTRY_URL/$PROJECT_NAME:$CI_COMMIT_REF_NAME 56 | - docker tag $PROJECT_NAME $REGISTRY_URL/$PROJECT_NAME:latest 57 | - docker push $REGISTRY_URL/$PROJECT_NAME:$CI_COMMIT_REF_NAME 58 | - docker push $REGISTRY_URL/$PROJECT_NAME:latest 59 | only: 60 | - tags 61 | tags: 62 | - builder 63 | 64 | deploy_docker: 65 | stage: deploy 66 | script: 67 | - echo SUCCESS 68 | only: 69 | - tags 70 | tags: 71 | - builder 72 | -------------------------------------------------------------------------------- /config/autoload/server.php: -------------------------------------------------------------------------------- 1 | SWOOLE_BASE, 23 | 'type' => CoroutineServer::class, 24 | 'servers' => [ 25 | [ 26 | 'name' => 'http', 27 | 'type' => Server::SERVER_HTTP, 28 | 'host' => '0.0.0.0', 29 | 'port' => 9501, 30 | 'sock_type' => SocketType::TCP, 31 | 'callbacks' => [ 32 | Event::ON_REQUEST => [Hyperf\HttpServer\Server::class, 'onRequest'], 33 | ], 34 | ], 35 | ], 36 | 'settings' => [ 37 | 'enable_coroutine' => true, 38 | 'worker_num' => 4, 39 | 'pid_file' => BASE_PATH . '/runtime/hyperf.pid', 40 | 'open_tcp_nodelay' => true, 41 | 'max_coroutine' => 100000, 42 | 'open_http2_protocol' => true, 43 | 'max_request' => 0, 44 | 'socket_buffer_size' => 2 * 1024 * 1024, 45 | 'package_max_length' => 2 * 1024 * 1024, 46 | ], 47 | 'callbacks' => [ 48 | Event::ON_BEFORE_START => [ServerStartCallback::class, 'beforeStart'], 49 | Event::ON_WORKER_START => [WorkerStartCallback::class, 'onWorkerStart'], 50 | Event::ON_PIPE_MESSAGE => [PipeMessageCallback::class, 'onPipeMessage'], 51 | Event::ON_WORKER_EXIT => [WorkerExitCallback::class, 'onWorkerExit'], 52 | ], 53 | ]; 54 | -------------------------------------------------------------------------------- /app/Kernel/Context/Coroutine.php: -------------------------------------------------------------------------------- 1 | logger = $container->get(StdoutLoggerInterface::class); 31 | } 32 | 33 | /** 34 | * @return int Returns the coroutine ID of the coroutine just created. 35 | * Returns -1 when coroutine create failed. 36 | */ 37 | public function create(callable $callable): int 38 | { 39 | $id = Co::id(); 40 | $coroutine = Co::create(function () use ($callable, $id) { 41 | try { 42 | // Shouldn't copy all contexts to avoid socket already been bound to another coroutine. 43 | Context::copy($id, [ 44 | AppendRequestIdProcessor::REQUEST_ID, 45 | ServerRequestInterface::class, 46 | ]); 47 | $callable(); 48 | } catch (Throwable $throwable) { 49 | $this->logger->warning((string) $throwable); 50 | } 51 | }); 52 | 53 | try { 54 | return $coroutine->getId(); 55 | } catch (Throwable $throwable) { 56 | $this->logger->warning((string) $throwable); 57 | return -1; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Listener/DbQueryExecutedListener.php: -------------------------------------------------------------------------------- 1 | logger = $container->get(LoggerFactory::class)->get('sql'); 31 | } 32 | 33 | public function listen(): array 34 | { 35 | return [ 36 | QueryExecuted::class, 37 | ]; 38 | } 39 | 40 | /** 41 | * @param QueryExecuted $event 42 | */ 43 | public function process(object $event): void 44 | { 45 | if ($event instanceof QueryExecuted) { 46 | $sql = $event->sql; 47 | if (! Arr::isAssoc($event->bindings)) { 48 | $position = 0; 49 | foreach ($event->bindings as $value) { 50 | $position = strpos($sql, '?', $position); 51 | if ($position === false) { 52 | break; 53 | } 54 | $value = "'{$value}'"; 55 | $sql = substr_replace($sql, $value, $position, 1); 56 | $position += strlen($value); 57 | } 58 | } 59 | 60 | $this->logger->info(sprintf('[%s:%s] %s', $event->connectionName, $event->time, $sql)); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/Exception/Handler/BusinessExceptionHandler.php: -------------------------------------------------------------------------------- 1 | response = $container->get(Response::class); 36 | $this->logger = $container->get(StdoutLoggerInterface::class); 37 | } 38 | 39 | public function handle(Throwable $throwable, ResponseInterface $response) 40 | { 41 | switch (true) { 42 | case $throwable instanceof HttpException: 43 | return $this->response->handleException($throwable); 44 | case $throwable instanceof BusinessException: 45 | $this->logger->warning(format_throwable($throwable)); 46 | return $this->response->fail($throwable->getCode(), $throwable->getMessage()); 47 | case $throwable instanceof CircularDependencyException: 48 | $this->logger->error($throwable->getMessage()); 49 | return $this->response->fail(ErrorCode::SERVER_ERROR->value, $throwable->getMessage()); 50 | } 51 | 52 | $this->logger->error(format_throwable($throwable)); 53 | 54 | return $this->response->fail(ErrorCode::SERVER_ERROR->value, 'Server Error'); 55 | } 56 | 57 | public function isValid(Throwable $throwable): bool 58 | { 59 | return true; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Kernel/Http/Response.php: -------------------------------------------------------------------------------- 1 | response = $container->get(ResponseInterface::class); 34 | } 35 | 36 | public function success(mixed $data = []): ResponsePlusInterface 37 | { 38 | return $this->response->json([ 39 | 'code' => 0, 40 | 'data' => $data, 41 | ]); 42 | } 43 | 44 | public function fail(int $code, string $message = ''): ResponsePlusInterface 45 | { 46 | return $this->response->json([ 47 | 'code' => $code, 48 | 'message' => $message, 49 | ]); 50 | } 51 | 52 | public function redirect($url, int $status = 302): ResponsePlusInterface 53 | { 54 | return $this->response() 55 | ->setHeader('Location', (string) $url) 56 | ->setStatus($status); 57 | } 58 | 59 | public function cookie(Cookie $cookie) 60 | { 61 | ResponseContext::set($this->response()->withCookie($cookie)); 62 | return $this; 63 | } 64 | 65 | public function handleException(HttpException $throwable): ResponsePlusInterface 66 | { 67 | if ($throwable instanceof BadRequestHttpException) { 68 | di()->get(StdoutLoggerInterface::class)->warning('body: ' . $throwable->getRequest()?->getBody() . ' ' . $throwable); 69 | } 70 | 71 | return $this->response() 72 | ->addHeader('Server', 'Hyperf') 73 | ->setStatus($throwable->getStatusCode()) 74 | ->setBody(new SwooleStream($throwable->getMessage())); 75 | } 76 | 77 | public function response(): ResponsePlusInterface 78 | { 79 | return ResponseContext::get(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/Listener/QueueHandleListener.php: -------------------------------------------------------------------------------- 1 | logger = $container->get(LoggerFactory::class)->get('queue'); 35 | } 36 | 37 | public function listen(): array 38 | { 39 | return [ 40 | AfterHandle::class, 41 | BeforeHandle::class, 42 | FailedHandle::class, 43 | RetryHandle::class, 44 | ]; 45 | } 46 | 47 | public function process(object $event): void 48 | { 49 | if ($event instanceof Event && $event->getMessage()->job()) { 50 | $job = $event->getMessage()->job(); 51 | $jobClass = get_class($job); 52 | if ($job instanceof AnnotationJob) { 53 | $jobClass = sprintf('Job[%s@%s]', $job->class, $job->method); 54 | } 55 | $date = date('Y-m-d H:i:s'); 56 | 57 | switch (true) { 58 | case $event instanceof BeforeHandle: 59 | $this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass)); 60 | break; 61 | case $event instanceof AfterHandle: 62 | $this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass)); 63 | break; 64 | case $event instanceof FailedHandle: 65 | $this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass)); 66 | $this->logger->error((string) $event->getThrowable()); 67 | break; 68 | case $event instanceof RetryHandle: 69 | $this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass)); 70 | break; 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperf/biz-skeleton", 3 | "type": "project", 4 | "keywords": [ 5 | "php", 6 | "swoole", 7 | "framework", 8 | "hyperf", 9 | "microservice", 10 | "middleware" 11 | ], 12 | "description": "A coroutine framework that focuses on hyperspeed and flexible, specifically use for build microservices and middlewares.", 13 | "license": "MIT", 14 | "require": { 15 | "php": ">=8.1", 16 | "ext-json": "*", 17 | "ext-openssl": "*", 18 | "ext-pdo": "*", 19 | "ext-pdo_mysql": "*", 20 | "ext-redis": "*", 21 | "ext-swoole": ">=5.0", 22 | "hyperf/async-queue": "3.1.*", 23 | "hyperf/cache": "3.1.*", 24 | "hyperf/command": "3.1.*", 25 | "hyperf/config": "3.1.*", 26 | "hyperf/constants": "3.1.*", 27 | "hyperf/context": "3.1.*", 28 | "hyperf/contract": "3.1.*", 29 | "hyperf/coroutine": "3.1.*", 30 | "hyperf/database": "3.1.*", 31 | "hyperf/db-connection": "3.1.*", 32 | "hyperf/di": "3.1.*", 33 | "hyperf/dispatcher": "3.1.*", 34 | "hyperf/engine": "^2.0", 35 | "hyperf/event": "3.1.*", 36 | "hyperf/exception-handler": "3.1.*", 37 | "hyperf/framework": "3.1.*", 38 | "hyperf/guzzle": "3.1.*", 39 | "hyperf/http-server": "3.1.*", 40 | "hyperf/laminas-mime": "^3.0", 41 | "hyperf/logger": "3.1.*", 42 | "hyperf/model-cache": "3.1.*", 43 | "hyperf/polyfill-coroutine": "3.1.*", 44 | "hyperf/pool": "3.1.*", 45 | "hyperf/process": "3.1.*", 46 | "hyperf/redis": "3.1.*", 47 | "hyperf/server": "3.1.*", 48 | "hyperf/utils": "3.1.*" 49 | }, 50 | "require-dev": { 51 | "friendsofphp/php-cs-fixer": "^3.0", 52 | "hyperf/devtool": "3.1.*", 53 | "hyperf/testing": "3.1.*", 54 | "mockery/mockery": "^1.0", 55 | "phpstan/phpstan": "^1.0", 56 | "swoole/ide-helper": "dev-master" 57 | }, 58 | "autoload": { 59 | "psr-4": { 60 | "App\\": "app/" 61 | }, 62 | "files": [ 63 | "app/Kernel/Functions.php" 64 | ] 65 | }, 66 | "autoload-dev": { 67 | "psr-4": { 68 | "HyperfTest\\": "test/" 69 | } 70 | }, 71 | "minimum-stability": "dev", 72 | "prefer-stable": true, 73 | "config": { 74 | "optimize-autoloader": true, 75 | "sort-packages": true 76 | }, 77 | "extra": [], 78 | "scripts": { 79 | "post-root-package-install": [ 80 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 81 | ], 82 | "post-autoload-dump": [ 83 | "rm -rf runtime/container" 84 | ], 85 | "analyse": "phpstan analyse --memory-limit 512M", 86 | "cs-fix": "php-cs-fixer fix $1", 87 | "start": "php ./bin/hyperf.php start", 88 | "test": "co-phpunit --prepend test/bootstrap.php --colors=always", 89 | "rector": "rector process --clear-cache" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /storage/classes/ResolverDispatcher.php: -------------------------------------------------------------------------------- 1 | resolve($this->container); 45 | } 46 | 47 | return $this->getDefinitionResolver($definition)->resolve($definition, $parameters); 48 | } 49 | 50 | /** 51 | * Check if a definition can be resolved. 52 | * 53 | * @param DefinitionInterface $definition object that defines how the value should be obtained 54 | * @param array $parameters optional parameters to use to build the entry 55 | */ 56 | public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool 57 | { 58 | if ($definition instanceof SelfResolvingDefinitionInterface) { 59 | return $definition->isResolvable($this->container); 60 | } 61 | 62 | return $this->getDefinitionResolver($definition)->isResolvable($definition, $parameters); 63 | } 64 | 65 | /** 66 | * Returns a resolver capable of handling the given definition. 67 | * 68 | * @throws RuntimeException no definition resolver was found for this type of definition 69 | */ 70 | private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface 71 | { 72 | return match (true) { 73 | $definition instanceof ObjectDefinition => $this->objectResolver ??= new ObjectResolver($this->container, $this), 74 | $definition instanceof FactoryDefinition => $this->factoryResolver ??= new FactoryResolver($this->container, $this), 75 | default => throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition)), 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/Cases/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 37 | 38 | $res = $this->get('/'); 39 | 40 | $this->assertSame(0, $res['code']); 41 | $this->assertSame('Hello Hyperf.', $res['data']['message']); 42 | $this->assertSame('GET', $res['data']['method']); 43 | $this->assertSame('Hyperf', $res['data']['user']); 44 | 45 | $res = $this->get('/', ['user' => 'limx']); 46 | 47 | $this->assertSame(0, $res['code']); 48 | $this->assertSame('limx', $res['data']['user']); 49 | 50 | $res = $this->post('/', [ 51 | 'user' => 'limx', 52 | ]); 53 | $this->assertSame('Hello Hyperf.', $res['data']['message']); 54 | $this->assertSame('POST', $res['data']['method']); 55 | $this->assertSame('limx', $res['data']['user']); 56 | 57 | Context::set(AppendRequestIdProcessor::REQUEST_ID, $id = uniqid()); 58 | $pool = new Channel(1); 59 | di()->get(Coroutine::class)->create(function () use ($pool) { 60 | try { 61 | $all = Context::getContainer(); 62 | $pool->push((array) $all); 63 | } catch (Throwable $exception) { 64 | $pool->push(false); 65 | } 66 | }); 67 | 68 | $data = $pool->pop(); 69 | $this->assertIsArray($data); 70 | $this->assertSame($id, $data[AppendRequestIdProcessor::REQUEST_ID]); 71 | } 72 | 73 | public function testGetDefinitionResolver() 74 | { 75 | $container = Mockery::mock(ContainerInterface::class); 76 | $dispatcher = new ClassInvoker(new ResolverDispatcher($container)); 77 | $resolver = $dispatcher->getDefinitionResolver(Mockery::mock(FactoryDefinition::class)); 78 | $this->assertInstanceOf(FactoryResolver::class, $resolver); 79 | $this->assertSame($resolver, $dispatcher->factoryResolver); 80 | 81 | $resolver2 = $dispatcher->getDefinitionResolver(Mockery::mock(FactoryDefinition::class)); 82 | $this->assertInstanceOf(FactoryResolver::class, $resolver2); 83 | $this->assertSame($resolver2, $dispatcher->factoryResolver); 84 | } 85 | 86 | /** 87 | * @group OpenSSL 88 | */ 89 | public function testOpenSSL() 90 | { 91 | $this->assertNotFalse(openssl_encrypt('12345', 'bf', 'xxxxxxxx', 0, 'xxxxxxxx')); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /storage/classes/Coroutine.php: -------------------------------------------------------------------------------- 1 | get(StdoutLoggerInterface::class)->error((string) $exception); 41 | } 42 | }); 43 | } 44 | 45 | public static function sleep(float $seconds): void 46 | { 47 | usleep(intval($seconds * 1000 * 1000)); 48 | } 49 | 50 | /** 51 | * Returns the parent coroutine ID. 52 | * Returns 0 when running in the top level coroutine. 53 | * @throws RunningInNonCoroutineException when running in non-coroutine context 54 | * @throws CoroutineDestroyedException when the coroutine has been destroyed 55 | */ 56 | public static function parentId(?int $coroutineId = null): int 57 | { 58 | return Co::pid($coroutineId); 59 | } 60 | 61 | /** 62 | * The alias of Coroutine::parentId(). 63 | * @throws CoroutineDestroyedException when running in non-coroutine context 64 | * @throws RunningInNonCoroutineException when the coroutine has been destroyed 65 | */ 66 | public static function pid(?int $coroutineId = null): int 67 | { 68 | return Co::pid($coroutineId); 69 | } 70 | 71 | /** 72 | * @return int Returns the coroutine ID of the coroutine just created. 73 | * Returns -1 when coroutine create failed. 74 | */ 75 | public static function create(callable $callable): int 76 | { 77 | return di()->get(Go::class)->create($callable); 78 | } 79 | 80 | /** 81 | * Create a coroutine with a copy of the parent coroutine context. 82 | */ 83 | public static function fork(callable $callable, array $keys = []): int 84 | { 85 | $cid = static::id(); 86 | $callable = static function () use ($callable, $cid, $keys) { 87 | Context::copy($cid, $keys); 88 | $callable(); 89 | }; 90 | 91 | return static::create($callable); 92 | } 93 | 94 | public static function inCoroutine(): bool 95 | { 96 | return Co::id() > 0; 97 | } 98 | 99 | public static function stats(): array 100 | { 101 | return Co::stats(); 102 | } 103 | 104 | public static function exists(int $id): bool 105 | { 106 | return Co::exists($id); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | setParallelConfig(new ParallelConfig(swoole_cpu_num(), 20)) 27 | ->setRiskyAllowed(true) 28 | ->setRules([ 29 | '@PSR2' => true, 30 | '@Symfony' => true, 31 | '@DoctrineAnnotation' => true, 32 | '@PhpCsFixer' => true, 33 | 'header_comment' => [ 34 | 'comment_type' => 'PHPDoc', 35 | 'header' => $header, 36 | 'separate' => 'none', 37 | 'location' => 'after_declare_strict', 38 | ], 39 | 'array_syntax' => [ 40 | 'syntax' => 'short', 41 | ], 42 | 'list_syntax' => [ 43 | 'syntax' => 'short', 44 | ], 45 | 'concat_space' => [ 46 | 'spacing' => 'one', 47 | ], 48 | 'blank_line_before_statement' => [ 49 | 'statements' => [ 50 | 'declare', 51 | ], 52 | ], 53 | 'general_phpdoc_annotation_remove' => [ 54 | 'annotations' => [ 55 | 'author', 56 | ], 57 | ], 58 | 'ordered_imports' => [ 59 | 'imports_order' => [ 60 | 'class', 'function', 'const', 61 | ], 62 | 'sort_algorithm' => 'alpha', 63 | ], 64 | 'single_line_comment_style' => [ 65 | 'comment_types' => [ 66 | ], 67 | ], 68 | 'yoda_style' => [ 69 | 'always_move_variable' => false, 70 | 'equal' => false, 71 | 'identical' => false, 72 | ], 73 | 'phpdoc_align' => [ 74 | 'align' => 'left', 75 | ], 76 | 'multiline_whitespace_before_semicolons' => [ 77 | 'strategy' => 'no_multi_line', 78 | ], 79 | 'constant_case' => [ 80 | 'case' => 'lower', 81 | ], 82 | 'global_namespace_import' => [ 83 | 'import_classes' => true, 84 | 'import_constants' => true, 85 | 'import_functions' => true, 86 | ], 87 | 'ordered_types' => [ 88 | 'null_adjustment' => 'always_first', 89 | ], 90 | 'phpdoc_to_comment' => false, 91 | 'class_attributes_separation' => true, 92 | 'combine_consecutive_unsets' => true, 93 | 'declare_strict_types' => true, 94 | 'linebreak_after_opening_tag' => true, 95 | 'lowercase_static_reference' => true, 96 | 'no_useless_else' => true, 97 | 'no_unused_imports' => true, 98 | 'not_operator_with_successor_space' => true, 99 | 'not_operator_with_space' => false, 100 | 'ordered_class_elements' => true, 101 | 'php_unit_strict' => false, 102 | 'phpdoc_separation' => false, 103 | 'single_quote' => true, 104 | 'standardize_not_equals' => true, 105 | 'multiline_comment_opening_closing' => true, 106 | 'single_line_empty_body' => false, 107 | ]) 108 | ->setFinder( 109 | Finder::create() 110 | ->exclude('public') 111 | ->exclude('runtime') 112 | ->exclude('vendor') 113 | ->in(__DIR__) 114 | ) 115 | ->setUsingCache(false); 116 | --------------------------------------------------------------------------------