代码初始化

This commit is contained in:
2025-08-07 20:21:47 +08:00
commit 50f3a2dbb0
2191 changed files with 374790 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
/.github export-ignore
/examples export-ignore
/tests export-ignore

View File

@@ -0,0 +1,4 @@
/vendor/
composer.lock
*.cache
*.log

View File

@@ -0,0 +1,89 @@
<?php
$header = <<<'EOF'
This file is part of Hyperf.
@link https://www.hyperf.io
@document https://hyperf.wiki
@contact group@hyperf.io
@license https://github.com/hyperf/hyperf/blob/master/LICENSE
EOF;
return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
'@PSR2' => true,
'@Symfony' => true,
'@DoctrineAnnotation' => true,
'@PhpCsFixer' => true,
'header_comment' => [
'comment_type' => 'PHPDoc',
'header' => $header,
'separate' => 'none',
'location' => 'after_declare_strict',
],
'array_syntax' => [
'syntax' => 'short'
],
'list_syntax' => [
'syntax' => 'short'
],
'concat_space' => [
'spacing' => 'one'
],
'blank_line_before_statement' => [
'statements' => [
'declare',
],
],
'general_phpdoc_annotation_remove' => [
'annotations' => [
'author'
],
],
'ordered_imports' => [
'imports_order' => [
'class', 'function', 'const',
],
'sort_algorithm' => 'alpha',
],
'single_line_comment_style' => [
'comment_types' => [
],
],
'yoda_style' => [
'always_move_variable' => false,
'equal' => false,
'identical' => false,
],
'phpdoc_align' => [
'align' => 'left',
],
'multiline_whitespace_before_semicolons' => [
'strategy' => 'no_multi_line',
],
'constant_case' => [
'case' => 'lower',
],
'class_attributes_separation' => true,
'combine_consecutive_unsets' => true,
'declare_strict_types' => true,
'linebreak_after_opening_tag' => true,
'lowercase_static_reference' => true,
'no_useless_else' => true,
'no_unused_imports' => true,
'not_operator_with_successor_space' => true,
'not_operator_with_space' => false,
'ordered_class_elements' => true,
'php_unit_strict' => false,
'phpdoc_separation' => false,
'single_quote' => true,
'standardize_not_equals' => true,
'multiline_comment_opening_closing' => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->exclude('vendor')
->in(__DIR__)
)
->setUsingCache(false);

View File

@@ -0,0 +1,6 @@
<?php
namespace PHPSTORM_META {
// Reflect
override(\Psr\Container\ContainerInterface::get(0), map('@'));
}

View File

@@ -0,0 +1,46 @@
# Default Dockerfile
#
# @link https://www.hyperf.io
# @document https://hyperf.wiki
# @contact group@hyperf.io
# @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
ARG PHP_VERSION
ARG ALPINE_VERSION
FROM hyperf/hyperf:${PHP_VERSION}-alpine-${ALPINE_VERSION}-swoole
LABEL maintainer="Hyperf Developers <group@hyperf.io>" version="1.0" license="MIT" app.name="Hyperf"
ARG timezone
ARG PHP_VERSION
ENV TIMEZONE=${timezone:-"Asia/Shanghai"}
ENV COMPOSER_ROOT_VERSION="v1.2.0"
# update
RUN set -ex \
# show php version and extensions
&& php -v \
&& php -m \
&& php --ri swoole \
# ---------- some config ----------
&& cd "/etc/php${PHP_VERSION%\.*}" \
# - config PHP
&& { \
echo "upload_max_filesize=128M"; \
echo "post_max_size=128M"; \
echo "memory_limit=1G"; \
echo "date.timezone=${TIMEZONE}"; \
} | tee conf.d/99_overrides.ini \
# - config timezone
&& ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \
&& echo "${TIMEZONE}" > /etc/timezone \
# ---------- clear works ----------
&& rm -rf /var/cache/apk/* /tmp/* /usr/share/man \
&& echo -e "\033[42;37m Build Completed :).\033[0m\n"
WORKDIR /opt/www
COPY . /opt/www
RUN composer install -o

View File

@@ -0,0 +1,7 @@
# Swoole Engine
![Swoole Engine Test](https://github.com/hyperf/engine/workflows/Swoole%20Engine%20Test/badge.svg)
```
composer require hyperf/engine
```

View File

@@ -0,0 +1,49 @@
{
"name": "hyperf/engine",
"type": "library",
"license": "MIT",
"keywords": [
"php",
"hyperf"
],
"description": "",
"autoload": {
"psr-4": {
"Hyperf\\Engine\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"HyperfTest\\": "tests"
}
},
"require": {
"php": ">=7.4"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.0",
"hyperf/guzzle": "^2.2",
"phpstan/phpstan": "^1.0",
"phpunit/phpunit": "^9.4",
"swoole/ide-helper": "dev-master"
},
"suggest": {
"ext-swoole": ">=4.5"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"optimize-autoloader": true,
"sort-packages": true
},
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
},
"scripts": {
"test": "phpunit -c phpunit.xml --colors=always",
"analyse": "phpstan analyse --memory-limit 1024M -l 0 ./src",
"cs-fix": "php-cs-fixer fix $1"
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
verbose="true"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuite name="Testsuite">
<directory>./tests/</directory>
</testsuite>
</phpunit>

View File

@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Hyperf\Engine\Contract\ChannelInterface;
use Hyperf\Engine\Exception\RuntimeException;
if (PHP_VERSION_ID > 80000 && SWOOLE_VERSION_ID >= 50000) {
class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
{
protected bool $closed = false;
public function push(mixed $data, float $timeout = -1): bool
{
return parent::push($data, $timeout);
}
public function pop(float $timeout = -1): mixed
{
return parent::pop($timeout);
}
public function getCapacity(): int
{
return $this->capacity;
}
public function getLength(): int
{
return $this->length();
}
public function isAvailable(): bool
{
return ! $this->isClosing();
}
public function close(): bool
{
$this->closed = true;
return parent::close();
}
public function hasProducers(): bool
{
throw new RuntimeException('Not supported.');
}
public function hasConsumers(): bool
{
throw new RuntimeException('Not supported.');
}
public function isReadable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isWritable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isClosing(): bool
{
return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
}
public function isTimeout(): bool
{
return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
}
}
} else {
class Channel extends \Swoole\Coroutine\Channel implements ChannelInterface
{
/**
* @var bool
*/
protected $closed = false;
public function getCapacity(): int
{
return $this->capacity;
}
public function getLength(): int
{
return $this->length();
}
public function isAvailable(): bool
{
return ! $this->isClosing();
}
public function close(): bool
{
$this->closed = true;
return parent::close();
}
public function hasProducers(): bool
{
throw new RuntimeException('Not supported.');
}
public function hasConsumers(): bool
{
throw new RuntimeException('Not supported.');
}
public function isReadable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isWritable(): bool
{
throw new RuntimeException('Not supported.');
}
public function isClosing(): bool
{
return $this->closed || $this->errCode === SWOOLE_CHANNEL_CLOSED;
}
public function isTimeout(): bool
{
return ! $this->closed && $this->errCode === SWOOLE_CHANNEL_TIMEOUT;
}
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Swoole\Coroutine\Http\Server as HttpServer;
use Swoole\Coroutine\Server;
class Constant
{
public const ENGINE = 'Swoole';
public static function isCoroutineServer($server): bool
{
return $server instanceof Server || $server instanceof HttpServer;
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract;
if (PHP_VERSION_ID > 80000 && SWOOLE_VERSION_ID >= 50000) {
interface ChannelInterface
{
/**
* @param float|int $timeout [optional] = -1
*/
public function push(mixed $data, float $timeout = -1): bool;
/**
* @param float $timeout seconds [optional] = -1
* @return mixed when pop failed, return false
*/
public function pop(float $timeout = -1): mixed;
/**
* Swow: When the channel is closed, all the data in it will be destroyed.
* Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
*/
public function close(): bool;
public function getCapacity(): int;
public function getLength(): int;
public function isAvailable(): bool;
public function hasProducers(): bool;
public function hasConsumers(): bool;
public function isEmpty(): bool;
public function isFull(): bool;
public function isReadable(): bool;
public function isWritable(): bool;
public function isClosing(): bool;
public function isTimeout(): bool;
}
} else {
interface ChannelInterface
{
/**
* @param mixed $data [required]
* @param float|int $timeout [optional] = -1
* @return bool
*/
public function push($data, $timeout = -1);
/**
* @param float $timeout seconds [optional] = -1
* @return mixed when pop failed, return false
*/
public function pop($timeout = -1);
/**
* Swow: When the channel is closed, all the data in it will be destroyed.
* Swoole: When the channel is closed, the data in it can still be popped out, but push behavior will no longer succeed.
* @return mixed
*/
public function close(): bool;
/**
* @return int
*/
public function getCapacity();
/**
* @return int
*/
public function getLength();
/**
* @return bool
*/
public function isAvailable();
/**
* @return bool
*/
public function hasProducers();
/**
* @return bool
*/
public function hasConsumers();
/**
* @return bool
*/
public function isEmpty();
/**
* @return bool
*/
public function isFull();
/**
* @return bool
*/
public function isReadable();
/**
* @return bool
*/
public function isWritable();
/**
* @return bool
*/
public function isClosing();
/**
* @return bool
*/
public function isTimeout();
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
interface CoroutineInterface
{
/**
* @param callable $callable [required]
*/
public function __construct(callable $callable);
/**
* @param mixed ...$data
* @return $this
*/
public function execute(...$data);
/**
* @return int
*/
public function getId();
/**
* @param callable $callable [required]
* @param mixed ...$data
* @return $this
*/
public static function create(callable $callable, ...$data);
/**
* @return int returns coroutine id from current coroutine, -1 in non coroutine environment
*/
public static function id();
/**
* Returns the parent coroutine ID.
* Returns 0 when running in the top level coroutine.
* @throws RunningInNonCoroutineException when running in non-coroutine context
* @throws CoroutineDestroyedException when the coroutine has been destroyed
*/
public static function pid(?int $id = null);
/**
* Set config to coroutine.
*/
public static function set(array $config);
/**
* @param null|int $id coroutine id
* @return null|\ArrayObject
*/
public static function getContextFor(?int $id = null);
/**
* Execute callback when coroutine destruct.
*/
public static function defer(callable $callable);
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract\Http;
use Hyperf\Engine\Http\RawResponse;
interface ClientInterface
{
public function set(array $settings): bool;
/**
* @param string[][] $headers
*/
public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse;
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Contract\WebSocket;
interface WebSocketInterface
{
public const ON_MESSAGE = 'message';
public const ON_CLOSE = 'close';
public function on(string $event, callable $callback): void;
public function start(): void;
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
use Hyperf\Engine\Contract\CoroutineInterface;
use Hyperf\Engine\Exception\CoroutineDestroyedException;
use Hyperf\Engine\Exception\RunningInNonCoroutineException;
use Hyperf\Engine\Exception\RuntimeException;
use Swoole\Coroutine as SwooleCo;
class Coroutine implements CoroutineInterface
{
/**
* @var callable
*/
private $callable;
/**
* @var int
*/
private $id;
public function __construct(callable $callable)
{
$this->callable = $callable;
}
public static function create(callable $callable, ...$data)
{
$coroutine = new static($callable);
$coroutine->execute(...$data);
return $coroutine;
}
public function execute(...$data)
{
$this->id = SwooleCo::create($this->callable, ...$data);
return $this;
}
public function getId()
{
if (is_null($this->id)) {
throw new RuntimeException('Coroutine was not be executed.');
}
return $this->id;
}
public static function id()
{
return SwooleCo::getCid();
}
public static function pid(?int $id = null)
{
if ($id) {
$cid = SwooleCo::getPcid($id);
if ($cid === false) {
throw new CoroutineDestroyedException(sprintf('Coroutine #%d has been destroyed.', $id));
}
} else {
$cid = SwooleCo::getPcid();
}
if ($cid === false) {
throw new RunningInNonCoroutineException('Non-Coroutine environment don\'t has parent coroutine id.');
}
return max(0, $cid);
}
public static function set(array $config)
{
SwooleCo::set($config);
}
/**
* @return null|\ArrayObject
*/
public static function getContextFor(?int $id = null)
{
if ($id === null) {
return SwooleCo::getContext();
}
return SwooleCo::getContext($id);
}
public static function defer(callable $callable)
{
SwooleCo::defer($callable);
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class CoroutineDestroyedException extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class HttpClientException extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class RunningInNonCoroutineException extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Exception;
class RuntimeException extends \RuntimeException
{
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class Extension
{
public static function isLoaded(): bool
{
return extension_loaded('Swoole');
}
}

View File

@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
use Hyperf\Engine\Contract\Http\ClientInterface;
use Hyperf\Engine\Exception\HttpClientException;
use Swoole\Coroutine\Http\Client as HttpClient;
class Client extends HttpClient implements ClientInterface
{
public function set(array $settings): bool
{
return parent::set($settings);
}
/**
* @param string[][] $headers
*/
public function request(string $method = 'GET', string $path = '/', array $headers = [], string $contents = '', string $version = '1.1'): RawResponse
{
$this->setMethod($method);
$this->setData($contents);
$this->setHeaders($this->encodeHeaders($headers));
$this->execute($path);
if ($this->errCode !== 0) {
throw new HttpClientException($this->errMsg, $this->errCode);
}
return new RawResponse(
$this->statusCode,
$this->decodeHeaders($this->headers ?? []),
$this->body,
$version
);
}
/**
* @param string[] $headers
* @return string[][]
*/
private function decodeHeaders(array $headers): array
{
$result = [];
foreach ($headers as $name => $header) {
// The key of header is lower case.
$result[$name][] = $header;
}
if ($this->set_cookie_headers) {
$result['set-cookie'] = $this->set_cookie_headers;
}
return $result;
}
/**
* Swoole engine not support two dimensional array.
* @param string[][] $headers
* @return string[]
*/
private function encodeHeaders(array $headers): array
{
$result = [];
foreach ($headers as $name => $value) {
$result[$name] = is_array($value) ? implode(',', $value) : $value;
}
return $result;
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
use Swoole\Http\Response;
class FdGetter
{
public function get(Response $response): int
{
return $response->fd;
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\Http;
final class RawResponse
{
/**
* @var int
*/
public $statusCode = 0;
/**
* @var string[][]
*/
public $headers = [];
/**
* @var string
*/
public $body = '';
/**
* Protocol version.
* @var string
*/
public $version = '';
/**
* @param string[][] $headers
*/
public function __construct(int $statusCode, array $headers, string $body, string $version)
{
$this->statusCode = $statusCode;
$this->headers = $headers;
$this->body = $body;
$this->version = $version;
}
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class Socket extends \Swoole\Coroutine\Socket
{
}

View File

@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine;
class WaitGroup extends \Swoole\Coroutine\WaitGroup
{
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
class Frame
{
public const PING = '27890027';
public const PONG = '278a0027';
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
class Opcode
{
public const CONTINUATION = 0;
public const TEXT = 1;
public const BINARY = 2;
public const CLOSE = 8;
public const PING = 9;
public const PONG = 10;
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Engine\WebSocket;
use Hyperf\Engine\Contract\WebSocket\WebSocketInterface;
use Swoole\Http\Request;
use Swoole\Http\Response;
use Swoole\WebSocket\CloseFrame;
class WebSocket implements WebSocketInterface
{
/**
* @var Response
*/
protected $connection;
/**
* @var array<string, callable>
*/
protected $events = [];
public function __construct(Response $connection, Request $request)
{
$this->connection = $connection;
$this->connection->upgrade();
}
public function on(string $event, callable $callback): void
{
$this->events[$event] = $callback;
}
public function start(): void
{
while (true) {
$frame = $this->connection->recv();
if ($frame === false || $frame instanceof CloseFrame || $frame === '') {
$callback = $this->events[static::ON_CLOSE];
$callback($this->connection, $this->connection->fd);
break;
}
$callback = $this->events[static::ON_MESSAGE];
$callback($this->connection, $frame);
}
$this->connection = null;
$this->events = [];
}
}