diff --git a/composer.json b/composer.json index 16abba81..f8879a71 100644 --- a/composer.json +++ b/composer.json @@ -44,12 +44,12 @@ "amphp/websocket-client": "^1.0" }, "require-dev": { + "danog/class-finder": "dev-master", "phpdocumentor/reflection-docblock": "^5.2", "vlucas/phpdotenv": "^3", "ennexa/amp-update-cache": "dev-master", - "phpunit/phpunit": "^8", + "phpunit/phpunit": "^9", "amphp/php-cs-fixer-config": "dev-master", - "haydenpierce/class-finder": "^0.4.2", "amphp/http-server": "dev-master", "amphp/http": "^1.6", "ext-ctype": "*", @@ -57,8 +57,7 @@ "danog/7to5": "^1", "vimeo/psalm": "dev-master", "phpstan/phpstan": "^0.12.14", - "friendsofphp/php-cs-fixer": "^2.16", - "squizlabs/php_codesniffer": "^3.5" + "friendsofphp/php-cs-fixer": "^2" }, "suggest": { "ext-libtgvoip": "Install the php-libtgvoip extension to make phone calls (https://github.com/danog/php-libtgvoip)" diff --git a/src/danog/MadelineProto/APIWrapper.php b/src/danog/MadelineProto/APIWrapper.php index 57c0e077..1d24452f 100644 --- a/src/danog/MadelineProto/APIWrapper.php +++ b/src/danog/MadelineProto/APIWrapper.php @@ -122,7 +122,7 @@ final class APIWrapper } /** - * Property list + * Property list. * * @return array */ @@ -132,7 +132,7 @@ final class APIWrapper } /** - * Sleep function + * Sleep function. * * @return array */ diff --git a/src/danog/MadelineProto/ContextConnector.php b/src/danog/MadelineProto/ContextConnector.php index 490e1f7e..23211201 100644 --- a/src/danog/MadelineProto/ContextConnector.php +++ b/src/danog/MadelineProto/ContextConnector.php @@ -55,7 +55,7 @@ class ContextConnector implements Connector $this->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING); return $result->getSocket(); } catch (\Throwable $e) { - if (defined('MADELINEPROTO_TEST') && \constant("MADELINEPROTO_TEST") === 'pony') { + if (\defined('MADELINEPROTO_TEST') && \constant("MADELINEPROTO_TEST") === 'pony') { throw $e; } $this->logger->logger('Connection failed: '.$e, \danog\MadelineProto\Logger::ERROR); diff --git a/src/danog/MadelineProto/Coroutine.php b/src/danog/MadelineProto/Coroutine.php index 08c39046..ea292191 100644 --- a/src/danog/MadelineProto/Coroutine.php +++ b/src/danog/MadelineProto/Coroutine.php @@ -281,7 +281,7 @@ final class Coroutine implements Promise, \ArrayAccess, JsonSerializable } /** - * Obtain + * Obtain. * * @return string */ diff --git a/src/danog/MadelineProto/DataCenter.php b/src/danog/MadelineProto/DataCenter.php index 1e6d4186..45ada40e 100644 --- a/src/danog/MadelineProto/DataCenter.php +++ b/src/danog/MadelineProto/DataCenter.php @@ -146,6 +146,7 @@ class DataCenter /** @psalm-suppress UndefinedPropertyFetch */ $array[$id]['permAuthKey']['authorized'] = $socket->authorized; } + $array[$id] = []; } } $this->setDataCenterConnections($array); diff --git a/src/danog/MadelineProto/Db/RedisArray.php b/src/danog/MadelineProto/Db/RedisArray.php index c85addc4..0470cb16 100644 --- a/src/danog/MadelineProto/Db/RedisArray.php +++ b/src/danog/MadelineProto/Db/RedisArray.php @@ -208,10 +208,10 @@ class RedisArray extends SqlArray return call(function () { $iterator = $this->getIterator(); $result = []; - $len = strlen($this->rKey('')); + $len = \strlen($this->rKey('')); while (yield $iterator->advance()) { [$key, $value] = $iterator->getCurrent(); - $key = substr($key, $len); + $key = \substr($key, $len); $result[$key] = $value; } return $result; diff --git a/src/danog/MadelineProto/Ipc/Wrapper.php b/src/danog/MadelineProto/Ipc/Wrapper.php index e4bca757..0a05c254 100644 --- a/src/danog/MadelineProto/Ipc/Wrapper.php +++ b/src/danog/MadelineProto/Ipc/Wrapper.php @@ -53,7 +53,8 @@ class Wrapper extends ClientAbstract * @param mixed $data Payload data * @param SessionPaths $ipc IPC URI * - * @return \Generator|Promise, mixed, Wrapper> + * @return \Generator + * @psalm-return \Generator|Promise, mixed, Wrapper> */ public static function create(&$data, SessionPaths $session, Logger $logger): \Generator { diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 5f37e014..986374db 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -862,9 +862,13 @@ class MTProto extends AsyncConstruct implements TLCallback $this->rpcLoop = new PeriodicLoopInternal($this, [$this, 'rpcReport'], 'config', 60 * 1000); } if (!$this->ipcServer) { - $this->ipcServer = new Server($this); - $this->ipcServer->setSettings($this->settings->getIpc()); - $this->ipcServer->setIpcPath($this->wrapper->session); + try { + $this->ipcServer = new Server($this); + $this->ipcServer->setSettings($this->settings->getIpc()); + $this->ipcServer->setIpcPath($this->wrapper->session); + } catch (\Throwable $e) { + $this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR); + } } $this->callCheckerLoop->start(); $this->serializeLoop->start(); @@ -872,7 +876,11 @@ class MTProto extends AsyncConstruct implements TLCallback $this->configLoop->start(); $this->checkTosLoop->start(); $this->rpcLoop->start(); - $this->ipcServer->start(); + try { + $this->ipcServer->start(); + } catch (\Throwable $e) { + $this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR); + } } /** * Stop all internal loops. @@ -1167,6 +1175,9 @@ class MTProto extends AsyncConstruct implements TLCallback */ public function unreference(): void { + if (!isset($this->logger)) { + $this->logger = new Logger(new \danog\MadelineProto\Settings\Logger); + } $this->logger->logger("Will unreference instance"); if (isset(self::$references[\spl_object_hash($this)])) { unset(self::$references[\spl_object_hash($this)]); @@ -1175,21 +1186,25 @@ class MTProto extends AsyncConstruct implements TLCallback if (isset($this->seqUpdater)) { $this->seqUpdater->signal(true); } - $channelIds = []; - foreach ($this->channels_state->get() as $state) { - $channelIds[] = $state->getChannel(); - } - \sort($channelIds); - foreach ($channelIds as $channelId) { - if (isset($this->feeders[$channelId])) { - $this->feeders[$channelId]->signal(true); + if (isset($this->channels_state)) { + $channelIds = []; + foreach ($this->channels_state->get() as $state) { + $channelIds[] = $state->getChannel(); } - if (isset($this->updaters[$channelId])) { - $this->updaters[$channelId]->signal(true); + \sort($channelIds); + foreach ($channelIds as $channelId) { + if (isset($this->feeders[$channelId])) { + $this->feeders[$channelId]->signal(true); + } + if (isset($this->updaters[$channelId])) { + $this->updaters[$channelId]->signal(true); + } } } - foreach ($this->datacenter->getDataCenterConnections() as $datacenter) { - $datacenter->disconnect(); + if (isset($this->datacenter)) { + foreach ($this->datacenter->getDataCenterConnections() as $datacenter) { + $datacenter->disconnect(); + } } $this->logger->logger("Unreferenced instance"); } diff --git a/src/danog/MadelineProto/PhpDoc/ClassDoc.php b/src/danog/MadelineProto/PhpDoc/ClassDoc.php index 43128ea2..6aff681b 100644 --- a/src/danog/MadelineProto/PhpDoc/ClassDoc.php +++ b/src/danog/MadelineProto/PhpDoc/ClassDoc.php @@ -24,22 +24,25 @@ class ClassDoc extends GenericDoc * @var array */ private array $methods = []; - public function __construct(ReflectionClass $reflectionClass) + public function __construct(PhpDocBuilder $builder, ReflectionClass $reflectionClass) { + $this->builder = $builder; + $this->name = $reflectionClass->getName(); $doc = $reflectionClass->getDocComment(); if (!$doc) { Logger::log($reflectionClass->getName()." has no PHPDOC"); $this->ignore = true; return; } - $doc = PhpDocBuilder::$factory->create($doc); + $doc = $this->builder->getFactory()->create($doc); - parent::__construct($doc); + parent::__construct($doc, $reflectionClass); $tags = $doc->getTags(); foreach ($tags as $tag) { if ($tag instanceof Property) { $this->properties[$tag->getVariableName()] = new PropertyDoc( + $this->builder, $tag->getName(), $tag->getType(), $tag->getDescription() @@ -52,6 +55,7 @@ class ClassDoc extends GenericDoc $type = \str_replace('@property ', '', $type); $description ??= ''; $this->properties[$varName] = new PropertyDoc( + $this->builder, $varName, $type, $description @@ -66,8 +70,8 @@ class ClassDoc extends GenericDoc } $description = ''; if ($refl->getDocComment()) { - $docConst = PhpDocBuilder::$factory->create($refl->getDocComment()); - if (\in_array($refl->getDeclaringClass()->getName(), PhpDocBuilder::DISALLOW)) { + $docConst = $this->builder->getFactory()->create($refl->getDocComment()); + if ($this->builder->shouldIgnore($refl->getDeclaringClass()->getName())) { continue; } $description .= $docConst->getSummary(); @@ -88,12 +92,40 @@ class ClassDoc extends GenericDoc foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { - if (str_starts_with($method->getName(), '__') && $method !== '__construct') continue; - $this->methods[$method->getName()] = new MethodDoc($method); + if (str_starts_with($method->getName(), '__') && $method !== '__construct') { + continue; + } + $this->methods[$method->getName()] = new MethodDoc($this->builder, $method); } + + $this->methods = \array_filter($this->methods, fn (MethodDoc $doc): bool => !$doc->shouldIgnore()); + //$this->properties = \array_filter($this->properties, fn (PropertyDoc $doc): bool => !$doc->shouldIgnore()); } public function format(): string { + $init = parent::format(); + $methods = ''; + $properties = ''; + if ($this->methods) { + $init .= "\n"; + foreach ($this->methods as $method) { + $init .= "* ".$method->getSignature()."\n"; + } + $init .= "\n"; + $init .= "## Methods:\n$methods\n"; + foreach ($this->methods as $method) { + $init .= $method->format(); + $init .= "\n"; + } + } + if ($properties) { + $init .= "## Properties:\n$properties\n"; + /*foreach ($this->properties as $property) { + $init .= $property->format(); + $init .= "\n"; + }*/ + } + return $init; } } diff --git a/src/danog/MadelineProto/PhpDoc/FunctionDoc.php b/src/danog/MadelineProto/PhpDoc/FunctionDoc.php new file mode 100644 index 00000000..a3abacdc --- /dev/null +++ b/src/danog/MadelineProto/PhpDoc/FunctionDoc.php @@ -0,0 +1,25 @@ +builder = $builder; + $this->nameGenericDoc = $reflectionClass->getName(); + $doc = $reflectionClass->getDocComment(); + if (!$doc) { + Logger::log($reflectionClass->getName()." has no PHPDOC"); + $this->ignore = true; + return; + } + $doc = $this->builder->getFactory()->create($doc); + + parent::__construct($builder, $reflectionClass); + } +} diff --git a/src/danog/MadelineProto/PhpDoc/GenericDoc.php b/src/danog/MadelineProto/PhpDoc/GenericDoc.php index 8d858119..ba8d4a16 100644 --- a/src/danog/MadelineProto/PhpDoc/GenericDoc.php +++ b/src/danog/MadelineProto/PhpDoc/GenericDoc.php @@ -2,39 +2,60 @@ namespace danog\MadelineProto\PhpDoc; +use danog\MadelineProto\PhpDocBuilder; use phpDocumentor\Reflection\DocBlock; use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\Tags\Author; use phpDocumentor\Reflection\DocBlock\Tags\Deprecated; use phpDocumentor\Reflection\DocBlock\Tags\Generic; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen; +use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url; use phpDocumentor\Reflection\DocBlock\Tags\See; abstract class GenericDoc { + /** + * Builder instance. + */ + protected PhpDocBuilder $builder; + /** + * Name. + */ + protected string $name; /** * Title. */ - private string $title; + protected string $title; /** * Description. */ - private Description $description; + protected Description $description; /** * See also array. * - * @var array + * @var array */ - private array $seeAlso = []; + protected array $seeAlso = []; /** * Author. */ - private Author $author; + protected Author $author; /** * Ignore this class. */ protected bool $ignore = false; - public function __construct(DocBlock $doc) + /** + * Class name. + */ + protected string $className; + /** + * Class namespace. + */ + protected string $namespace; + public function __construct(DocBlock $doc, $reflectionClass) { + $this->className = $reflectionClass->getName(); + $this->namespace = \str_replace('/', '\\', \dirname(\str_replace('\\', '/', $this->className))); $this->title = $doc->getSummary(); $this->description = $doc->getDescription(); $tags = $doc->getTags(); @@ -53,14 +74,65 @@ abstract class GenericDoc break; } if ($tag instanceof See) { - $this->seeAlso[$tag->getReference()->__toString()] = $tag->render(); + $this->seeAlso[$tag->getReference()->__toString()] = $tag; } } } + public function seeAlso(): string + { + $seeAlso = ''; + foreach ($this->seeAlso as $see) { + $ref = $see->getReference(); + if ($ref instanceof Fqsen) { + $ref = (string) $ref; + $ref = $this->builder->resolveTypeAlias($this->className, $ref); + $refExpl = \explode("\\", $ref); + $name = \array_pop($refExpl); + $namespace = \explode('/', $this->namespace); + $count = \count($refExpl); + foreach ($namespace as $k => $name) { + if (isset($refExpl[$k]) && $refExpl[$k] === $name) { + $count--; + } else { + break; + } + } + + $ref = \str_repeat('../', $count) + .\implode('/', \array_slice($refExpl, $count)) + ."/$name.md"; + + $desc = $see->getDescription() ?: $ref; + $seeAlso .= "* [$desc]($ref)\n"; + } + if ($ref instanceof Url) { + $desc = $see->getDescription() ?: $ref; + $seeAlso .= "* [$desc]($ref)\n"; + } + } + if ($seeAlso) { + $seeAlso = "\n#### See also: \n$seeAlso\n\n"; + } + return $seeAlso; + } public function format(): string { - return ''; + $seeAlso = $this->seeAlso(); + return <<name: $this->title + description: $this->description + image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png + --- + # `$this->name`: $this->title + [Back to API index](index.md) + + > Author: $this->author + + $this->description + $seeAlso + EOF; } public function shouldIgnore(): bool diff --git a/src/danog/MadelineProto/PhpDoc/MethodDoc.php b/src/danog/MadelineProto/PhpDoc/MethodDoc.php index 3ca9a462..0f2138c7 100644 --- a/src/danog/MadelineProto/PhpDoc/MethodDoc.php +++ b/src/danog/MadelineProto/PhpDoc/MethodDoc.php @@ -8,27 +8,37 @@ use phpDocumentor\Reflection\DocBlock\Description; use phpDocumentor\Reflection\DocBlock\Tags\Generic; use phpDocumentor\Reflection\DocBlock\Tags\Param; use phpDocumentor\Reflection\DocBlock\Tags\Return_; +use ReflectionFunctionAbstract; use ReflectionMethod; class MethodDoc extends GenericDoc { private $return; private array $params = []; - public function __construct(ReflectionMethod $method) + public function __construct(PhpDocBuilder $phpDocBuilder, ReflectionFunctionAbstract $method) { + $this->builder = $phpDocBuilder; + $this->name = $method->getName(); $doc = $method->getDocComment(); if (!$doc) { $this->ignore = true; - Logger::log($method->getDeclaringClass()->getName().'::'.$method->getName().' has no PHPDOC!'); + if ($method instanceof ReflectionMethod) { + Logger::log($method->getDeclaringClass()->getName().'::'.$method->getName().' has no PHPDOC!'); + } else { + Logger::log($method->getName().' has no PHPDOC!'); + } return; } - $doc = PhpDocBuilder::$factory->create($doc); + $doc = $this->builder->getFactory()->create($doc); - parent::__construct($doc); + parent::__construct($doc, $method instanceof ReflectionMethod ? $method->getDeclaringClass() : $method); foreach ($doc->getTags() as $tag) { if ($tag instanceof Param && !isset($this->params[$tag->getVariableName()])) { - $this->params[$tag->getVariableName()] = $tag; + $this->params[$tag->getVariableName()] = [ + $tag->getType(), + $tag->getDescription() + ]; } elseif ($tag instanceof Return_ && !$this->return) { $this->return = $tag; } elseif ($tag instanceof Generic && $tag->getName() === 'psalm-return') { @@ -38,7 +48,7 @@ class MethodDoc extends GenericDoc $description .= ' '; [$varName, $description] = \explode(" ", $description, 2); if (!$description && isset($this->params[$varName])) { - $description = $this->params[$varName]->getDescription(); + $description = (string) $this->params[$varName][1]; } else { $description = new Description($description); } @@ -50,8 +60,36 @@ class MethodDoc extends GenericDoc } } - public function render(): string + public function getSignature(): string { - return ''; + $sig = $this->name; + $sig .= "("; + foreach ($this->params as $var => [$type, $description]) { + $sig .= $type.' '; + $sig .= "$".$var; + $sig .= ', '; + } + $sig = \trim($sig, ', '); + $sig .= ')'; + if ($this->return) { + $sig .= ': '; + $sig .= $this->return; + } + return $sig; + } + public function format(): string + { + $sig = '### '.$this->getSignature(); + $sig .= "\n\n"; + $sig .= $this->title; + $sig .= "\n"; + $sig .= $this->description; + $sig .= "\n"; + if ($this->return && $this->return->getDescription() && $this->return->getDescription()->render()) { + $sig .= "\nReturn value: ".$this->return->getDescription()."\n"; + } + $sig .= $this->seeAlso(); + $sig .= "\n"; + return $sig; } } diff --git a/src/danog/MadelineProto/PhpDoc/PropertyDoc.php b/src/danog/MadelineProto/PhpDoc/PropertyDoc.php index 94b7db63..372de094 100644 --- a/src/danog/MadelineProto/PhpDoc/PropertyDoc.php +++ b/src/danog/MadelineProto/PhpDoc/PropertyDoc.php @@ -2,11 +2,11 @@ namespace danog\MadelineProto\PhpDoc; -use phpDocumentor\Reflection\DocBlock\Tags\Property; +use danog\MadelineProto\PhpDocBuilder; class PropertyDoc { - public function __construct(string $name, $type, $description) + public function __construct(PhpDocBuilder $phpDocBuilder, string $name, $type, $description) { } diff --git a/src/danog/MadelineProto/PhpDocBuilder.php b/src/danog/MadelineProto/PhpDocBuilder.php index b3869a7d..21881d93 100644 --- a/src/danog/MadelineProto/PhpDocBuilder.php +++ b/src/danog/MadelineProto/PhpDocBuilder.php @@ -18,109 +18,159 @@ namespace danog\MadelineProto; -use danog\MadelineProto\Async\AsyncConstruct; -use danog\MadelineProto\Db\DbPropertiesTrait; -use danog\MadelineProto\Files\Server; -use danog\MadelineProto\MTProtoTools\Crypt; -use danog\MadelineProto\MTProtoTools\GarbageCollector; -use danog\MadelineProto\MTProtoTools\MinDatabase; -use danog\MadelineProto\MTProtoTools\PasswordCalculator; -use danog\MadelineProto\MTProtoTools\ReferenceDatabase; -use danog\MadelineProto\MTProtoTools\UpdatesState; +use danog\ClassFinder\ClassFinder; use danog\MadelineProto\PhpDoc\ClassDoc; -use danog\MadelineProto\TL\TL; -use danog\MadelineProto\TL\TLCallback; -use danog\MadelineProto\TL\TLConstructors; -use danog\MadelineProto\TL\TLMethods; -use danog\MadelineProto\TON\ADNLConnection; -use danog\MadelineProto\TON\APIFactory as TAPIFactory; -use danog\MadelineProto\TON\InternalDoc as TInternalDoc; -use danog\MadelineProto\TON\Lite; -use HaydenPierce\ClassFinder\ClassFinder; -use phpDocumentor\Reflection\DocBlock\Tags\Author; +use danog\MadelineProto\PhpDoc\FunctionDoc; use phpDocumentor\Reflection\DocBlockFactory; use ReflectionClass; -use ReflectionMethod; +use ReflectionFunction; class PhpDocBuilder { - const DISALLOW = [ - AnnotationsBuilder::class, - APIFactory::class, - APIWrapper::class, - AbstractAPIFactory::class, - Bug74586Exception::class, - Connection::class, - ContextConnector::class, - DataCenter::class, - DataCenterConnection::class, - DoHConnector::class, - DocsBuilder::class, - InternalDoc::class, - Lang::class, - LightState::class, - Magic::class, - PhpDocBuilder::class, - RSA::class, - Serialization::class, - SessionPaths::class, - SettingsEmpty::class, - SettingsAbstract::class, - Snitch::class, - AsyncConstruct::class, - Server::class, // Remove when done - VoIP::class, - - Crypt::class, - NothingInTheSocketException::class, - - GarbageCollector::class, - MinDatabase::class, - PasswordCalculator::class, - ReferenceDatabase::class, - UpdatesState::class, - - TL::class, - TLConstructors::class, - TLMethods::class, - TLCallback::class, - - ADNLConnection::class, - TAPIFactory::class, - TInternalDoc::class, - Lite::class, - - \ArrayIterator::class, - ]; - public static DocBlockFactory $factory; + /** + * Namespace. + */ + private string $namespace; + /** + * Scan mode. + */ + private int $mode; + /** + * Docblock factory. + */ + private DocBlockFactory $factory; + /** + * Classes/interfaces/traits to ignore. + * + * @var ?callable + * @psalm-var null|callable(class-string) + */ + private $ignore; + /** + * Output directory. + */ private string $output; - public function __construct(string $output) + /** + * Use map. + * + * array> + */ + private array $useMap = []; + /** + * Create docblock builder. + * + * @param string $namespace Namespace + * @param int $mode Finder mode, an OR-selection of ClassFinder::ALLOW_* + * + * @return self + */ + public static function fromNamespace(string $namespace, int $mode = ClassFinder::ALLOW_ALL): self { - self::$factory = DocBlockFactory::createInstance(); - $this->output = $output; + return new self($namespace, $mode); } - public function run() + /** + * Constructor. + * + * @param string $namespace + * @param int $mode + */ + private function __construct(string $namespace, int $mode) { - $classes = ClassFinder::getClassesInNamespace('danog\\MadelineProto', ClassFinder::RECURSIVE_MODE); + $this->factory = DocBlockFactory::createInstance(); + $this->namespace = $namespace; + $this->mode = $mode; + } + /** + * Set filter to ignore certain classes. + * + * @param callable $ignore + * + * @psalm-param callable(class-string) $ignore + * + * @return self + */ + public function setFilter(callable $ignore): self + { + $this->ignore = $ignore; + + return $this; + } + /** + * Set output directory. + * + * @param string $output Output directory + * + * @return self + */ + public function setOutput(string $output): self + { + $this->output = $output; + + return $this; + } + /** + * Run documentor. + * + * @return self + */ + public function run(): self + { + $classes = ClassFinder::getClassesInNamespace($this->namespace, $this->mode | ClassFinder::RECURSIVE_MODE); foreach ($classes as $class) { - if (\in_array($class, self::DISALLOW) || str_starts_with($class, 'danog\\MadelineProto\\Ipc') - || str_starts_with($class, 'danog\\MadelineProto\\Loop\\Update') - || str_starts_with($class, 'danog\\MadelineProto\\Loop\\Connection') - || str_starts_with($class, 'danog\\MadelineProto\\MTProto\\') - || str_starts_with($class, 'danog\\MadelineProto\\MTProtoSession\\') - || str_starts_with($class, 'danog\\MadelineProto\\PhpDoc\\') - || str_starts_with($class, 'danog\\MadelineProto\\Db\\NullCache')) { - continue; - } - $class = new ReflectionClass($class); - if ($class->isTrait()) { + $this->addTypeAliases($class); + } + foreach ($classes as $class) { + if ($this->ignore && ($this->ignore)($class)) { continue; } + $class = \function_exists($class) + ? new ReflectionFunction($class) + : new ReflectionClass($class); $this->generate($class); } - $this->generate(new ReflectionClass(DbPropertiesTrait::class)); - } + return $this; + } + /** + * Resolve type alias. + * + * @internal + * + * @param string $fromClass Class from where this function is called + * @param string $name Name to resolve + * + * @psalm-param class-string $fromClass Class from where this function is called + * @psalm-param class-string $name Name to resolve + * + * @return string + */ + public function resolveTypeAlias(string $fromClass, string $name): string + { + return $this->useMap[$fromClass][$name] ?? $name; + } + /** + * Add type alias. + * + * @param string $class + * + * @psalm-param class-string $class + * + * @return void + */ + private function addTypeAliases(string $class) + { + $reflectionClass = \function_exists($class) + ? new ReflectionFunction($class) + : new ReflectionClass($class); + $payload = \file_get_contents($reflectionClass->getFileName()); + \preg_match_all("/use *(function)? +(.*?)(?: +as +(.+))? *;/", $payload, $matches, PREG_SET_ORDER|PREG_UNMATCHED_AS_NULL); + foreach ($matches as [, $function, $import, $alias]) { + $import = "\\$import"; + $alias ??= \basename(\str_replace('\\', '/', $import)); + $this->useMap[$class][$alias] = $import; + $this->useMap[$class]['\\'.$alias] = $import; + } + } /** * Create directory recursively. * @@ -137,25 +187,52 @@ class PhpDocBuilder return $file; } - private function generate(ReflectionClass $class): void + /** + * Generate documentation for class. + * + * @param ReflectionClass|ReflectionFunction $class Class + * + * @return void + */ + private function generate($class): void { $name = $class->getName(); $fName = $this->output; - $fName .= \str_replace(['\\', 'danog\\MadelineProto'], ['/', ''], $name); + $fName .= \str_replace('\\', DIRECTORY_SEPARATOR, $name); $fName .= '.md'; + + $class = $class instanceof ReflectionFunction + ? new FunctionDoc($this, $class) + : new ClassDoc($this, $class); + if ($class->shouldIgnore()) { + return; + } + $class = $class->format(); + $handle = \fopen(self::createDir($fName), 'w+'); + \fwrite($handle, $class); + \fclose($handle); + } - $class = new ClassDoc($class); - /* - \fwrite($handle, "---\n"); - \fwrite($handle, "title: $name: $title\n"); - \fwrite($handle, "description: $description\n"); - \fwrite($handle, "image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png\n"); - \fwrite($handle, "---\n"); - \fwrite($handle, "# $name: $title\n"); - \fwrite($handle, "[Back to API index](index.md)\n\n"); + /** + * Get docblock factory. + * + * @internal + * + * @return DocBlockFactory + */ + public function getFactory(): DocBlockFactory + { + return $this->factory; + } - \fwrite($handle, "> Author: $author \n"); -*/ + /** + * Whether should ignore trait/class/interface. + * + * @return bool + */ + public function shouldIgnore(string $class): bool + { + return ($this->ignore)($class); } } diff --git a/src/danog/MadelineProto/PredefinedConnector.php b/src/danog/MadelineProto/PredefinedConnector.php deleted file mode 100644 index b3d9bbc7..00000000 --- a/src/danog/MadelineProto/PredefinedConnector.php +++ /dev/null @@ -1 +0,0 @@ -type === MadelineProtoLogger::CALLABLE_LOGGER && !\is_callable($extra)) { - $this->setType(MadelineProtoLogger::NO_LOGGER); + $this->setType((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') + ? MadelineProtoLogger::ECHO_LOGGER + : MadelineProtoLogger::FILE_LOGGER); return $this; } $this->extra = $extra; diff --git a/tests/danog/MadelineProto/DataCenterTest.php b/tests/danog/MadelineProto/DataCenterTest.php index 8bcadc1e..05788aea 100644 --- a/tests/danog/MadelineProto/DataCenterTest.php +++ b/tests/danog/MadelineProto/DataCenterTest.php @@ -8,7 +8,7 @@ use danog\MadelineProto\MTProto; use danog\MadelineProto\Tools; use PHPUnit\Framework\TestCase; -\define("MADELINEPROTO_TEST", "pony"); +\define('MADELINEPROTO_TEST', 'pony'); final class DataCenterTest extends TestCase {