├── .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 |
--------------------------------------------------------------------------------