This commit is contained in:
li
2026-01-28 23:48:20 +08:00
commit b8cd0c9418
1293 changed files with 184011 additions and 0 deletions
+405
View File
@@ -0,0 +1,405 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper;
use GuzzleHttp\Client;
use ReflectionClass;
use ReflectionException;
use ReflectionExtension;
use ReflectionParameter;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel;
use Swoole\IDEHelper\Rules\NamespaceRule;
use Symfony\Component\Filesystem\Filesystem;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\DocBlock\Tag\ReturnTag;
use Laminas\Code\Generator\DocBlockGenerator;
use Laminas\Code\Reflection\ClassReflection;
/**
* Class AbstractStubGenerator
*
* @package Swoole\IDEHelper
*/
abstract class AbstractStubGenerator
{
protected const C_METHOD = 1;
protected const C_PROPERTY = 2;
protected const C_CONSTANT = 3;
protected const DEFAULT_VERSION = "master";
/**
* @var string
*/
protected $extension;
/**
* @var string
*/
protected $language;
/**
* @var string
*/
protected $dirConfig;
/**
* @var string
*/
protected $dirOutput;
/**
* @var ReflectionExtension
*/
protected $rf_ext;
/**
* @var string
*/
protected $rf_version;
protected const ALIAS_SHORT_NAME = 1; // Short names of coroutine classes.
protected const ALIAS_SNAKE_CASE = 2; // Class names in snake_case. e.g., swoole_timer.
protected $aliases = [
self::ALIAS_SHORT_NAME => [],
self::ALIAS_SNAKE_CASE => [],
];
/**
* Methods that don't need to have return type specified.
*/
protected const IGNORED_METHODS = [
'__construct' => null,
'__destruct' => null,
];
/**
* AbstractStubGenerator constructor.
*
* @throws Exception
* @throws ReflectionException
*/
public function __construct()
{
$this->init();
if (!extension_loaded($this->extension)) {
throw new Exception("Extension $this->extension not enabled or not installed.");
}
$this->language = 'chinese';
$this->dirOutput = dirname(__DIR__) . '/output/' . $this->extension;
$this->dirConfig = dirname(__DIR__) . '/config';
$this->rf_ext = new ReflectionExtension($this->extension);
}
/**
* @throws Exception
* @throws ReflectionException
*/
public function export(): void
{
// Retrieve and save all constants.
if ($this->rf_ext->getConstants()) {
$defines = '';
foreach ($this->rf_ext->getConstants() as $name => $value) {
$defines .= sprintf("define('%s', %s);\n", $name, (is_numeric($value) ? $value : "'{$value}'"));
}
$this->writeToPhpFile($this->dirOutput . '/constants.php', $defines);
}
// Retrieve and save all functions.
$output = $this->getFunctionsDef();
if (!empty($output)) {
$this->writeToPhpFile($this->dirOutput . '/functions.php', $output);
}
// Retrieve and save all classes.
$classes = $this->rf_ext->getClasses();
// There are three types of class names in Swoole:
// 1. short name of a class. Short names start with "Co\", and they can be found in file output/aliases.php.
// 2. fully qualified name (class name with namespace prefix), e.g., \Swoole\Timer. These classes can be found
// under folder output/namespace.
// 3. snake_case. e.g., swoole_timer. These aliases can be found in file output/aliases.php.
foreach ($classes as $className => $ref) {
if (strtolower(substr($className, 0, 3)) == 'co\\') {
$className = str_replace('Swoole\\Coroutine', 'Co', $ref->getName());
$this->aliases[self::ALIAS_SHORT_NAME][$className] = $ref->getName();
} elseif (strchr($className, '\\')) {
$this->exportNamespaceClass($className, $ref);
} else {
$this->aliases[self::ALIAS_SNAKE_CASE][$className] = $this->getNamespaceAlias($className);
}
}
$class_alias = '';
foreach (array_filter($this->aliases) as $type => $aliases) {
if (!empty($class_alias)) {
$class_alias .= "\n";
}
asort($aliases);
foreach ($aliases as $alias => $original) {
$class_alias .= "class_alias({$original}::class, {$alias}::class);\n";
}
}
$this->writeToPhpFile($this->dirOutput . '/aliases.php', $class_alias);
}
/**
* @return string
*/
public function getVersion(): string
{
return $this->rf_ext->getVersion();
}
/**
* @return string
*/
public function getExtension(): string
{
return $this->extension;
}
/**
* @param string $extension
* @return $this
*/
public function setExtension(string $extension): self
{
$this->extension = $extension;
return $this;
}
/**
* @param string $className
* @return string
*/
protected function getNamespaceAlias(string $className): string
{
if (strcasecmp($className, 'co') === 0) {
return Coroutine::class;
} elseif (strcasecmp($className, 'chan') === 0) {
return Channel::class;
} else {
return str_replace('_', '\\', ucwords($className, '_'));
}
}
/**
* @param string $class
* @param string $name
* @param string $type
* @return array
*/
protected function getConfig(string $class, string $name, string $type): array
{
switch ($type) {
case self::C_CONSTANT:
$dir = 'constant';
break;
case self::C_METHOD:
$dir = 'method';
break;
case self::C_PROPERTY:
$dir = 'property';
break;
default:
return false;
}
$file = $this->dirConfig . '/' . $this->language . '/' . strtolower($class) . '/' . $dir . '/' . $name . '.php';
if (is_file($file)) {
return include $file;
} else {
return array();
}
}
/**
* @param ReflectionParameter $parameter
* @return string|null
*/
protected function getDefaultValue(ReflectionParameter $parameter): ?string
{
try {
$default_value = $parameter->getDefaultValue();
if ($default_value === []) {
$default_value = '[]';
} elseif ($default_value === null) {
$default_value = 'null';
} elseif (is_bool($default_value)) {
$default_value = $default_value ? 'true' : 'false';
} else {
$default_value = var_export($default_value, true);
}
} catch (\Throwable $e) {
if ($parameter->isOptional()) {
$default_value = 'null';
} else {
$default_value = null;
}
}
return $default_value;
}
/**
* @return string
*/
protected function getFunctionsDef(): string
{
$all = '';
foreach ($this->rf_ext->getFunctions() as $function) {
$vp = array();
$comment = "/**\n";
$params = $function->getParameters();
foreach ($params as $param) {
$default_value = $this->getDefaultValue($param);
$comment .= " * @param \${$param->name}[" .
($param->isOptional() ? 'optional' : 'required') .
"]\n";
$vp[] = ($param->isPassedByReference() ? '&' : '') .
"\${$param->name}" .
($default_value ? " = {$default_value}" : '');
}
$comment .= " * @return mixed\n";
$comment .= " */\n";
$comment .= sprintf("function %s(%s){}\n\n", $function->getName(), join(', ', $vp));
$all .= $comment;
}
return $all;
}
/**
* @param string $classname
* @param ReflectionClass $ref
* @throws Exception
* @throws ReflectionException
*/
protected function exportNamespaceClass(string $classname, ReflectionClass $ref): void
{
(new NamespaceRule($this))->validate($classname);
$class = ClassGenerator::fromReflection(new ClassReflection($ref->getName()));
foreach ($class->getMethods() as $method) {
if ((null === $method->getReturnType()) && !array_key_exists($method->getName(), self::IGNORED_METHODS)) {
$method->setDocBlock(
DocBlockGenerator::fromArray(
[
'shortDescription' => null,
'longDescription' => null,
'tags' => [
new ReturnTag(
[
'datatype' => 'mixed',
]
),
],
]
)
);
}
}
$this->writeToPhpFile(
$this->dirOutput . '/namespace/' . implode('/', array_slice(explode('\\', $classname), 1)) . '.php',
$class->generate()
);
}
/**
* @param string $path
* @param string $content
* @return AbstractStubGenerator
*/
protected function writeToPhpFile(string $path, string $content): self
{
$this->mkdir(dirname($path));
file_put_contents($path, "<?php\n\n" . $content);
return $this;
}
/**
* @param string $dir
* @return AbstractStubGenerator
*/
protected function mkdir(string $dir): self
{
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
return $this;
}
/**
* @param string $name
* @param string $version
* @param string $targetDir
* @return $this
* @throws Exception
*/
protected function download(string $name, string $version = self::DEFAULT_VERSION, string $targetDir = ""): self
{
$version = $version ?: self::DEFAULT_VERSION;
$targetDir = $targetDir ?: $name;
if (preg_match("/^v[0-9]+\.[0-9]+\.[0-9]+(\-[A-Za-z0-9]+)?$/", $version)) {
$downloadUrl = "https://github.com/swoole/${name}/archive/${version}.zip";
$version = substr($version, 1);
} elseif (preg_match("/^[0-9]+\.[0-9]+\.[0-9]+(\-[A-Za-z0-9]+)?$/", $version)) {
$downloadUrl = "https://github.com/swoole/${name}/archive/v${version}.zip";
} else {
$downloadUrl = "https://github.com/swoole/${name}/archive/${version}.zip";
}
$unzippedDir = "${name}-${version}";
if (file_exists("temp.zip")) {
unlink("temp.zip");
}
$this->deleteDir($unzippedDir)->deleteDir($targetDir);
(new Client())->get($downloadUrl, ["sink" => "temp.zip"]);
shell_exec("unzip temp.zip");
if (!is_dir($unzippedDir)) {
throw new Exception(
sprintf(
"Top directory in the zip file downloaded from URL '%s' is not '%s'.",
$downloadUrl,
$unzippedDir
)
);
}
rename($unzippedDir, $targetDir);
unlink("temp.zip");
return $this;
}
/**
* @param string $dir
* @param bool $recreate
* @throws Exception
*/
protected function deleteDir(string $dir, bool $recreate = false): self
{
(new Filesystem())->remove($dir);
if ($recreate) {
$this->mkdir($dir);
}
return $this;
}
/**
* @return AbstractStubGenerator
*/
abstract protected function init(): AbstractStubGenerator;
}
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper;
/**
* Class Constant
*
* @package Swoole\IDEHelper
*/
class Constant
{
public const EXT_SWOOLE = 'swoole';
public const EXT_SWOOLE_ASYNC = 'swoole_async';
public const EXT_SWOOLE_ORM = 'swoole_orm';
public const EXT_SWOOLE_POSTGRESQL = 'swoole_postgresql';
public const EXT_SWOOLE_SERIALIZE = 'swoole_serialize';
public const EXT_SWOOLE_ZOOKEEPER = 'swoole_zookeeper';
}
+9
View File
@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper;
class Exception extends \Exception
{
}
+71
View File
@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\Rules;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Exception;
/**
* Class NamespaceRule
*
* @package Swoole\IDEHelper\Rules
*/
abstract class AbstractRule
{
/**
* @var AbstractStubGenerator
*/
protected $generator;
/**
* AbstractRule constructor.
*
* @param AbstractStubGenerator $generator
*/
public function __construct(AbstractStubGenerator $generator)
{
$this->setGenerator($generator);
}
/**
* @param mixed ...$params
*/
public function validate(...$params): void
{
if (in_array($this->getGenerator()->getExtension(), $this->getEnabledExtensions())) {
$this->validateWith(...$params);
}
}
/**
* @return AbstractStubGenerator
*/
protected function getGenerator(): AbstractStubGenerator
{
return $this->generator;
}
/**
* @param AbstractStubGenerator $generator
* @return $this
*/
protected function setGenerator(AbstractStubGenerator $generator): self
{
$this->generator = $generator;
return $this;
}
/**
* @param mixed ...$params
* @throws Exception
*/
abstract protected function validateWith(...$params): void;
/**
* @return array
*/
abstract protected function getEnabledExtensions(): array;
}
+38
View File
@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\Rules;
use Swoole\IDEHelper\Constant;
use Swoole\IDEHelper\Exception;
/**
* Class NamespaceRule
*
* @package Swoole\IDEHelper\Rules
*/
class NamespaceRule extends AbstractRule
{
/**
* @inheritDoc
*/
protected function validateWith(...$params): void
{
if (strcasecmp(explode('\\', $params[0])[0], $this->getGenerator()->getExtension()) !== 0) {
throw new Exception(
"Class $params[0] should be under namespace \\{$this->getGenerator()->getExtension()} but not."
);
}
}
/**
* @inheritDoc
*/
protected function getEnabledExtensions(): array
{
return [
Constant::EXT_SWOOLE,
];
}
}
+26
View File
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
/**
* Class Swoole
*
* @package Swoole\IDEHelper\StubGenerators
*/
class Swoole extends AbstractStubGenerator
{
/**
* @inheritDoc
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE;
return $this;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
/**
* Class SwooleAsync
*
* @package Swoole\IDEHelper\StubGenerators
*/
class SwooleAsync extends AbstractStubGenerator
{
/**
* @inheritDoc
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE_ASYNC;
return $this;
}
}
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use DirectoryIterator;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
use Swoole\IDEHelper\Exception;
use Symfony\Component\Filesystem\Filesystem;
/**
* Class SwooleLib
*
* @package Swoole\IDEHelper\StubGenerators
* @see https://github.com/swoole/swoole-src/tree/master/library
* @see https://github.com/swoole/swoole-src/blob/v4.5.6/ext-src/php_swoole_library.h#L5
*/
class SwooleLib extends AbstractStubGenerator
{
private const EXTRA_SRC_FILES = [
"src/ext",
"src/std",
"src/constants.php",
"src/functions.php",
];
/**
* AbstractStubGenerator constructor.
*
* @throws Exception
* @throws ReflectionException
*/
public function __construct()
{
parent::__construct();
$this->extension = "swoole_library";
$this->dirOutput = dirname($this->dirOutput) . DIRECTORY_SEPARATOR . $this->extension;
}
/**
* @inheritDoc
*/
public function export(): void
{
$this->download("library", $this->rf_version, $this->dirOutput);
$extraFiles = self::EXTRA_SRC_FILES;
/** @var DirectoryIterator $file */
foreach (new DirectoryIterator($this->dirOutput) as $file) {
if (!$file->isDot() && ($file->getFilename() != 'src')) {
$extraFiles[] = $file->getFilename();
}
}
$fileSystem = new Filesystem();
foreach ($extraFiles as $file) {
$fileSystem->remove($this->dirOutput . DIRECTORY_SEPARATOR . $file);
}
}
/**
* @inheritDoc
* @throws Exception
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE; // Set to a dummy value temporarily.
$this->rf_version = $_SERVER["SWOOLE_LIB_VERSION"] ?? self::DEFAULT_VERSION;
return $this;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
/**
* Class SwooleOrm
*
* @package Swoole\IDEHelper\StubGenerators
*/
class SwooleOrm extends AbstractStubGenerator
{
/**
* @inheritDoc
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE_ORM;
return $this;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
/**
* Class SwoolePostgresql
*
* @package Swoole\IDEHelper\StubGenerators
*/
class SwoolePostgresql extends AbstractStubGenerator
{
/**
* @inheritDoc
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE_POSTGRESQL;
return $this;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
/**
* Class SwooleSerialize
*
* @package Swoole\IDEHelper\StubGenerators
*/
class SwooleSerialize extends AbstractStubGenerator
{
/**
* @inheritDoc
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE_SERIALIZE;
return $this;
}
}
@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Swoole\IDEHelper\StubGenerators;
use Swoole\IDEHelper\AbstractStubGenerator;
use Swoole\IDEHelper\Constant;
/**
* Class SwooleZookeeper
*
* @package Swoole\IDEHelper\StubGenerators
*/
class SwooleZookeeper extends AbstractStubGenerator
{
/**
* @inheritDoc
*/
protected function init(): AbstractStubGenerator
{
$this->extension = Constant::EXT_SWOOLE_ZOOKEEPER;
return $this;
}
}