This commit is contained in:
wangjinlei
2022-04-06 18:02:49 +08:00
parent e34f87de36
commit c1885928ff
262 changed files with 18633 additions and 0 deletions

281
vendor/composer/InstalledVersions.php vendored Normal file
View File

@@ -0,0 +1,281 @@
<?php
namespace Composer;
use Composer\Semver\VersionParser;
class InstalledVersions
{
private static $installed = array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '2c373f1587474940063688399f43ef8f93fed848',
'name' => 'topthink/think',
),
'versions' =>
array (
'phpmailer/phpmailer' =>
array (
'pretty_version' => 'v6.6.0',
'version' => '6.6.0.0',
'aliases' =>
array (
),
'reference' => 'e43bac82edc26ca04b36143a48bde1c051cfd5b1',
),
'tecnickcom/tcpdf' =>
array (
'pretty_version' => '6.4.4',
'version' => '6.4.4.0',
'aliases' =>
array (
),
'reference' => '42cd0f9786af7e5db4fcedaa66f717b0d0032320',
),
'topthink/framework' =>
array (
'pretty_version' => 'v5.0.24',
'version' => '5.0.24.0',
'aliases' =>
array (
),
'reference' => 'c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be',
),
'topthink/think' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '2c373f1587474940063688399f43ef8f93fed848',
),
'topthink/think-captcha' =>
array (
'pretty_version' => 'v1.0.8',
'version' => '1.0.8.0',
'aliases' =>
array (
),
'reference' => '1d64363c814c92f6086c4fa5e3223fe7e23db09d',
),
'topthink/think-helper' =>
array (
'pretty_version' => 'v3.0.0',
'version' => '3.0.0.0',
'aliases' =>
array (
),
'reference' => '8ba5f66e68106369fcc3211e7d2dbaf7bc9ce455',
),
'topthink/think-installer' =>
array (
'pretty_version' => 'v1.0.14',
'version' => '1.0.14.0',
'aliases' =>
array (
),
'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1',
),
'topthink/think-queue' =>
array (
'pretty_version' => 'v1.1.4',
'version' => '1.1.4.0',
'aliases' =>
array (
),
'reference' => 'ad709611d516e13d6760234bc98e91faa901cae8',
),
'weiwei/api-doc' =>
array (
'pretty_version' => '1.6.2',
'version' => '1.6.2.0',
'aliases' =>
array (
),
'reference' => '6c2c3c03ce1139275cc5a5057677175ed8691e19',
),
),
);
public static function getInstalledPackages()
{
return array_keys(self::$installed['versions']);
}
public static function isInstalled($packageName)
{
return isset(self::$installed['versions'][$packageName]);
}
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
public static function getVersionRanges($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
$ranges = array();
if (isset(self::$installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = self::$installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', self::$installed['versions'][$packageName])) {
$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
public static function getVersion($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
if (!isset(self::$installed['versions'][$packageName]['version'])) {
return null;
}
return self::$installed['versions'][$packageName]['version'];
}
public static function getPrettyVersion($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return self::$installed['versions'][$packageName]['pretty_version'];
}
public static function getReference($packageName)
{
if (!isset(self::$installed['versions'][$packageName])) {
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
if (!isset(self::$installed['versions'][$packageName]['reference'])) {
return null;
}
return self::$installed['versions'][$packageName]['reference'];
}
public static function getRootPackage()
{
return self::$installed['root'];
}
public static function getRawData()
{
return self::$installed;
}
public static function reload($data)
{
self::$installed = $data;
}
}

96
vendor/composer/installed.php vendored Normal file
View File

@@ -0,0 +1,96 @@
<?php return array (
'root' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '2c373f1587474940063688399f43ef8f93fed848',
'name' => 'topthink/think',
),
'versions' =>
array (
'phpmailer/phpmailer' =>
array (
'pretty_version' => 'v6.6.0',
'version' => '6.6.0.0',
'aliases' =>
array (
),
'reference' => 'e43bac82edc26ca04b36143a48bde1c051cfd5b1',
),
'tecnickcom/tcpdf' =>
array (
'pretty_version' => '6.4.4',
'version' => '6.4.4.0',
'aliases' =>
array (
),
'reference' => '42cd0f9786af7e5db4fcedaa66f717b0d0032320',
),
'topthink/framework' =>
array (
'pretty_version' => 'v5.0.24',
'version' => '5.0.24.0',
'aliases' =>
array (
),
'reference' => 'c255c22b2f5fa30f320ecf6c1d29f7740eb3e8be',
),
'topthink/think' =>
array (
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'aliases' =>
array (
),
'reference' => '2c373f1587474940063688399f43ef8f93fed848',
),
'topthink/think-captcha' =>
array (
'pretty_version' => 'v1.0.8',
'version' => '1.0.8.0',
'aliases' =>
array (
),
'reference' => '1d64363c814c92f6086c4fa5e3223fe7e23db09d',
),
'topthink/think-helper' =>
array (
'pretty_version' => 'v3.0.0',
'version' => '3.0.0.0',
'aliases' =>
array (
),
'reference' => '8ba5f66e68106369fcc3211e7d2dbaf7bc9ce455',
),
'topthink/think-installer' =>
array (
'pretty_version' => 'v1.0.14',
'version' => '1.0.14.0',
'aliases' =>
array (
),
'reference' => 'eae1740ac264a55c06134b6685dfb9f837d004d1',
),
'topthink/think-queue' =>
array (
'pretty_version' => 'v1.1.4',
'version' => '1.1.4.0',
'aliases' =>
array (
),
'reference' => 'ad709611d516e13d6760234bc98e91faa901cae8',
),
'weiwei/api-doc' =>
array (
'pretty_version' => '1.6.2',
'version' => '1.6.2.0',
'aliases' =>
array (
),
'reference' => '6c2c3c03ce1139275cc5a5057677175ed8691e19',
),
),
);

26
vendor/composer/platform_check.php vendored Normal file
View File

@@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 50500)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 5.5.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

19
vendor/doctrine/annotations/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2013 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
vendor/doctrine/annotations/README.md vendored Normal file
View File

@@ -0,0 +1,18 @@
# Doctrine Annotations
[![Build Status](https://github.com/doctrine/annotations/workflows/Continuous%20Integration/badge.svg?label=build)](https://github.com/doctrine/persistence/actions)
[![Dependency Status](https://www.versioneye.com/package/php--doctrine--annotations/badge.png)](https://www.versioneye.com/package/php--doctrine--annotations)
[![Reference Status](https://www.versioneye.com/php/doctrine:annotations/reference_badge.svg)](https://www.versioneye.com/php/doctrine:annotations/references)
[![Total Downloads](https://poser.pugx.org/doctrine/annotations/downloads.png)](https://packagist.org/packages/doctrine/annotations)
[![Latest Stable Version](https://img.shields.io/packagist/v/doctrine/annotations.svg?label=stable)](https://packagist.org/packages/doctrine/annotations)
Docblock Annotations Parser library (extracted from [Doctrine Common](https://github.com/doctrine/common)).
## Documentation
See the [doctrine-project website](https://www.doctrine-project.org/projects/doctrine-annotations/en/latest/index.html).
## Contributing
When making a pull request, make sure your changes follow the
[Coding Standard Guidelines](https://www.doctrine-project.org/projects/doctrine-coding-standard/en/current/reference/index.html#introduction).

View File

@@ -0,0 +1,44 @@
{
"name": "doctrine/annotations",
"type": "library",
"description": "Docblock Annotations Parser",
"keywords": ["annotations", "docblock", "parser"],
"homepage": "https://www.doctrine-project.org/projects/annotations.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Benjamin Eberlei", "email": "kontakt@beberlei.de"},
{"name": "Jonathan Wage", "email": "jonwage@gmail.com"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^7.1 || ^8.0",
"ext-tokenizer": "*",
"doctrine/lexer": "1.*",
"psr/cache": "^1 || ^2 || ^3"
},
"require-dev": {
"doctrine/cache": "^1.11 || ^2.0",
"doctrine/coding-standard": "^6.0 || ^8.1",
"phpstan/phpstan": "^0.12.20",
"phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5",
"symfony/cache": "^4.4 || ^5.2"
},
"config": {
"sort-packages": true
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" }
},
"autoload-dev": {
"psr-4": {
"Doctrine\\Performance\\Common\\Annotations\\": "tests/Doctrine/Performance/Common/Annotations",
"Doctrine\\Tests\\Common\\Annotations\\": "tests/Doctrine/Tests/Common/Annotations"
},
"files": [
"tests/Doctrine/Tests/Common/Annotations/Fixtures/functions.php",
"tests/Doctrine/Tests/Common/Annotations/Fixtures/SingleClassLOC1000.php"
]
}
}

View File

@@ -0,0 +1,252 @@
Handling Annotations
====================
There are several different approaches to handling annotations in PHP.
Doctrine Annotations maps docblock annotations to PHP classes. Because
not all docblock annotations are used for metadata purposes a filter is
applied to ignore or skip classes that are not Doctrine annotations.
Take a look at the following code snippet:
.. code-block:: php
namespace MyProject\Entities;
use Doctrine\ORM\Mapping AS ORM;
use Symfony\Component\Validator\Constraints AS Assert;
/**
* @author Benjamin Eberlei
* @ORM\Entity
* @MyProject\Annotations\Foobarable
*/
class User
{
/**
* @ORM\Id @ORM\Column @ORM\GeneratedValue
* @dummy
* @var int
*/
private $id;
/**
* @ORM\Column(type="string")
* @Assert\NotEmpty
* @Assert\Email
* @var string
*/
private $email;
}
In this snippet you can see a variety of different docblock annotations:
- Documentation annotations such as ``@var`` and ``@author``. These
annotations are ignored and never considered for throwing an
exception due to wrongly used annotations.
- Annotations imported through use statements. The statement ``use
Doctrine\ORM\Mapping AS ORM`` makes all classes under that namespace
available as ``@ORM\ClassName``. Same goes for the import of
``@Assert``.
- The ``@dummy`` annotation. It is not a documentation annotation and
not ignored. For Doctrine Annotations it is not entirely clear how
to handle this annotation. Depending on the configuration an exception
(unknown annotation) will be thrown when parsing this annotation.
- The fully qualified annotation ``@MyProject\Annotations\Foobarable``.
This is transformed directly into the given class name.
How are these annotations loaded? From looking at the code you could
guess that the ORM Mapping, Assert Validation and the fully qualified
annotation can just be loaded using
the defined PHP autoloaders. This is not the case however: For error
handling reasons every check for class existence inside the
``AnnotationReader`` sets the second parameter $autoload
of ``class_exists($name, $autoload)`` to false. To work flawlessly the
``AnnotationReader`` requires silent autoloaders which many autoloaders are
not. Silent autoloading is NOT part of the `PSR-0 specification
<https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md>`_
for autoloading.
This is why Doctrine Annotations uses its own autoloading mechanism
through a global registry. If you are wondering about the annotation
registry being global, there is no other way to solve the architectural
problems of autoloading annotation classes in a straightforward fashion.
Additionally if you think about PHP autoloading then you recognize it is
a global as well.
To anticipate the configuration section, making the above PHP class work
with Doctrine Annotations requires this setup:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
AnnotationRegistry::registerFile("/path/to/doctrine/lib/Doctrine/ORM/Mapping/Driver/DoctrineAnnotations.php");
AnnotationRegistry::registerAutoloadNamespace("Symfony\Component\Validator\Constraint", "/path/to/symfony/src");
AnnotationRegistry::registerAutoloadNamespace("MyProject\Annotations", "/path/to/myproject/src");
$reader = new AnnotationReader();
AnnotationReader::addGlobalIgnoredName('dummy');
The second block with the annotation registry calls registers all the
three different annotation namespaces that are used.
Doctrine Annotations saves all its annotations in a single file, that is
why ``AnnotationRegistry#registerFile`` is used in contrast to
``AnnotationRegistry#registerAutoloadNamespace`` which creates a PSR-0
compatible loading mechanism for class to file names.
In the third block, we create the actual ``AnnotationReader`` instance.
Note that we also add ``dummy`` to the global list of ignored
annotations for which we do not throw exceptions. Setting this is
necessary in our example case, otherwise ``@dummy`` would trigger an
exception to be thrown during the parsing of the docblock of
``MyProject\Entities\User#id``.
Setup and Configuration
-----------------------
To use the annotations library is simple, you just need to create a new
``AnnotationReader`` instance:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
This creates a simple annotation reader with no caching other than in
memory (in php arrays). Since parsing docblocks can be expensive you
should cache this process by using a caching reader.
To cache annotations, you can create a ``Doctrine\Common\Annotations\PsrCachedReader``.
This reader decorates the original reader and stores all annotations in a PSR-6
cache:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\PsrCachedReader;
$cache = ... // instantiate a PSR-6 Cache pool
$reader = new PsrCachedReader(
new AnnotationReader(),
$cache,
$debug = true
);
The ``debug`` flag is used here as well to invalidate the cache files
when the PHP class with annotations changed and should be used during
development.
.. warning ::
The ``AnnotationReader`` works and caches under the
assumption that all annotations of a doc-block are processed at
once. That means that annotation classes that do not exist and
aren't loaded and cannot be autoloaded (using the
AnnotationRegistry) would never be visible and not accessible if a
cache is used unless the cache is cleared and the annotations
requested again, this time with all annotations defined.
By default the annotation reader returns a list of annotations with
numeric indexes. If you want your annotations to be indexed by their
class name you can wrap the reader in an ``IndexedReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\IndexedReader;
$reader = new IndexedReader(new AnnotationReader());
.. warning::
You should never wrap the indexed reader inside a cached reader,
only the other way around. This way you can re-use the cache with
indexed or numeric keys, otherwise your code may experience failures
due to caching in a numerical or indexed format.
Registering Annotations
~~~~~~~~~~~~~~~~~~~~~~~
As explained in the introduction, Doctrine Annotations uses its own
autoloading mechanism to determine if a given annotation has a
corresponding PHP class that can be autoloaded. For annotation
autoloading you have to configure the
``Doctrine\Common\Annotations\AnnotationRegistry``. There are three
different mechanisms to configure annotation autoloading:
- Calling ``AnnotationRegistry#registerFile($file)`` to register a file
that contains one or more annotation classes.
- Calling ``AnnotationRegistry#registerNamespace($namespace, $dirs =
null)`` to register that the given namespace contains annotations and
that their base directory is located at the given $dirs or in the
include path if ``NULL`` is passed. The given directories should *NOT*
be the directory where classes of the namespace are in, but the base
directory of the root namespace. The AnnotationRegistry uses a
namespace to directory separator approach to resolve the correct path.
- Calling ``AnnotationRegistry#registerLoader($callable)`` to register
an autoloader callback. The callback accepts the class as first and
only parameter and has to return ``true`` if the corresponding file
was found and included.
.. note::
Loaders have to fail silently, if a class is not found even if it
matches for example the namespace prefix of that loader. Never is a
loader to throw a warning or exception if the loading failed
otherwise parsing doc block annotations will become a huge pain.
A sample loader callback could look like:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationRegistry;
use Symfony\Component\ClassLoader\UniversalClassLoader;
AnnotationRegistry::registerLoader(function($class) {
$file = str_replace("\\", DIRECTORY_SEPARATOR, $class) . ".php";
if (file_exists("/my/base/path/" . $file)) {
// file_exists() makes sure that the loader fails silently
require "/my/base/path/" . $file;
}
});
$loader = new UniversalClassLoader();
AnnotationRegistry::registerLoader(array($loader, "loadClass"));
Ignoring missing exceptions
~~~~~~~~~~~~~~~~~~~~~~~~~~~
By default an exception is thrown from the ``AnnotationReader`` if an
annotation was found that:
- is not part of the list of ignored "documentation annotations";
- was not imported through a use statement;
- is not a fully qualified class that exists.
You can disable this behavior for specific names if your docblocks do
not follow strict requirements:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
AnnotationReader::addGlobalIgnoredName('foo');
PHP Imports
~~~~~~~~~~~
By default the annotation reader parses the use-statement of a php file
to gain access to the import rules and register them for the annotation
processing. Only if you are using PHP Imports can you validate the
correct usage of annotations and throw exceptions if you misspelled an
annotation. This mechanism is enabled by default.
To ease the upgrade path, we still allow you to disable this mechanism.
Note however that we will remove this in future versions:
.. code-block:: php
$reader = new \Doctrine\Common\Annotations\AnnotationReader();
$reader->setEnabledPhpImports(false);

View File

@@ -0,0 +1,443 @@
Custom Annotation Classes
=========================
If you want to define your own annotations, you just have to group them
in a namespace and register this namespace in the ``AnnotationRegistry``.
Annotation classes have to contain a class-level docblock with the text
``@Annotation``:
.. code-block:: php
namespace MyCompany\Annotations;
/** @Annotation */
class Bar
{
// some code
}
Inject annotation values
------------------------
The annotation parser checks if the annotation constructor has arguments,
if so then it will pass the value array, otherwise it will try to inject
values into public properties directly:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
*
* Some Annotation using a constructor
*/
class Bar
{
private $foo;
public function __construct(array $values)
{
$this->foo = $values['foo'];
}
}
/**
* @Annotation
*
* Some Annotation without a constructor
*/
class Foo
{
public $bar;
}
Optional: Constructors with Named Parameters
--------------------------------------------
Starting with Annotations v1.11 a new annotation instantiation strategy
is available that aims at compatibility of Annotation classes with the PHP 8
attribute feature. You need to declare a constructor with regular parameter
names that match the named arguments in the annotation syntax.
To enable this feature, you can tag your annotation class with
``@NamedArgumentConstructor`` (available from v1.12) or implement the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation`` interface
(available from v1.11 and deprecated as of v1.12).
When using the ``@NamedArgumentConstructor`` tag, the first argument of the
constructor is considered as the default one.
Usage with the ``@NamedArgumentContrustor`` tag
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(string $foo)
{
$this->foo = $foo;
}
}
/** Usable with @Bar(foo="baz") */
/** Usable with @Bar("baz") */
In combination with PHP 8's constructor property promotion feature
you can simplify this to:
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @NamedArgumentConstructor
*/
class Bar implements NamedArgumentConstructorAnnotation
{
public function __construct(private string $foo) {}
}
Usage with the
``Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation``
interface (v1.11, deprecated as of v1.12):
.. code-block:: php
namespace MyCompany\Annotations;
use Doctrine\Common\Annotations\NamedArgumentConstructorAnnotation;
/** @Annotation */
class Bar implements NamedArgumentConstructorAnnotation
{
private $foo;
public function __construct(private string $foo) {}
}
/** Usable with @Bar(foo="baz") */
Annotation Target
-----------------
``@Target`` indicates the kinds of class elements to which an annotation
type is applicable. Then you could define one or more targets:
- ``CLASS`` Allowed in class docblocks
- ``PROPERTY`` Allowed in property docblocks
- ``METHOD`` Allowed in the method docblocks
- ``FUNCTION`` Allowed in function dockblocks
- ``ALL`` Allowed in class, property, method and function docblocks
- ``ANNOTATION`` Allowed inside other annotations
If the annotations is not allowed in the current context, an
``AnnotationException`` is thrown.
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
// some code
}
/**
* @Annotation
* @Target("CLASS")
*/
class Foo
{
// some code
}
Attribute types
---------------
The annotation parser checks the given parameters using the phpdoc
annotation ``@var``, The data type could be validated using the ``@var``
annotation on the annotation properties or using the ``@Attributes`` and
``@Attribute`` annotations.
If the data type does not match you get an ``AnnotationException``
.. code-block:: php
namespace MyCompany\Annotations;
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
*/
class Bar
{
/** @var mixed */
public $mixed;
/** @var boolean */
public $boolean;
/** @var bool */
public $bool;
/** @var float */
public $float;
/** @var string */
public $string;
/** @var integer */
public $integer;
/** @var array */
public $array;
/** @var SomeAnnotationClass */
public $annotation;
/** @var array<integer> */
public $arrayOfIntegers;
/** @var array<SomeAnnotationClass> */
public $arrayOfAnnotations;
}
/**
* @Annotation
* @Target({"METHOD","PROPERTY"})
* @Attributes({
* @Attribute("stringProperty", type = "string"),
* @Attribute("annotProperty", type = "SomeAnnotationClass"),
* })
*/
class Foo
{
public function __construct(array $values)
{
$this->stringProperty = $values['stringProperty'];
$this->annotProperty = $values['annotProperty'];
}
// some code
}
Annotation Required
-------------------
``@Required`` indicates that the field must be specified when the
annotation is used. If it is not used you get an ``AnnotationException``
stating that this value can not be null.
Declaring a required field:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Foo
{
/** @Required */
public $requiredField;
}
Usage:
.. code-block:: php
/** @Foo(requiredField="value") */
public $direction; // Valid
/** @Foo */
public $direction; // Required field missing, throws an AnnotationException
Enumerated values
-----------------
- An annotation property marked with ``@Enum`` is a field that accepts a
fixed set of scalar values.
- You should use ``@Enum`` fields any time you need to represent fixed
values.
- The annotation parser checks the given value and throws an
``AnnotationException`` if the value does not match.
Declaring an enumerated property:
.. code-block:: php
/**
* @Annotation
* @Target("ALL")
*/
class Direction
{
/**
* @Enum({"NORTH", "SOUTH", "EAST", "WEST"})
*/
public $value;
}
Annotation usage:
.. code-block:: php
/** @Direction("NORTH") */
public $direction; // Valid value
/** @Direction("NORTHEAST") */
public $direction; // Invalid value, throws an AnnotationException
Constants
---------
The use of constants and class constants is available on the annotations
parser.
The following usages are allowed:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
use MyCompany\Entity\SomeClass;
/**
* @Foo(PHP_EOL)
* @Bar(Bar::FOO)
* @Foo({SomeClass::FOO, SomeClass::BAR})
* @Bar({SomeClass::FOO_KEY = SomeClass::BAR_VALUE})
*/
class User
{
}
Be careful with constants and the cache !
.. note::
The cached reader will not re-evaluate each time an annotation is
loaded from cache. When a constant is changed the cache must be
cleaned.
Usage
-----
Using the library API is simple. Using the annotations described in the
previous section, you can now annotate other classes with your
annotations:
.. code-block:: php
namespace MyCompany\Entity;
use MyCompany\Annotations\Foo;
use MyCompany\Annotations\Bar;
/**
* @Foo(bar="foo")
* @Bar(foo="bar")
*/
class User
{
}
Now we can write a script to get the annotations above:
.. code-block:: php
$reflClass = new ReflectionClass('MyCompany\Entity\User');
$classAnnotations = $reader->getClassAnnotations($reflClass);
foreach ($classAnnotations AS $annot) {
if ($annot instanceof \MyCompany\Annotations\Foo) {
echo $annot->bar; // prints "foo";
} else if ($annot instanceof \MyCompany\Annotations\Bar) {
echo $annot->foo; // prints "bar";
}
}
You have a complete API for retrieving annotation class instances from a
class, property or method docblock:
Reader API
~~~~~~~~~~
Access all annotations of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotations(\ReflectionClass $class);
Access one annotation of a class
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getClassAnnotation(\ReflectionClass $class, $annotationName);
Access all annotations of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotations(\ReflectionMethod $method);
Access one annotation of a method
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getMethodAnnotation(\ReflectionMethod $method, $annotationName);
Access all annotations of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotations(\ReflectionProperty $property);
Access one annotation of a property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getPropertyAnnotation(\ReflectionProperty $property, $annotationName);
Access all annotations of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotations(\ReflectionFunction $property);
Access one annotation of a function
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: php
public function getFunctionAnnotation(\ReflectionFunction $property, $annotationName);

View File

@@ -0,0 +1,101 @@
Introduction
============
Doctrine Annotations allows to implement custom annotation
functionality for PHP classes and functions.
.. code-block:: php
class Foo
{
/**
* @MyAnnotation(myProperty="value")
*/
private $bar;
}
Annotations aren't implemented in PHP itself which is why this component
offers a way to use the PHP doc-blocks as a place for the well known
annotation syntax using the ``@`` char.
Annotations in Doctrine are used for the ORM configuration to build the
class mapping, but it can be used in other projects for other purposes
too.
Installation
============
You can install the Annotation component with composer:
.. code-block::
  $ composer require doctrine/annotations
Create an annotation class
==========================
An annotation class is a representation of the later used annotation
configuration in classes. The annotation class of the previous example
looks like this:
.. code-block:: php
/**
* @Annotation
*/
final class MyAnnotation
{
public $myProperty;
}
The annotation class is declared as an annotation by ``@Annotation``.
:ref:`Read more about custom annotations. <custom>`
Reading annotations
===================
The access to the annotations happens by reflection of the class or function
containing them. There are multiple reader-classes implementing the
``Doctrine\Common\Annotations\Reader`` interface, that can access the
annotations of a class. A common one is
``Doctrine\Common\Annotations\AnnotationReader``:
.. code-block:: php
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
// Deprecated and will be removed in 2.0 but currently needed
AnnotationRegistry::registerLoader('class_exists');
$reflectionClass = new ReflectionClass(Foo::class);
$property = $reflectionClass->getProperty('bar');
$reader = new AnnotationReader();
$myAnnotation = $reader->getPropertyAnnotation(
$property,
MyAnnotation::class
);
echo $myAnnotation->myProperty; // result: "value"
Note that ``AnnotationRegistry::registerLoader('class_exists')`` only works
if you already have an autoloader configured (i.e. composer autoloader).
Otherwise, :ref:`please take a look to the other annotation autoload mechanisms <annotations>`.
A reader has multiple methods to access the annotations of a class or
function.
:ref:`Read more about handling annotations. <annotations>`
IDE Support
-----------
Some IDEs already provide support for annotations:
- Eclipse via the `Symfony2 Plugin <https://github.com/pulse00/Symfony-2-Eclipse-Plugin>`_
- PhpStorm via the `PHP Annotations Plugin <https://plugins.jetbrains.com/plugin/7320-php-annotations>`_ or the `Symfony Plugin <https://plugins.jetbrains.com/plugin/7219-symfony-support>`_
.. _Read more about handling annotations.: annotations
.. _Read more about custom annotations.: custom

View File

@@ -0,0 +1,6 @@
.. toctree::
:depth: 3
index
annotations
custom

View File

@@ -0,0 +1,59 @@
<?php
namespace Doctrine\Common\Annotations;
use BadMethodCallException;
use function sprintf;
/**
* Annotations class.
*/
class Annotation
{
/**
* Value property. Common among all derived classes.
*
* @var mixed
*/
public $value;
/**
* @param array<string, mixed> $data Key-value for properties to be defined in this class.
*/
final public function __construct(array $data)
{
foreach ($data as $key => $value) {
$this->$key = $value;
}
}
/**
* Error handler for unknown property accessor in Annotation class.
*
* @param string $name Unknown property name.
*
* @throws BadMethodCallException
*/
public function __get($name)
{
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
/**
* Error handler for unknown property mutator in Annotation class.
*
* @param string $name Unknown property name.
* @param mixed $value Property value.
*
* @throws BadMethodCallException
*/
public function __set($name, $value)
{
throw new BadMethodCallException(
sprintf("Unknown property '%s' on annotation '%s'.", $name, static::class)
);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the attribute type during the parsing process.
*
* @Annotation
*/
final class Attribute
{
/** @var string */
public $name;
/** @var string */
public $type;
/** @var bool */
public $required = false;
}

View File

@@ -0,0 +1,15 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check the types of all declared attributes during the parsing process.
*
* @Annotation
*/
final class Attributes
{
/** @var array<Attribute> */
public $value;
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function get_class;
use function gettype;
use function in_array;
use function is_object;
use function is_scalar;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the available values during the parsing process.
*
* @Annotation
* @Attributes({
* @Attribute("value", required = true, type = "array"),
* @Attribute("literal", required = false, type = "array")
* })
*/
final class Enum
{
/** @phpstan-var list<scalar> */
public $value;
/**
* Literal target declaration.
*
* @var mixed[]
*/
public $literal;
/**
* @throws InvalidArgumentException
*
* @phpstan-param array{literal?: mixed[], value: list<scalar>} $values
*/
public function __construct(array $values)
{
if (! isset($values['literal'])) {
$values['literal'] = [];
}
foreach ($values['value'] as $var) {
if (! is_scalar($var)) {
throw new InvalidArgumentException(sprintf(
'@Enum supports only scalar values "%s" given.',
is_object($var) ? get_class($var) : gettype($var)
));
}
}
foreach ($values['literal'] as $key => $var) {
if (! in_array($key, $values['value'])) {
throw new InvalidArgumentException(sprintf(
'Undefined enumerator value "%s" for literal "%s".',
$key,
$var
));
}
}
$this->value = $values['value'];
$this->literal = $values['literal'];
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use RuntimeException;
use function is_array;
use function is_string;
use function json_encode;
use function sprintf;
/**
* Annotation that can be used to signal to the parser to ignore specific
* annotations during the parsing process.
*
* @Annotation
*/
final class IgnoreAnnotation
{
/** @phpstan-var list<string> */
public $names;
/**
* @throws RuntimeException
*
* @phpstan-param array{value: string|list<string>} $values
*/
public function __construct(array $values)
{
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (! is_array($values['value'])) {
throw new RuntimeException(sprintf(
'@IgnoreAnnotation expects either a string name, or an array of strings, but got %s.',
json_encode($values['value'])
));
}
$this->names = $values['value'];
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that indicates that the annotated class should be constructed with a named argument call.
*
* @Annotation
* @Target("CLASS")
*/
final class NamedArgumentConstructor
{
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
/**
* Annotation that can be used to signal to the parser
* to check if that attribute is required during the parsing process.
*
* @Annotation
*/
final class Required
{
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Doctrine\Common\Annotations\Annotation;
use InvalidArgumentException;
use function array_keys;
use function get_class;
use function gettype;
use function implode;
use function is_array;
use function is_object;
use function is_string;
use function sprintf;
/**
* Annotation that can be used to signal to the parser
* to check the annotation target during the parsing process.
*
* @Annotation
*/
final class Target
{
public const TARGET_CLASS = 1;
public const TARGET_METHOD = 2;
public const TARGET_PROPERTY = 4;
public const TARGET_ANNOTATION = 8;
public const TARGET_FUNCTION = 16;
public const TARGET_ALL = 31;
/** @var array<string, int> */
private static $map = [
'ALL' => self::TARGET_ALL,
'CLASS' => self::TARGET_CLASS,
'METHOD' => self::TARGET_METHOD,
'PROPERTY' => self::TARGET_PROPERTY,
'FUNCTION' => self::TARGET_FUNCTION,
'ANNOTATION' => self::TARGET_ANNOTATION,
];
/** @phpstan-var list<string> */
public $value;
/**
* Targets as bitmask.
*
* @var int
*/
public $targets;
/**
* Literal target declaration.
*
* @var string
*/
public $literal;
/**
* @throws InvalidArgumentException
*
* @phpstan-param array{value?: string|list<string>} $values
*/
public function __construct(array $values)
{
if (! isset($values['value'])) {
$values['value'] = null;
}
if (is_string($values['value'])) {
$values['value'] = [$values['value']];
}
if (! is_array($values['value'])) {
throw new InvalidArgumentException(
sprintf(
'@Target expects either a string value, or an array of strings, "%s" given.',
is_object($values['value']) ? get_class($values['value']) : gettype($values['value'])
)
);
}
$bitmask = 0;
foreach ($values['value'] as $literal) {
if (! isset(self::$map[$literal])) {
throw new InvalidArgumentException(
sprintf(
'Invalid Target "%s". Available targets: [%s]',
$literal,
implode(', ', array_keys(self::$map))
)
);
}
$bitmask |= self::$map[$literal];
}
$this->targets = $bitmask;
$this->value = $values['value'];
$this->literal = implode(', ', $this->value);
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace Doctrine\Common\Annotations;
use Exception;
use function get_class;
use function gettype;
use function implode;
use function is_object;
use function sprintf;
/**
* Description of AnnotationException
*/
class AnnotationException extends Exception
{
/**
* Creates a new AnnotationException describing a Syntax error.
*
* @param string $message Exception message
*
* @return AnnotationException
*/
public static function syntaxError($message)
{
return new self('[Syntax Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a Semantical error.
*
* @param string $message Exception message
*
* @return AnnotationException
*/
public static function semanticalError($message)
{
return new self('[Semantical Error] ' . $message);
}
/**
* Creates a new AnnotationException describing an error which occurred during
* the creation of the annotation.
*
* @param string $message
*
* @return AnnotationException
*/
public static function creationError($message)
{
return new self('[Creation Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a type error.
*
* @param string $message
*
* @return AnnotationException
*/
public static function typeError($message)
{
return new self('[Type Error] ' . $message);
}
/**
* Creates a new AnnotationException describing a constant semantical error.
*
* @param string $identifier
* @param string $context
*
* @return AnnotationException
*/
public static function semanticalErrorConstants($identifier, $context = null)
{
return self::semanticalError(sprintf(
"Couldn't find constant %s%s.",
$identifier,
$context ? ', ' . $context : ''
));
}
/**
* Creates a new AnnotationException describing an type error of an attribute.
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
* @param string $expected
* @param mixed $actual
*
* @return AnnotationException
*/
public static function attributeTypeError($attributeName, $annotationName, $context, $expected, $actual)
{
return self::typeError(sprintf(
'Attribute "%s" of @%s declared on %s expects %s, but got %s.',
$attributeName,
$annotationName,
$context,
$expected,
is_object($actual) ? 'an instance of ' . get_class($actual) : gettype($actual)
));
}
/**
* Creates a new AnnotationException describing an required error of an attribute.
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
* @param string $expected
*
* @return AnnotationException
*/
public static function requiredError($attributeName, $annotationName, $context, $expected)
{
return self::typeError(sprintf(
'Attribute "%s" of @%s declared on %s expects %s. This value should not be null.',
$attributeName,
$annotationName,
$context,
$expected
));
}
/**
* Creates a new AnnotationException describing a invalid enummerator.
*
* @param string $attributeName
* @param string $annotationName
* @param string $context
* @param mixed $given
*
* @return AnnotationException
*
* @phpstan-param list<string> $available
*/
public static function enumeratorError($attributeName, $annotationName, $context, $available, $given)
{
return new self(sprintf(
'[Enum Error] Attribute "%s" of @%s declared on %s accepts only [%s], but got %s.',
$attributeName,
$annotationName,
$context,
implode(', ', $available),
is_object($given) ? get_class($given) : $given
));
}
/**
* @return AnnotationException
*/
public static function optimizerPlusSaveComments()
{
return new self(
'You have to enable opcache.save_comments=1 or zend_optimizerplus.save_comments=1.'
);
}
/**
* @return AnnotationException
*/
public static function optimizerPlusLoadComments()
{
return new self(
'You have to enable opcache.load_comments=1 or zend_optimizerplus.load_comments=1.'
);
}
}

View File

@@ -0,0 +1,389 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Annotations\Annotation\IgnoreAnnotation;
use Doctrine\Common\Annotations\Annotation\Target;
use ReflectionClass;
use ReflectionFunction;
use ReflectionMethod;
use ReflectionProperty;
use function array_merge;
use function class_exists;
use function extension_loaded;
use function ini_get;
/**
* A reader for docblock annotations.
*/
class AnnotationReader implements Reader
{
/**
* Global map for imports.
*
* @var array<string, class-string>
*/
private static $globalImports = [
'ignoreannotation' => Annotation\IgnoreAnnotation::class,
];
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array<string, true>
*/
private static $globalIgnoredNames = ImplicitlyIgnoredAnnotationNames::LIST;
/**
* A list with annotations that are not causing exceptions when not resolved to an annotation class.
*
* The names are case sensitive.
*
* @var array<string, true>
*/
private static $globalIgnoredNamespaces = [];
/**
* Add a new annotation to the globally ignored annotation names with regard to exception handling.
*
* @param string $name
*/
public static function addGlobalIgnoredName($name)
{
self::$globalIgnoredNames[$name] = true;
}
/**
* Add a new annotation to the globally ignored annotation namespaces with regard to exception handling.
*
* @param string $namespace
*/
public static function addGlobalIgnoredNamespace($namespace)
{
self::$globalIgnoredNamespaces[$namespace] = true;
}
/**
* Annotations parser.
*
* @var DocParser
*/
private $parser;
/**
* Annotations parser used to collect parsing metadata.
*
* @var DocParser
*/
private $preParser;
/**
* PHP parser used to collect imports.
*
* @var PhpParser
*/
private $phpParser;
/**
* In-memory cache mechanism to store imported annotations per class.
*
* @psalm-var array<'class'|'function', array<string, array<string, class-string>>>
*/
private $imports = [];
/**
* In-memory cache mechanism to store ignored annotations per class.
*
* @psalm-var array<'class'|'function', array<string, array<string, true>>>
*/
private $ignoredAnnotationNames = [];
/**
* Initializes a new AnnotationReader.
*
* @throws AnnotationException
*/
public function __construct(?DocParser $parser = null)
{
if (
extension_loaded('Zend Optimizer+') && (ini_get('zend_optimizerplus.save_comments') === '0' ||
ini_get('opcache.save_comments') === '0')
) {
throw AnnotationException::optimizerPlusSaveComments();
}
if (extension_loaded('Zend OPcache') && ini_get('opcache.save_comments') === 0) {
throw AnnotationException::optimizerPlusSaveComments();
}
// Make sure that the IgnoreAnnotation annotation is loaded
class_exists(IgnoreAnnotation::class);
$this->parser = $parser ?: new DocParser();
$this->preParser = new DocParser();
$this->preParser->setImports(self::$globalImports);
$this->preParser->setIgnoreNotImportedAnnotations(true);
$this->preParser->setIgnoredAnnotationNames(self::$globalIgnoredNames);
$this->phpParser = new PhpParser();
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$this->parser->setTarget(Target::TARGET_CLASS);
$this->parser->setImports($this->getImports($class));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotations = $this->getClassAnnotations($class);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$context = 'property ' . $class->getName() . '::$' . $property->getName();
$this->parser->setTarget(Target::TARGET_PROPERTY);
$this->parser->setImports($this->getPropertyImports($property));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($property->getDocComment(), $context);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$annotations = $this->getPropertyAnnotations($property);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$context = 'method ' . $class->getName() . '::' . $method->getName() . '()';
$this->parser->setTarget(Target::TARGET_METHOD);
$this->parser->setImports($this->getMethodImports($method));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($class));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($method->getDocComment(), $context);
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$annotations = $this->getMethodAnnotations($method);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Gets the annotations applied to a function.
*
* @phpstan-return list<object> An array of Annotations.
*/
public function getFunctionAnnotations(ReflectionFunction $function): array
{
$context = 'function ' . $function->getName();
$this->parser->setTarget(Target::TARGET_FUNCTION);
$this->parser->setImports($this->getImports($function));
$this->parser->setIgnoredAnnotationNames($this->getIgnoredAnnotationNames($function));
$this->parser->setIgnoredAnnotationNamespaces(self::$globalIgnoredNamespaces);
return $this->parser->parse($function->getDocComment(), $context);
}
/**
* Gets a function annotation.
*
* @return object|null The Annotation or NULL, if the requested annotation does not exist.
*/
public function getFunctionAnnotation(ReflectionFunction $function, string $annotationName)
{
$annotations = $this->getFunctionAnnotations($function);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Returns the ignored annotations for the given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, true>
*/
private function getIgnoredAnnotationNames($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->ignoredAnnotationNames[$type][$name])) {
return $this->ignoredAnnotationNames[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->ignoredAnnotationNames[$type][$name];
}
/**
* Retrieves imports for a class or a function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @return array<string, class-string>
*/
private function getImports($reflection): array
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
if (isset($this->imports[$type][$name])) {
return $this->imports[$type][$name];
}
$this->collectParsingMetadata($reflection);
return $this->imports[$type][$name];
}
/**
* Retrieves imports for methods.
*
* @return array<string, class-string>
*/
private function getMethodImports(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if (
! $trait->hasMethod($method->getName())
|| $trait->getFileName() !== $method->getFileName()
) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Retrieves imports for properties.
*
* @return array<string, class-string>
*/
private function getPropertyImports(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$classImports = $this->getImports($class);
$traitImports = [];
foreach ($class->getTraits() as $trait) {
if (! $trait->hasProperty($property->getName())) {
continue;
}
$traitImports = array_merge($traitImports, $this->phpParser->parseUseStatements($trait));
}
return array_merge($classImports, $traitImports);
}
/**
* Collects parsing metadata for a given class or function.
*
* @param ReflectionClass|ReflectionFunction $reflection
*/
private function collectParsingMetadata($reflection): void
{
$type = $reflection instanceof ReflectionClass ? 'class' : 'function';
$name = $reflection->getName();
$ignoredAnnotationNames = self::$globalIgnoredNames;
$annotations = $this->preParser->parse($reflection->getDocComment(), $type . ' ' . $name);
foreach ($annotations as $annotation) {
if (! ($annotation instanceof IgnoreAnnotation)) {
continue;
}
foreach ($annotation->names as $annot) {
$ignoredAnnotationNames[$annot] = true;
}
}
$this->imports[$type][$name] = array_merge(
self::$globalImports,
$this->phpParser->parseUseStatements($reflection),
[
'__NAMESPACE__' => $reflection->getNamespaceName(),
'self' => $name,
]
);
$this->ignoredAnnotationNames[$type][$name] = $ignoredAnnotationNames;
}
}

View File

@@ -0,0 +1,190 @@
<?php
namespace Doctrine\Common\Annotations;
use function array_key_exists;
use function array_merge;
use function class_exists;
use function in_array;
use function is_file;
use function str_replace;
use function stream_resolve_include_path;
use function strpos;
use const DIRECTORY_SEPARATOR;
final class AnnotationRegistry
{
/**
* A map of namespaces to use for autoloading purposes based on a PSR-0 convention.
*
* Contains the namespace as key and an array of directories as value. If the value is NULL
* the include path is used for checking for the corresponding file.
*
* This autoloading mechanism does not utilize the PHP autoloading but implements autoloading on its own.
*
* @var string[][]|string[]|null[]
*/
private static $autoloadNamespaces = [];
/**
* A map of autoloader callables.
*
* @var callable[]
*/
private static $loaders = [];
/**
* An array of classes which cannot be found
*
* @var null[] indexed by class name
*/
private static $failedToAutoload = [];
/**
* Whenever registerFile() was used. Disables use of standard autoloader.
*
* @var bool
*/
private static $registerFileUsed = false;
public static function reset(): void
{
self::$autoloadNamespaces = [];
self::$loaders = [];
self::$failedToAutoload = [];
self::$registerFileUsed = false;
}
/**
* Registers file.
*
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
public static function registerFile(string $file): void
{
self::$registerFileUsed = true;
require_once $file;
}
/**
* Adds a namespace with one or many directories to look for files or null for the include path.
*
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
*
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*
* @phpstan-param string|list<string>|null $dirs
*/
public static function registerAutoloadNamespace(string $namespace, $dirs = null): void
{
self::$autoloadNamespaces[$namespace] = $dirs;
}
/**
* Registers multiple namespaces.
*
* Loading of this namespaces will be done with a PSR-0 namespace loading algorithm.
*
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*
* @param string[][]|string[]|null[] $namespaces indexed by namespace name
*/
public static function registerAutoloadNamespaces(array $namespaces): void
{
self::$autoloadNamespaces = array_merge(self::$autoloadNamespaces, $namespaces);
}
/**
* Registers an autoloading callable for annotations, much like spl_autoload_register().
*
* NOTE: These class loaders HAVE to be silent when a class was not found!
* IMPORTANT: Loaders have to return true if they loaded a class that could contain the searched annotation class.
*
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
public static function registerLoader(callable $callable): void
{
// Reset our static cache now that we have a new loader to work with
self::$failedToAutoload = [];
self::$loaders[] = $callable;
}
/**
* Registers an autoloading callable for annotations, if it is not already registered
*
* @deprecated This method is deprecated and will be removed in
* doctrine/annotations 2.0. Annotations will be autoloaded in 2.0.
*/
public static function registerUniqueLoader(callable $callable): void
{
if (in_array($callable, self::$loaders, true)) {
return;
}
self::registerLoader($callable);
}
/**
* Autoloads an annotation class silently.
*/
public static function loadAnnotationClass(string $class): bool
{
if (class_exists($class, false)) {
return true;
}
if (array_key_exists($class, self::$failedToAutoload)) {
return false;
}
foreach (self::$autoloadNamespaces as $namespace => $dirs) {
if (strpos($class, $namespace) !== 0) {
continue;
}
$file = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
if ($dirs === null) {
$path = stream_resolve_include_path($file);
if ($path) {
require $path;
return true;
}
} else {
foreach ((array) $dirs as $dir) {
if (is_file($dir . DIRECTORY_SEPARATOR . $file)) {
require $dir . DIRECTORY_SEPARATOR . $file;
return true;
}
}
}
}
foreach (self::$loaders as $loader) {
if ($loader($class) === true) {
return true;
}
}
if (
self::$loaders === [] &&
self::$autoloadNamespaces === [] &&
self::$registerFileUsed === false &&
class_exists($class)
) {
return true;
}
self::$failedToAutoload[$class] = null;
return false;
}
}

View File

@@ -0,0 +1,268 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Cache\Cache;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function array_map;
use function array_merge;
use function assert;
use function filemtime;
use function max;
use function time;
/**
* A cache aware annotation reader.
*
* @deprecated the CachedReader is deprecated and will be removed
* in version 2.0.0 of doctrine/annotations. Please use the
* {@see \Doctrine\Common\Annotations\PsrCachedReader} instead.
*/
final class CachedReader implements Reader
{
/** @var Reader */
private $delegate;
/** @var Cache */
private $cache;
/** @var bool */
private $debug;
/** @var array<string, array<object>> */
private $loadedAnnotations = [];
/** @var int[] */
private $loadedFilemtimes = [];
/**
* @param bool $debug
*/
public function __construct(Reader $reader, Cache $cache, $debug = false)
{
$this->delegate = $reader;
$this->cache = $cache;
$this->debug = (bool) $debug;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$cacheKey = $class->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getClassAnnotations($class);
$this->saveToCache($cacheKey, $annots);
}
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
foreach ($this->getClassAnnotations($class) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$cacheKey = $class->getName() . '$' . $property->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getPropertyAnnotations($property);
$this->saveToCache($cacheKey, $annots);
}
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$cacheKey = $class->getName() . '#' . $method->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class);
if ($annots === false) {
$annots = $this->delegate->getMethodAnnotations($method);
$this->saveToCache($cacheKey, $annots);
}
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* Clears loaded annotations.
*
* @return void
*/
public function clearLoadedAnnotations()
{
$this->loadedAnnotations = [];
$this->loadedFilemtimes = [];
}
/**
* Fetches a value from the cache.
*
* @param string $cacheKey The cache key.
*
* @return mixed The cached value or false when the value is not in cache.
*/
private function fetchFromCache($cacheKey, ReflectionClass $class)
{
$data = $this->cache->fetch($cacheKey);
if ($data !== false) {
if (! $this->debug || $this->isCacheFresh($cacheKey, $class)) {
return $data;
}
}
return false;
}
/**
* Saves a value to the cache.
*
* @param string $cacheKey The cache key.
* @param mixed $value The value.
*
* @return void
*/
private function saveToCache($cacheKey, $value)
{
$this->cache->save($cacheKey, $value);
if (! $this->debug) {
return;
}
$this->cache->save('[C]' . $cacheKey, time());
}
/**
* Checks if the cache is fresh.
*
* @param string $cacheKey
*
* @return bool
*/
private function isCacheFresh($cacheKey, ReflectionClass $class)
{
$lastModification = $this->getLastModification($class);
if ($lastModification === 0) {
return true;
}
return $this->cache->fetch('[C]' . $cacheKey) >= $lastModification;
}
/**
* Returns the time the class was last modified, testing traits and parents
*/
private function getLastModification(ReflectionClass $class): int
{
$filename = $class->getFileName();
if (isset($this->loadedFilemtimes[$filename])) {
return $this->loadedFilemtimes[$filename];
}
$parent = $class->getParentClass();
$lastModification = max(array_merge(
[$filename ? filemtime($filename) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $class->getTraits()),
array_map(function (ReflectionClass $class): int {
return $this->getLastModification($class);
}, $class->getInterfaces()),
$parent ? [$this->getLastModification($parent)] : []
));
assert($lastModification !== false);
return $this->loadedFilemtimes[$filename] = $lastModification;
}
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
{
$fileName = $reflectionTrait->getFileName();
if (isset($this->loadedFilemtimes[$fileName])) {
return $this->loadedFilemtimes[$fileName];
}
$lastModificationTime = max(array_merge(
[$fileName ? filemtime($fileName) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
));
assert($lastModificationTime !== false);
return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace Doctrine\Common\Annotations;
use Doctrine\Common\Lexer\AbstractLexer;
use function ctype_alpha;
use function is_numeric;
use function str_replace;
use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function substr;
/**
* Simple lexer for docblock annotations.
*/
final class DocLexer extends AbstractLexer
{
public const T_NONE = 1;
public const T_INTEGER = 2;
public const T_STRING = 3;
public const T_FLOAT = 4;
// All tokens that are also identifiers should be >= 100
public const T_IDENTIFIER = 100;
public const T_AT = 101;
public const T_CLOSE_CURLY_BRACES = 102;
public const T_CLOSE_PARENTHESIS = 103;
public const T_COMMA = 104;
public const T_EQUALS = 105;
public const T_FALSE = 106;
public const T_NAMESPACE_SEPARATOR = 107;
public const T_OPEN_CURLY_BRACES = 108;
public const T_OPEN_PARENTHESIS = 109;
public const T_TRUE = 110;
public const T_NULL = 111;
public const T_COLON = 112;
public const T_MINUS = 113;
/** @var array<string, int> */
protected $noCase = [
'@' => self::T_AT,
',' => self::T_COMMA,
'(' => self::T_OPEN_PARENTHESIS,
')' => self::T_CLOSE_PARENTHESIS,
'{' => self::T_OPEN_CURLY_BRACES,
'}' => self::T_CLOSE_CURLY_BRACES,
'=' => self::T_EQUALS,
':' => self::T_COLON,
'-' => self::T_MINUS,
'\\' => self::T_NAMESPACE_SEPARATOR,
];
/** @var array<string, int> */
protected $withCase = [
'true' => self::T_TRUE,
'false' => self::T_FALSE,
'null' => self::T_NULL,
];
/**
* Whether the next token starts immediately, or if there were
* non-captured symbols before that
*/
public function nextTokenIsAdjacent(): bool
{
return $this->token === null
|| ($this->lookahead !== null
&& ($this->lookahead['position'] - $this->token['position']) === strlen($this->token['value']));
}
/**
* {@inheritdoc}
*/
protected function getCatchablePatterns()
{
return [
'[a-z_\\\][a-z0-9_\:\\\]*[a-z_][a-z0-9_]*',
'(?:[+-]?[0-9]+(?:[\.][0-9]+)*)(?:[eE][+-]?[0-9]+)?',
'"(?:""|[^"])*+"',
];
}
/**
* {@inheritdoc}
*/
protected function getNonCatchablePatterns()
{
return ['\s+', '\*+', '(.)'];
}
/**
* {@inheritdoc}
*/
protected function getType(&$value)
{
$type = self::T_NONE;
if ($value[0] === '"') {
$value = str_replace('""', '"', substr($value, 1, strlen($value) - 2));
return self::T_STRING;
}
if (isset($this->noCase[$value])) {
return $this->noCase[$value];
}
if ($value[0] === '_' || $value[0] === '\\' || ctype_alpha($value[0])) {
return self::T_IDENTIFIER;
}
$lowerValue = strtolower($value);
if (isset($this->withCase[$lowerValue])) {
return $this->withCase[$lowerValue];
}
// Checking numeric value
if (is_numeric($value)) {
return strpos($value, '.') !== false || stripos($value, 'e') !== false
? self::T_FLOAT : self::T_INTEGER;
}
return $type;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,315 @@
<?php
namespace Doctrine\Common\Annotations;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use RuntimeException;
use function chmod;
use function file_put_contents;
use function filemtime;
use function gettype;
use function is_dir;
use function is_file;
use function is_int;
use function is_writable;
use function mkdir;
use function rename;
use function rtrim;
use function serialize;
use function sha1;
use function sprintf;
use function strtr;
use function tempnam;
use function uniqid;
use function unlink;
use function var_export;
/**
* File cache reader for annotations.
*
* @deprecated the FileCacheReader is deprecated and will be removed
* in version 2.0.0 of doctrine/annotations. Please use the
* {@see \Doctrine\Common\Annotations\PsrCachedReader} instead.
*/
class FileCacheReader implements Reader
{
/** @var Reader */
private $reader;
/** @var string */
private $dir;
/** @var bool */
private $debug;
/** @phpstan-var array<string, list<object>> */
private $loadedAnnotations = [];
/** @var array<string, string> */
private $classNameHashes = [];
/** @var int */
private $umask;
/**
* @param string $cacheDir
* @param bool $debug
* @param int $umask
*
* @throws InvalidArgumentException
*/
public function __construct(Reader $reader, $cacheDir, $debug = false, $umask = 0002)
{
if (! is_int($umask)) {
throw new InvalidArgumentException(sprintf(
'The parameter umask must be an integer, was: %s',
gettype($umask)
));
}
$this->reader = $reader;
$this->umask = $umask;
if (! is_dir($cacheDir) && ! @mkdir($cacheDir, 0777 & (~$this->umask), true)) {
throw new InvalidArgumentException(sprintf(
'The directory "%s" does not exist and could not be created.',
$cacheDir
));
}
$this->dir = rtrim($cacheDir, '\\/');
$this->debug = $debug;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name];
if (isset($this->loadedAnnotations[$key])) {
return $this->loadedAnnotations[$key];
}
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
if (! is_file($path)) {
$annot = $this->reader->getClassAnnotations($class);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
$filename = $class->getFilename();
if (
$this->debug
&& $filename !== false
&& filemtime($path) < filemtime($filename)
) {
@unlink($path);
$annot = $this->reader->getClassAnnotations($class);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
return $this->loadedAnnotations[$key] = include $path;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name] . '$' . $property->getName();
if (isset($this->loadedAnnotations[$key])) {
return $this->loadedAnnotations[$key];
}
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
if (! is_file($path)) {
$annot = $this->reader->getPropertyAnnotations($property);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
$filename = $class->getFilename();
if (
$this->debug
&& $filename !== false
&& filemtime($path) < filemtime($filename)
) {
@unlink($path);
$annot = $this->reader->getPropertyAnnotations($property);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
return $this->loadedAnnotations[$key] = include $path;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
if (! isset($this->classNameHashes[$class->name])) {
$this->classNameHashes[$class->name] = sha1($class->name);
}
$key = $this->classNameHashes[$class->name] . '#' . $method->getName();
if (isset($this->loadedAnnotations[$key])) {
return $this->loadedAnnotations[$key];
}
$path = $this->dir . '/' . strtr($key, '\\', '-') . '.cache.php';
if (! is_file($path)) {
$annot = $this->reader->getMethodAnnotations($method);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
$filename = $class->getFilename();
if (
$this->debug
&& $filename !== false
&& filemtime($path) < filemtime($filename)
) {
@unlink($path);
$annot = $this->reader->getMethodAnnotations($method);
$this->saveCacheFile($path, $annot);
return $this->loadedAnnotations[$key] = $annot;
}
return $this->loadedAnnotations[$key] = include $path;
}
/**
* Saves the cache file.
*
* @param string $path
* @param mixed $data
*
* @return void
*/
private function saveCacheFile($path, $data)
{
if (! is_writable($this->dir)) {
throw new InvalidArgumentException(sprintf(
<<<'EXCEPTION'
The directory "%s" is not writable. Both the webserver and the console user need access.
You can manage access rights for multiple users with "chmod +a".
If your system does not support this, check out the acl package.,
EXCEPTION
,
$this->dir
));
}
$tempfile = tempnam($this->dir, uniqid('', true));
if ($tempfile === false) {
throw new RuntimeException(sprintf('Unable to create tempfile in directory: %s', $this->dir));
}
@chmod($tempfile, 0666 & (~$this->umask));
$written = file_put_contents(
$tempfile,
'<?php return unserialize(' . var_export(serialize($data), true) . ');'
);
if ($written === false) {
throw new RuntimeException(sprintf('Unable to write cached file to: %s', $tempfile));
}
@chmod($tempfile, 0666 & (~$this->umask));
if (rename($tempfile, $path) === false) {
@unlink($tempfile);
throw new RuntimeException(sprintf('Unable to rename %s to %s', $tempfile, $path));
}
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
$annotations = $this->getClassAnnotations($class);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
$annotations = $this->getMethodAnnotations($method);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
$annotations = $this->getPropertyAnnotations($property);
foreach ($annotations as $annotation) {
if ($annotation instanceof $annotationName) {
return $annotation;
}
}
return null;
}
/**
* Clears loaded annotations.
*
* @return void
*/
public function clearLoadedAnnotations()
{
$this->loadedAnnotations = [];
}
}

View File

@@ -0,0 +1,177 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Annotations;
/**
* A list of annotations that are implicitly ignored during the parsing process.
*
* All names are case sensitive.
*/
final class ImplicitlyIgnoredAnnotationNames
{
private const Reserved = [
'Annotation' => true,
'Attribute' => true,
'Attributes' => true,
/* Can we enable this? 'Enum' => true, */
'Required' => true,
'Target' => true,
'NamedArgumentConstructor' => true,
];
private const WidelyUsedNonStandard = [
'fix' => true,
'fixme' => true,
'override' => true,
];
private const PhpDocumentor1 = [
'abstract' => true,
'access' => true,
'code' => true,
'deprec' => true,
'endcode' => true,
'exception' => true,
'final' => true,
'ingroup' => true,
'inheritdoc' => true,
'inheritDoc' => true,
'magic' => true,
'name' => true,
'private' => true,
'static' => true,
'staticvar' => true,
'staticVar' => true,
'toc' => true,
'tutorial' => true,
'throw' => true,
];
private const PhpDocumentor2 = [
'api' => true,
'author' => true,
'category' => true,
'copyright' => true,
'deprecated' => true,
'example' => true,
'filesource' => true,
'global' => true,
'ignore' => true,
/* Can we enable this? 'index' => true, */
'internal' => true,
'license' => true,
'link' => true,
'method' => true,
'package' => true,
'param' => true,
'property' => true,
'property-read' => true,
'property-write' => true,
'return' => true,
'see' => true,
'since' => true,
'source' => true,
'subpackage' => true,
'throws' => true,
'todo' => true,
'TODO' => true,
'usedby' => true,
'uses' => true,
'var' => true,
'version' => true,
];
private const PHPUnit = [
'author' => true,
'after' => true,
'afterClass' => true,
'backupGlobals' => true,
'backupStaticAttributes' => true,
'before' => true,
'beforeClass' => true,
'codeCoverageIgnore' => true,
'codeCoverageIgnoreStart' => true,
'codeCoverageIgnoreEnd' => true,
'covers' => true,
'coversDefaultClass' => true,
'coversNothing' => true,
'dataProvider' => true,
'depends' => true,
'doesNotPerformAssertions' => true,
'expectedException' => true,
'expectedExceptionCode' => true,
'expectedExceptionMessage' => true,
'expectedExceptionMessageRegExp' => true,
'group' => true,
'large' => true,
'medium' => true,
'preserveGlobalState' => true,
'requires' => true,
'runTestsInSeparateProcesses' => true,
'runInSeparateProcess' => true,
'small' => true,
'test' => true,
'testdox' => true,
'testWith' => true,
'ticket' => true,
'uses' => true,
];
private const PhpCheckStyle = ['SuppressWarnings' => true];
private const PhpStorm = ['noinspection' => true];
private const PEAR = ['package_version' => true];
private const PlainUML = [
'startuml' => true,
'enduml' => true,
];
private const Symfony = ['experimental' => true];
private const PhpCodeSniffer = [
'codingStandardsIgnoreStart' => true,
'codingStandardsIgnoreEnd' => true,
];
private const SlevomatCodingStandard = ['phpcsSuppress' => true];
private const Phan = ['suppress' => true];
private const Rector = ['noRector' => true];
private const StaticAnalysis = [
// PHPStan, Psalm
'extends' => true,
'implements' => true,
'template' => true,
'use' => true,
// Psalm
'pure' => true,
'immutable' => true,
];
public const LIST = self::Reserved
+ self::WidelyUsedNonStandard
+ self::PhpDocumentor1
+ self::PhpDocumentor2
+ self::PHPUnit
+ self::PhpCheckStyle
+ self::PhpStorm
+ self::PEAR
+ self::PlainUML
+ self::Symfony
+ self::SlevomatCodingStandard
+ self::PhpCodeSniffer
+ self::Phan
+ self::Rector
+ self::StaticAnalysis;
private function __construct()
{
}
}

View File

@@ -0,0 +1,100 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use function call_user_func_array;
use function get_class;
/**
* Allows the reader to be used in-place of Doctrine's reader.
*/
class IndexedReader implements Reader
{
/** @var Reader */
private $delegate;
public function __construct(Reader $reader)
{
$this->delegate = $reader;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$annotations = [];
foreach ($this->delegate->getClassAnnotations($class) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotation)
{
return $this->delegate->getClassAnnotation($class, $annotation);
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$annotations = [];
foreach ($this->delegate->getMethodAnnotations($method) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotation)
{
return $this->delegate->getMethodAnnotation($method, $annotation);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$annotations = [];
foreach ($this->delegate->getPropertyAnnotations($property) as $annot) {
$annotations[get_class($annot)] = $annot;
}
return $annotations;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotation)
{
return $this->delegate->getPropertyAnnotation($property, $annotation);
}
/**
* Proxies all methods to the delegate.
*
* @param string $method
* @param mixed[] $args
*
* @return mixed
*/
public function __call($method, $args)
{
return call_user_func_array([$this->delegate, $method], $args);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Doctrine\Common\Annotations;
/**
* Marker interface for PHP7/PHP8 compatible support
* for named arguments (and constructor property promotion).
*
* @deprecated Implementing this interface is deprecated
* Use the Annotation @NamedArgumentConstructor instead
*/
interface NamedArgumentConstructorAnnotation
{
}

View File

@@ -0,0 +1,92 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionFunction;
use SplFileObject;
use function is_file;
use function method_exists;
use function preg_quote;
use function preg_replace;
/**
* Parses a file for namespaces/use/class declarations.
*/
final class PhpParser
{
/**
* Parses a class.
*
* @deprecated use parseUseStatements instead
*
* @param ReflectionClass $class A <code>ReflectionClass</code> object.
*
* @return array<string, class-string> A list with use statements in the form (Alias => FQN).
*/
public function parseClass(ReflectionClass $class)
{
return $this->parseUseStatements($class);
}
/**
* Parse a class or function for use statements.
*
* @param ReflectionClass|ReflectionFunction $reflection
*
* @psalm-return array<string, string> a list with use statements in the form (Alias => FQN).
*/
public function parseUseStatements($reflection): array
{
if (method_exists($reflection, 'getUseStatements')) {
return $reflection->getUseStatements();
}
$filename = $reflection->getFileName();
if ($filename === false) {
return [];
}
$content = $this->getFileContent($filename, $reflection->getStartLine());
if ($content === null) {
return [];
}
$namespace = preg_quote($reflection->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
$tokenizer = new TokenParser('<?php ' . $content);
return $tokenizer->parseUseStatements($reflection->getNamespaceName());
}
/**
* Gets the content of the file right up to the given line number.
*
* @param string $filename The name of the file to load.
* @param int $lineNumber The number of lines to read from file.
*
* @return string|null The content of the file or null if the file does not exist.
*/
private function getFileContent($filename, $lineNumber)
{
if (! is_file($filename)) {
return null;
}
$content = '';
$lineCnt = 0;
$file = new SplFileObject($filename);
while (! $file->eof()) {
if ($lineCnt++ === $lineNumber) {
break;
}
$content .= $file->fgets();
}
return $content;
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace Doctrine\Common\Annotations;
use Psr\Cache\CacheItemPoolInterface;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
use Reflector;
use function array_map;
use function array_merge;
use function assert;
use function filemtime;
use function max;
use function rawurlencode;
use function time;
/**
* A cache aware annotation reader.
*/
final class PsrCachedReader implements Reader
{
/** @var Reader */
private $delegate;
/** @var CacheItemPoolInterface */
private $cache;
/** @var bool */
private $debug;
/** @var array<string, array<object>> */
private $loadedAnnotations = [];
/** @var int[] */
private $loadedFilemtimes = [];
public function __construct(Reader $reader, CacheItemPoolInterface $cache, bool $debug = false)
{
$this->delegate = $reader;
$this->cache = $cache;
$this->debug = (bool) $debug;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
$cacheKey = $class->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getClassAnnotations', $class);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
foreach ($this->getClassAnnotations($class) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
$class = $property->getDeclaringClass();
$cacheKey = $class->getName() . '$' . $property->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getPropertyAnnotations', $property);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
$class = $method->getDeclaringClass();
$cacheKey = $class->getName() . '#' . $method->getName();
if (isset($this->loadedAnnotations[$cacheKey])) {
return $this->loadedAnnotations[$cacheKey];
}
$annots = $this->fetchFromCache($cacheKey, $class, 'getMethodAnnotations', $method);
return $this->loadedAnnotations[$cacheKey] = $annots;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
public function clearLoadedAnnotations(): void
{
$this->loadedAnnotations = [];
$this->loadedFilemtimes = [];
}
/** @return mixed[] */
private function fetchFromCache(
string $cacheKey,
ReflectionClass $class,
string $method,
Reflector $reflector
): array {
$cacheKey = rawurlencode($cacheKey);
$item = $this->cache->getItem($cacheKey);
if (($this->debug && ! $this->refresh($cacheKey, $class)) || ! $item->isHit()) {
$this->cache->save($item->set($this->delegate->{$method}($reflector)));
}
return $item->get();
}
/**
* Used in debug mode to check if the cache is fresh.
*
* @return bool Returns true if the cache was fresh, or false if the class
* being read was modified since writing to the cache.
*/
private function refresh(string $cacheKey, ReflectionClass $class): bool
{
$lastModification = $this->getLastModification($class);
if ($lastModification === 0) {
return true;
}
$item = $this->cache->getItem('[C]' . $cacheKey);
if ($item->isHit() && $item->get() >= $lastModification) {
return true;
}
$this->cache->save($item->set(time()));
return false;
}
/**
* Returns the time the class was last modified, testing traits and parents
*/
private function getLastModification(ReflectionClass $class): int
{
$filename = $class->getFileName();
if (isset($this->loadedFilemtimes[$filename])) {
return $this->loadedFilemtimes[$filename];
}
$parent = $class->getParentClass();
$lastModification = max(array_merge(
[$filename ? filemtime($filename) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $class->getTraits()),
array_map(function (ReflectionClass $class): int {
return $this->getLastModification($class);
}, $class->getInterfaces()),
$parent ? [$this->getLastModification($parent)] : []
));
assert($lastModification !== false);
return $this->loadedFilemtimes[$filename] = $lastModification;
}
private function getTraitLastModificationTime(ReflectionClass $reflectionTrait): int
{
$fileName = $reflectionTrait->getFileName();
if (isset($this->loadedFilemtimes[$fileName])) {
return $this->loadedFilemtimes[$fileName];
}
$lastModificationTime = max(array_merge(
[$fileName ? filemtime($fileName) : 0],
array_map(function (ReflectionClass $reflectionTrait): int {
return $this->getTraitLastModificationTime($reflectionTrait);
}, $reflectionTrait->getTraits())
));
assert($lastModificationTime !== false);
return $this->loadedFilemtimes[$fileName] = $lastModificationTime;
}
}

View File

@@ -0,0 +1,80 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Interface for annotation readers.
*/
interface Reader
{
/**
* Gets the annotations applied to a class.
*
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getClassAnnotations(ReflectionClass $class);
/**
* Gets a class annotation.
*
* @param ReflectionClass $class The ReflectionClass of the class from which
* the class annotations should be read.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName);
/**
* Gets the annotations applied to a method.
*
* @param ReflectionMethod $method The ReflectionMethod of the method from which
* the annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getMethodAnnotations(ReflectionMethod $method);
/**
* Gets a method annotation.
*
* @param ReflectionMethod $method The ReflectionMethod to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName);
/**
* Gets the annotations applied to a property.
*
* @param ReflectionProperty $property The ReflectionProperty of the property
* from which the annotations should be read.
*
* @return array<object> An array of Annotations.
*/
public function getPropertyAnnotations(ReflectionProperty $property);
/**
* Gets a property annotation.
*
* @param ReflectionProperty $property The ReflectionProperty to read the annotations from.
* @param class-string<T> $annotationName The name of the annotation.
*
* @return T|null The Annotation or NULL, if the requested annotation does not exist.
*
* @template T
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName);
}

View File

@@ -0,0 +1,114 @@
<?php
namespace Doctrine\Common\Annotations;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
/**
* Simple Annotation Reader.
*
* This annotation reader is intended to be used in projects where you have
* full-control over all annotations that are available.
*
* @deprecated Deprecated in favour of using AnnotationReader
*/
class SimpleAnnotationReader implements Reader
{
/** @var DocParser */
private $parser;
/**
* Initializes a new SimpleAnnotationReader.
*/
public function __construct()
{
$this->parser = new DocParser();
$this->parser->setIgnoreNotImportedAnnotations(true);
}
/**
* Adds a namespace in which we will look for annotations.
*
* @param string $namespace
*
* @return void
*/
public function addNamespace($namespace)
{
$this->parser->addNamespace($namespace);
}
/**
* {@inheritDoc}
*/
public function getClassAnnotations(ReflectionClass $class)
{
return $this->parser->parse($class->getDocComment(), 'class ' . $class->getName());
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotations(ReflectionMethod $method)
{
return $this->parser->parse(
$method->getDocComment(),
'method ' . $method->getDeclaringClass()->name . '::' . $method->getName() . '()'
);
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotations(ReflectionProperty $property)
{
return $this->parser->parse(
$property->getDocComment(),
'property ' . $property->getDeclaringClass()->name . '::$' . $property->getName()
);
}
/**
* {@inheritDoc}
*/
public function getClassAnnotation(ReflectionClass $class, $annotationName)
{
foreach ($this->getClassAnnotations($class) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getMethodAnnotation(ReflectionMethod $method, $annotationName)
{
foreach ($this->getMethodAnnotations($method) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
/**
* {@inheritDoc}
*/
public function getPropertyAnnotation(ReflectionProperty $property, $annotationName)
{
foreach ($this->getPropertyAnnotations($property) as $annot) {
if ($annot instanceof $annotationName) {
return $annot;
}
}
return null;
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Doctrine\Common\Annotations;
use function array_merge;
use function count;
use function explode;
use function strtolower;
use function token_get_all;
use const PHP_VERSION_ID;
use const T_AS;
use const T_COMMENT;
use const T_DOC_COMMENT;
use const T_NAME_FULLY_QUALIFIED;
use const T_NAME_QUALIFIED;
use const T_NAMESPACE;
use const T_NS_SEPARATOR;
use const T_STRING;
use const T_USE;
use const T_WHITESPACE;
/**
* Parses a file for namespaces/use/class declarations.
*/
class TokenParser
{
/**
* The token list.
*
* @phpstan-var list<mixed[]>
*/
private $tokens;
/**
* The number of tokens.
*
* @var int
*/
private $numTokens;
/**
* The current array pointer.
*
* @var int
*/
private $pointer = 0;
/**
* @param string $contents
*/
public function __construct($contents)
{
$this->tokens = token_get_all($contents);
// The PHP parser sets internal compiler globals for certain things. Annoyingly, the last docblock comment it
// saw gets stored in doc_comment. When it comes to compile the next thing to be include()d this stored
// doc_comment becomes owned by the first thing the compiler sees in the file that it considers might have a
// docblock. If the first thing in the file is a class without a doc block this would cause calls to
// getDocBlock() on said class to return our long lost doc_comment. Argh.
// To workaround, cause the parser to parse an empty docblock. Sure getDocBlock() will return this, but at least
// it's harmless to us.
token_get_all("<?php\n/**\n *\n */");
$this->numTokens = count($this->tokens);
}
/**
* Gets the next non whitespace and non comment token.
*
* @param bool $docCommentIsComment If TRUE then a doc comment is considered a comment and skipped.
* If FALSE then only whitespace and normal comments are skipped.
*
* @return mixed[]|string|null The token if exists, null otherwise.
*/
public function next($docCommentIsComment = true)
{
for ($i = $this->pointer; $i < $this->numTokens; $i++) {
$this->pointer++;
if (
$this->tokens[$i][0] === T_WHITESPACE ||
$this->tokens[$i][0] === T_COMMENT ||
($docCommentIsComment && $this->tokens[$i][0] === T_DOC_COMMENT)
) {
continue;
}
return $this->tokens[$i];
}
return null;
}
/**
* Parses a single use statement.
*
* @return array<string, string> A list with all found class names for a use statement.
*/
public function parseUseStatement()
{
$groupRoot = '';
$class = '';
$alias = '';
$statements = [];
$explicitAlias = false;
while (($token = $this->next())) {
if (! $explicitAlias && $token[0] === T_STRING) {
$class .= $token[1];
$alias = $token[1];
} elseif ($explicitAlias && $token[0] === T_STRING) {
$alias = $token[1];
} elseif (
PHP_VERSION_ID >= 80000 &&
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
) {
$class .= $token[1];
$classSplit = explode('\\', $token[1]);
$alias = $classSplit[count($classSplit) - 1];
} elseif ($token[0] === T_NS_SEPARATOR) {
$class .= '\\';
$alias = '';
} elseif ($token[0] === T_AS) {
$explicitAlias = true;
$alias = '';
} elseif ($token === ',') {
$statements[strtolower($alias)] = $groupRoot . $class;
$class = '';
$alias = '';
$explicitAlias = false;
} elseif ($token === ';') {
$statements[strtolower($alias)] = $groupRoot . $class;
break;
} elseif ($token === '{') {
$groupRoot = $class;
$class = '';
} elseif ($token === '}') {
continue;
} else {
break;
}
}
return $statements;
}
/**
* Gets all use statements.
*
* @param string $namespaceName The namespace name of the reflected class.
*
* @return array<string, string> A list with all found use statements.
*/
public function parseUseStatements($namespaceName)
{
$statements = [];
while (($token = $this->next())) {
if ($token[0] === T_USE) {
$statements = array_merge($statements, $this->parseUseStatement());
continue;
}
if ($token[0] !== T_NAMESPACE || $this->parseNamespace() !== $namespaceName) {
continue;
}
// Get fresh array for new namespace. This is to prevent the parser to collect the use statements
// for a previous namespace with the same name. This is the case if a namespace is defined twice
// or if a namespace with the same name is commented out.
$statements = [];
}
return $statements;
}
/**
* Gets the namespace.
*
* @return string The found namespace.
*/
public function parseNamespace()
{
$name = '';
while (
($token = $this->next()) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR || (
PHP_VERSION_ID >= 80000 &&
($token[0] === T_NAME_QUALIFIED || $token[0] === T_NAME_FULLY_QUALIFIED)
))
) {
$name .= $token[1];
}
return $name;
}
/**
* Gets the class name.
*
* @return string The found class name.
*/
public function parseClass()
{
// Namespaces and class names are tokenized the same: T_STRINGs
// separated by T_NS_SEPARATOR so we can use one function to provide
// both.
return $this->parseNamespace();
}
}

19
vendor/doctrine/lexer/LICENSE vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2006-2018 Doctrine Project
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
vendor/doctrine/lexer/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
# Doctrine Lexer
Build Status: [![Build Status](https://travis-ci.org/doctrine/lexer.svg?branch=master)](https://travis-ci.org/doctrine/lexer)
Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.
This lexer is used in Doctrine Annotations and in Doctrine ORM (DQL).
https://www.doctrine-project.org/projects/lexer.html

41
vendor/doctrine/lexer/composer.json vendored Normal file
View File

@@ -0,0 +1,41 @@
{
"name": "doctrine/lexer",
"type": "library",
"description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.",
"keywords": [
"php",
"parser",
"lexer",
"annotations",
"docblock"
],
"homepage": "https://www.doctrine-project.org/projects/lexer.html",
"license": "MIT",
"authors": [
{"name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com"},
{"name": "Roman Borschel", "email": "roman@code-factory.org"},
{"name": "Johannes Schmitt", "email": "schmittjoh@gmail.com"}
],
"require": {
"php": "^7.2 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^6.0",
"phpstan/phpstan": "^0.11.8",
"phpunit/phpunit": "^8.2"
},
"autoload": {
"psr-4": { "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" }
},
"autoload-dev": {
"psr-4": { "Doctrine\\Tests\\": "tests/Doctrine" }
},
"extra": {
"branch-alias": {
"dev-master": "1.2.x-dev"
}
},
"config": {
"sort-packages": true
}
}

View File

@@ -0,0 +1,328 @@
<?php
declare(strict_types=1);
namespace Doctrine\Common\Lexer;
use ReflectionClass;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
use const PREG_SPLIT_OFFSET_CAPTURE;
use function implode;
use function in_array;
use function preg_split;
use function sprintf;
use function substr;
/**
* Base class for writing simple lexers, i.e. for creating small DSLs.
*/
abstract class AbstractLexer
{
/**
* Lexer original input string.
*
* @var string
*/
private $input;
/**
* Array of scanned tokens.
*
* Each token is an associative array containing three items:
* - 'value' : the string value of the token in the input string
* - 'type' : the type of the token (identifier, numeric, string, input
* parameter, none)
* - 'position' : the position of the token in the input string
*
* @var array
*/
private $tokens = [];
/**
* Current lexer position in input string.
*
* @var int
*/
private $position = 0;
/**
* Current peek of current lexer position.
*
* @var int
*/
private $peek = 0;
/**
* The next token in the input.
*
* @var array|null
*/
public $lookahead;
/**
* The last matched/seen token.
*
* @var array|null
*/
public $token;
/**
* Composed regex for input parsing.
*
* @var string
*/
private $regex;
/**
* Sets the input data to be tokenized.
*
* The Lexer is immediately reset and the new input tokenized.
* Any unprocessed tokens from any previous input are lost.
*
* @param string $input The input to be tokenized.
*
* @return void
*/
public function setInput($input)
{
$this->input = $input;
$this->tokens = [];
$this->reset();
$this->scan($input);
}
/**
* Resets the lexer.
*
* @return void
*/
public function reset()
{
$this->lookahead = null;
$this->token = null;
$this->peek = 0;
$this->position = 0;
}
/**
* Resets the peek pointer to 0.
*
* @return void
*/
public function resetPeek()
{
$this->peek = 0;
}
/**
* Resets the lexer position on the input to the given position.
*
* @param int $position Position to place the lexical scanner.
*
* @return void
*/
public function resetPosition($position = 0)
{
$this->position = $position;
}
/**
* Retrieve the original lexer's input until a given position.
*
* @param int $position
*
* @return string
*/
public function getInputUntilPosition($position)
{
return substr($this->input, 0, $position);
}
/**
* Checks whether a given token matches the current lookahead.
*
* @param int|string $token
*
* @return bool
*/
public function isNextToken($token)
{
return $this->lookahead !== null && $this->lookahead['type'] === $token;
}
/**
* Checks whether any of the given tokens matches the current lookahead.
*
* @param array $tokens
*
* @return bool
*/
public function isNextTokenAny(array $tokens)
{
return $this->lookahead !== null && in_array($this->lookahead['type'], $tokens, true);
}
/**
* Moves to the next token in the input string.
*
* @return bool
*/
public function moveNext()
{
$this->peek = 0;
$this->token = $this->lookahead;
$this->lookahead = isset($this->tokens[$this->position])
? $this->tokens[$this->position++] : null;
return $this->lookahead !== null;
}
/**
* Tells the lexer to skip input tokens until it sees a token with the given value.
*
* @param string $type The token type to skip until.
*
* @return void
*/
public function skipUntil($type)
{
while ($this->lookahead !== null && $this->lookahead['type'] !== $type) {
$this->moveNext();
}
}
/**
* Checks if given value is identical to the given token.
*
* @param mixed $value
* @param int|string $token
*
* @return bool
*/
public function isA($value, $token)
{
return $this->getType($value) === $token;
}
/**
* Moves the lookahead token forward.
*
* @return array|null The next token or NULL if there are no more tokens ahead.
*/
public function peek()
{
if (isset($this->tokens[$this->position + $this->peek])) {
return $this->tokens[$this->position + $this->peek++];
}
return null;
}
/**
* Peeks at the next token, returns it and immediately resets the peek.
*
* @return array|null The next token or NULL if there are no more tokens ahead.
*/
public function glimpse()
{
$peek = $this->peek();
$this->peek = 0;
return $peek;
}
/**
* Scans the input string for tokens.
*
* @param string $input A query string.
*
* @return void
*/
protected function scan($input)
{
if (! isset($this->regex)) {
$this->regex = sprintf(
'/(%s)|%s/%s',
implode(')|(', $this->getCatchablePatterns()),
implode('|', $this->getNonCatchablePatterns()),
$this->getModifiers()
);
}
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_OFFSET_CAPTURE;
$matches = preg_split($this->regex, $input, -1, $flags);
if ($matches === false) {
// Work around https://bugs.php.net/78122
$matches = [[$input, 0]];
}
foreach ($matches as $match) {
// Must remain before 'value' assignment since it can change content
$type = $this->getType($match[0]);
$this->tokens[] = [
'value' => $match[0],
'type' => $type,
'position' => $match[1],
];
}
}
/**
* Gets the literal for a given token.
*
* @param int|string $token
*
* @return int|string
*/
public function getLiteral($token)
{
$className = static::class;
$reflClass = new ReflectionClass($className);
$constants = $reflClass->getConstants();
foreach ($constants as $name => $value) {
if ($value === $token) {
return $className . '::' . $name;
}
}
return $token;
}
/**
* Regex modifiers
*
* @return string
*/
protected function getModifiers()
{
return 'iu';
}
/**
* Lexical catchable patterns.
*
* @return array
*/
abstract protected function getCatchablePatterns();
/**
* Lexical non-catchable patterns.
*
* @return array
*/
abstract protected function getNonCatchablePatterns();
/**
* Retrieve token type. Also processes the token value if necessary.
*
* @param string $value
*
* @return int|string|null
*/
abstract protected function getType(&$value);
}

View File

@@ -0,0 +1,17 @@
name: 'GitHub Actions Mirror'
on: [push]
jobs:
mirror_to_gitee:
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@v1
- name: 'Mirror to gitee'
uses: pixta-dev/repository-mirroring-action@v1
with:
target_repo_url:
git@gitee.com:hg-code/thinkphp-apidoc.git
ssh_private_key:
${{ secrets.GITEE_KEY }}

1
vendor/hg/apidoc/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

21
vendor/hg/apidoc/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 HG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

79
vendor/hg/apidoc/README.md vendored Normal file
View File

@@ -0,0 +1,79 @@
<p align="center">
<img width="120" src="https://apidoc.demo.hg-code.com/images/logo.png">
</p>
<h1 align="center">
ThinkPHP ApiDoc
</h1>
<div align="center">
基于ThinkPHP 根据注释自动生成API接口文档
</div>
<div align="center" style="margin-top:10px;margin-bottom:50px;">
<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/v/hg/apidoc"></a>
<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/dt/hg/apidoc"></a>
<a href="https://packagist.org/packages/hg/apidoc"><img src="https://img.shields.io/packagist/l/hg/apidoc"></a>
<a href="https://github.com/HGthecode/thinkphp-apidoc"><img src="https://img.shields.io/github/issues/HGthecode/thinkphp-apidoc"></a>
<a href="https://github.com/HGthecode/thinkphp-apidoc"><img src="https://img.shields.io/github/forks/HGthecode/thinkphp-apidoc"></a>
</div>
## 🤷‍♀️ Apidoc是什么
如今前后端分离的开发模式以必不可少基于ThinkPHP可以很方便的作为后端Api的开发。可是一个Api开发过程中需要快速调试开发完成后需要给其它开发者对接这时一个详细的Api文档就显得特别重要。
大多数开发者可能都是通过各种工具配合来达到这一目的其各种工具的安装和配置也是繁琐。甚至还有通过word等文本工具手写api文档的这样的开发效率与可维护性是非常差的。
综合种种Api开发中的痛点我们专为ThinkPHP开发了Apidoc的扩展插件本插件可通过简单的注解即可生成Api文档及帮助开发者提高生产效率的在线调试、快速生成Crud、一键生成整个模块Api等涵盖Api开发方方面面。
## ✨特性
- 开箱即用无繁杂的配置、安装后按文档编写注释即可自动生成API文档。
- 在线调试:在线文档可直接调试,支持设置全局参数,接口调试省时省力。
- 轻松编写:支持公共注释定义、业务逻辑层、数据表字段的引用,几句注释即可完成。
- 安全高效:支持访问密码验证、应用/版本独立密码;支持文档缓存。
- 多应用/多版本可适应各种单应用、多应用、多版本的项目的Api管理。
- Markdown文档支持.md文件的文档展示。
- 快速生成Crud配置+模板即可快速生成Crud接口代码及数据表的创建大大提高工作效率。
- 控制器分组:更精细化的对控制器接口进行分组展示。
## 📌兼容
ThinkPHP 5.x
ThinkPHP 6.x
> 📢 2.6.0版本开始全面兼容TP5了啦~~~
## 📖使用文档
[ThinkPHP ApiDoc V2.x文档](https://hg-code.gitee.io/thinkphp-apidoc/)
## 🏆支持我们
如果本项目对您有所帮助请点个Star支持我们
- [Github](https://github.com/HGthecode/thinkphp-apidoc) -> <a href="https://github.com/HGthecode/thinkphp-apidoc" target="_blank">
<img height="22" src="https://img.shields.io/github/stars/HGthecode/thinkphp-apidoc?style=social" class="attachment-full size-full" alt="Star me on GitHub" data-recalc-dims="1" /></a>
- [Gitee](https://gitee.com/hg-code/thinkphp-apidoc) -> <a href="https://gitee.com/hg-code/thinkphp-apidoc/stargazers"><img src="https://gitee.com/hg-code/thinkphp-apidoc/badge/star.svg" alt="star"></a>
## 💡鸣谢
[ThinkPHP](http://www.thinkphp.cn/)
<a href="http://www.thinkphp.cn/" target="_blank">ThinkPHP</a>
<a href="https://github.com/doctrine/annotations" target="_blank">doctrine/annotations</a>
## 🔗链接
<a href="https://github.com/HGthecode/apidoc-ui" target="_blank">ApiDoc UI前端</a>
<a href="https://github.com/HGthecode/thinkphp-apidoc-demo" target="_blank">ApiDoc Demo 示例项目</a>

43
vendor/hg/apidoc/composer.json vendored Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "hg/apidoc",
"description": "thinkphp API文档自动生成",
"type": "think-extend",
"keywords": [
"thinkphp",
"swagger",
"apidoc",
"api文档",
"接口文档",
"自动生成",
"注释生成",
"php接口文档"
],
"require": {
"php": ">=7.1.0",
"doctrine/annotations": "^1.6",
"symfony/class-loader": "~3.2.0"
},
"license": "MIT",
"authors": [
{
"name": "hg-code",
"email": "376401263@qq.com"
}
],
"autoload": {
"psr-4": {
"hg\\apidoc\\": "src/"
}
},
"extra": {
"think": {
"services": [
"hg\\apidoc\\Service"
],
"config": {
"apidoc": "src/config.php"
}
}
},
"minimum-stability": "beta"
}

172
vendor/hg/apidoc/src/Auth.php vendored Normal file
View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc;
use think\facade\Config;
use think\facade\Request;
use hg\apidoc\exception\AuthException;
class Auth
{
protected $config = [];
public function __construct()
{
$this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
}
/**
* 验证密码
* @param $password
* @return false|string
*/
public function verifyAuth(string $password, string $appKey)
{
if (!empty($appKey)) {
$currentApps = (new Utils())->getCurrentApps($appKey);
$currentApp = $currentApps[count($currentApps) - 1];
if (!empty($currentApp) && !empty($currentApp['password'])) {
// 应用密码
if (md5($currentApp['password']) === $password) {
return $this->createToken($currentApp['password']);
}
throw new AuthException("password error");
}
}
if ($this->config['auth']['enable']) {
// 密码验证
if (md5($this->config['auth']['password']) === $password) {
return $this->createToken($this->config['auth']['password']);
}
throw new AuthException("password error");
}
return false;
}
/**
* 获取tokencode
* @param string $password
* @return string
*/
protected function getTokenCode(string $password): string
{
return md5(md5($password) . strtotime(date('Y-m-d', time())));
}
/**
* 创建token
* @param string $password
* @return string
*/
public function createToken(string $password): string
{
return $this->handleToken($this->getTokenCode($password), "CE");
}
/**
* 验证token
* @param $token
* @return bool
*/
public function checkToken(string $token, string $password): bool
{
if (empty($password)) {
$password = $this->config['auth']['password'];
}
$decode = $this->handleToken($token, "DE");
if ($decode === $this->getTokenCode($password)) {
return true;
}
return false;
}
/**
* @param $request
* @return bool
*/
public function checkAuth(string $appKey): bool
{
$config = $this->config;
$request = Request::instance();
$token = $request->param("apidocToken");
if (!empty($appKey)) {
$currentApps = (new Utils())->getCurrentApps($appKey);
$currentApp = $currentApps[count($currentApps) - 1];
if (!empty($currentApp) && !empty($currentApp['password'])) {
if (empty($token)) {
throw new AuthException("token not found");
}
// 应用密码
if ($this->checkToken($token, $currentApp['password'])) {
return true;
} else {
throw new AuthException("token error");
}
} else if (!(!empty($config['auth']) && $config['auth']['enable'])) {
return true;
}
}
if(!empty($config['auth']) && $config['auth']['enable'] && empty($token)){
throw new AuthException("token not found");
}else if (!empty($token) && !$this->checkToken($token, "")) {
throw new AuthException("token error");
}
return true;
}
/**
* 处理token
* @param $string
* @param string $operation
* @param string $key
* @param int $expiry
* @return false|string
*/
protected function handleToken(string $string, string $operation = 'DE', string $key = '', int $expiry = 0):string
{
$ckey_length = 4;
$key = md5($key ? $key : $this->config['auth']['secret_key']);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya . md5($keya . $keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for ($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for ($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for ($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if ($operation == 'DE') {
if ((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
} else {
return $keyc . str_replace('=', '', base64_encode($result));
}
}
}

259
vendor/hg/apidoc/src/Controller.php vendored Normal file
View File

@@ -0,0 +1,259 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc;
use hg\apidoc\exception\AuthException;
use hg\apidoc\exception\ErrorException;
use hg\apidoc\parseApi\CacheApiData;
use hg\apidoc\parseApi\ParseAnnotation;
use hg\apidoc\parseApi\ParseMarkdown;
use think\App;
use think\facade\Config;
use think\facade\Request;
use hg\apidoc\crud\CreateCrud;
class Controller
{
protected $app;
protected $config;
protected $defaultConfig=[
'crud'=>[
'model'=>[
'fields_types'=>[
"int",
"tinyint",
"smallint",
"mediumint",
"integer",
"bigint",
"bit",
"real",
"float",
"decimal",
"numeric",
"char",
"varchar",
"date",
"time",
"year",
"timestamp",
"datetime",
"tinyblob",
"blob",
"mediumblob",
"longblob",
"tinytext",
"text",
"mediumtext",
"longtext",
"enum",
"set",
"binary",
"varbinary",
"point",
"linestring",
"polygon",
"geometry",
"multipoint",
"multilinestring",
"multipolygon",
"geometrycollection"
]
]
]
];
public function __construct(App $app)
{
$this->app = $app;
$this->config = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
}
/**
* 获取配置
* @return \think\response\Json
*/
public function getConfig(){
$config = $this->config;
if (!empty($config['auth'])){
unset($config['auth']['auth_password']);
unset($config['auth']['password']);
unset($config['auth']['key']);
}
// 处理统一返回信息
if (!empty($config['responses']) && is_string($config['responses'])){
// 兼容原配置
$config['responses'] = [
'jsonStr'=>$config['responses']
];
}else if (!empty($config['responses']) && isset($config['responses']['show_responses']) && !$config['responses']['show_responses'] && !empty($config['responses']['data'])){
// 显示在提示中
$responsesStr = '{'."\r\n";
$responsesMain = "";
foreach ($config['responses']['data'] as $item){
$responsesStr.='"'.$item['name'].'":"'.$item['desc'].'",'."\r\n";
if (!empty($item['main']) && $item['main']==true){
$responsesMain = $item;
}
}
$responsesStr.= '}';
$config['responses']['jsonStr']=$responsesStr;
$config['responses']['main']=$responsesMain;
}
$config['debug']=app()->isDebug();
if (!empty($config['crud'])){
// 无配置可选字段类型,使用默认的
if (!empty($config['crud']['model']) && empty($config['crud']['model']['fields_types'])){
$config['crud']['model']['fields_types'] = $this->defaultConfig['crud']['model']['fields_types'];
}
// 过滤route文件配置
if (!empty($config['crud']['route'])){
unset($config['crud']['route']);
}
}
// 清除apps配置中的password
$config['apps'] = (new Utils())->handleAppsConfig($config['apps']);
return Utils::showJson(0,"",$config);
}
/**
* 验证密码
* @return false|\think\response\Json
* @throws \think\Exception
*/
public function verifyAuth(){
$config = $this->config;
$request = Request::instance();
$params = $request->param();
$password = $params['password'];
if (empty($password)){
throw new AuthException( "password not found");
}
$appKey = !empty($params['appKey'])?$params['appKey']:"";
if (!$appKey && !(!empty($config['auth']) && $config['auth']['enable'])) {
return false;
}
try {
$hasAuth = (new Auth())->verifyAuth($password,$appKey);
return Utils::showJson(0,"",$hasAuth);
} catch (AuthException $e) {
return Utils::showJson($e->getCode(),$e->getMessage());
}
}
/**
* 获取文档数据
* @return \think\response\Json
*/
public function getApidoc(){
$config = $this->config;
$request = Request::instance();
$params = $request->param();
(new Auth())->checkAuth($params['appKey']);
$cacheData=null;
if (!empty($config['cache']) && $config['cache']['enable']){
$cacheData = (new CacheApiData())->get($params['appKey'],$params['cacheFileName']);
if ($cacheData && $params['reload']=='false'){
$apiData = $cacheData['data'];
}else{
// 生成数据并缓存
$apiData = (new ParseAnnotation())->renderApiData($params['appKey']);
$cacheData =(new CacheApiData())->set($params['appKey'],$apiData);
}
}else{
// 生成数据
$apiData = (new ParseAnnotation())->renderApiData($params['appKey']);
}
$groups=[['title'=>'全部','name'=>0]];
// 获取md
$docs = (new ParseMarkdown())->getDocsMenu();
if (count($docs)>0){
$menu_title = !empty($config['docs']) && !empty($config['docs']['menu_title'])?$config['docs']['menu_title']:'文档';
$groups[]=['title'=>$menu_title,'name'=>'markdown_doc'];
}
if (!empty($config['groups']) && count($config['groups'])>0 && !empty($apiData['groups']) && count($apiData['groups'])>0){
$configGroups=[];
foreach ($config['groups'] as $group) {
if (in_array($group['name'],$apiData['groups'])){
$configGroups[]=$group;
}
}
$groups = array_merge($groups,$configGroups);
}
$json=[
'groups'=>$groups,
'list'=>$apiData['data'],
'docs'=>$docs,
'tags'=>$apiData['tags']
];
if ($cacheData && !empty($cacheData['list'])){
$json['cacheFiles']=$cacheData['list'];
$json['cacheName']=$cacheData['name'];
}
return Utils::showJson(0,"",$json);
}
/**
* 获取md文档内容
* @return \think\response\Json
*/
public function getMdDetail(){
$request = Request::instance();
$params = $request->param();
try {
if (empty($params['path'])){
throw new ErrorException("mdPath not found");
}
if (empty($params['appKey'])){
throw new ErrorException("appkey not found");
}
(new Auth())->checkAuth($params['appKey']);
$res = (new ParseMarkdown())->getContent($params['appKey'],$params['path']);
return Utils::showJson(0,"",$res);
} catch (ErrorException $e) {
return Utils::showJson($e->getCode(),$e->getMessage());
}
}
/**
* 创建Crud
* @return \think\response\Json
*/
public function createCrud()
{
if (!\think\facade\App::isDebug()) {
throw new ErrorException("no debug",501);
}
$request = Request::instance();
$params = $request->param();
if (!isset($params['appKey'])){
throw new ErrorException("appkey not found");
}
(new Auth())->checkAuth($params['appKey']);
$res = (new CreateCrud())->create($params);
return Utils::showJson(0,"",$res);
}
}

35
vendor/hg/apidoc/src/Service.php vendored Normal file
View File

@@ -0,0 +1,35 @@
<?php
namespace hg\apidoc;
use think\facade\Config;
use think\facade\Route;
class Service extends \think\Service
{
public function boot()
{
$this->registerRoutes(function (){
$route_prefix = 'apidoc';
$apidocConfig = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
$routes = function () {
$controller_namespace = '\hg\apidoc\Controller@';
Route::get('config' , $controller_namespace . 'getConfig');
Route::get('apiData' , $controller_namespace . 'getApidoc');
Route::get('mdDetail' , $controller_namespace . 'getMdDetail');
Route::post('verifyAuth' , $controller_namespace . 'verifyAuth');
Route::post('createCrud' , $controller_namespace . 'createCrud');
};
if (!empty($apidocConfig['allowCrossDomain'])){
Route::group($route_prefix, $routes)->allowCrossDomain();
}
Route::group($route_prefix, $routes);
});
}
}

330
vendor/hg/apidoc/src/Utils.php vendored Normal file
View File

@@ -0,0 +1,330 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc;
use hg\apidoc\exception\ErrorException;
use think\facade\Config;
use think\response\Json;
class Utils
{
protected static $snakeCache = [];
/**
* 统一返回json格式
* @param int $code
* @param string $msg
* @param string $data
* @return \think\response\Json
*/
public static function showJson(int $code = 0, string $msg = "", $data = ""):Json
{
$res = [
'code' => $code,
'msg' => $msg,
'data' => $data,
];
return json($res);
}
/**
* 过滤参数字段
* @param $data
* @param $fields
* @param string $type
* @return array
*/
public function filterParamsField(array $data, $fields, string $type = "field"): array
{
if ($fields && strpos($fields, ',') !== false){
$fieldArr = explode(',', $fields);
}else{
$fieldArr = [$fields];
}
$dataList = [];
foreach ($data as $item) {
if (!empty($item['name']) && in_array($item['name'], $fieldArr) && $type === 'field') {
$dataList[] = $item;
} else if (!(!empty($item['name']) && in_array($item['name'], $fieldArr)) && $type == "withoutField") {
$dataList[] = $item;
}
}
return $dataList;
}
/**
* 读取文件内容
* @param $fileName
* @return false|string
*/
public static function getFileContent(string $fileName): string
{
$content = "";
if (file_exists($fileName)) {
$handle = fopen($fileName, "r");
$content = fread($handle, filesize($fileName));
fclose($handle);
}
return $content;
}
/**
* 保存文件
* @param $path
* @param $str_tmp
* @return bool
*/
public static function createFile(string $path, string $str_tmp): bool
{
$pathArr = explode("/", $path);
unset($pathArr[count($pathArr) - 1]);
$dir = implode("/", $pathArr);
if (!file_exists($dir)) {
mkdir($dir, 0777, true);
}
$fp = fopen($path, "w") or die("Unable to open file!");
fwrite($fp, $str_tmp); //存入内容
fclose($fp);
return true;
}
/**
* 删除文件
* @param $path
*/
public static function delFile(string $path)
{
$url = iconv('utf-8', 'gbk', $path);
if (PATH_SEPARATOR == ':') { //linux
unlink($path);
} else { //Windows
unlink($url);
}
}
/**
* 将tree树形数据转成list数据
* @param array $tree tree数据
* @param string $childName 子节点名称
* @return array 转换后的list数据
*/
public function treeToList(array $tree, string $childName = 'children',string $key = "id",string $parentField = "parent")
{
$array = array();
foreach ($tree as $val) {
$array[] = $val;
if (isset($val[$childName])) {
$children = $this->treeToList($val[$childName], $childName);
if ($children) {
$newChildren = [];
foreach ($children as $item) {
$item[$parentField] = $val[$key];
$newChildren[] = $item;
}
$array = array_merge($array, $newChildren);
}
}
}
return $array;
}
/**
* 根据一组keys获取所有关联节点
* @param $tree
* @param $keys
*/
public function getTreeNodesByKeys(array $tree, array $keys, string $field = "id", string $childrenField = "children")
{
$list = $this->TreeToList($tree, $childrenField, "folder");
$data = [];
foreach ($keys as $k => $v) {
$parent = !$k ? "" : $keys[$k - 1];
foreach ($list as $item) {
if (((!empty($item['parent']) && $item['parent'] === $parent) || empty($item['parent'])) && $item[$field] == $v) {
$data[] = $item;
break;
}
}
}
return $data;
}
/**
* 替换模板变量
* @param $temp
* @param $data
* @return string|string[]
*/
public static function replaceTemplate(string $temp, array $data):string
{
$str = $temp;
foreach ($data as $k => $v) {
$key = '${' . $k . '}';
if (strpos($str, $key) !== false) {
$str = str_replace($key, $v, $str);
}
}
return $str;
}
/**
* 替换当前所选应用/版本的变量
* @param $temp
* @param $currentApps
* @return string|string[]
*/
public function replaceCurrentAppTemplate(string $temp,array $currentApps):string
{
$str = $temp;
if (!empty($currentApps) && count($currentApps) > 0) {
$data = [];
for ($i = 0; $i <= 3; $i++) {
if (isset($currentApps[$i])) {
$appItem = $currentApps[$i];
foreach ($appItem as $k => $v) {
$key = 'app[' . $i . '].' . $k;
$data[$key] = $v;
}
} else {
$appItem = $currentApps[0];
foreach ($appItem as $k => $v) {
$key = 'app[' . $i . '].' . $k;
$data[$key] = "";
}
}
}
$str = $this->replaceTemplate($str, $data);
}
return $str;
}
/**
* 根据条件获取数组中的值
* @param array $array
* @param $query
* @return mixed|null
*/
public static function getArrayFind(array $array, $query)
{
$res = null;
if (is_array($array)) {
foreach ($array as $item) {
if ($query($item)) {
$res = $item;
break;
}
}
}
return $res;
}
/**
* 合并对象数组并根据key去重
* @param string $name
* @param mixed ...$array
* @return array
*/
public static function arrayMergeAndUnique(string $key = "name", ...$array):array
{
$mergeArr = [];
foreach ($array as $k => $v) {
$mergeArr = array_merge($mergeArr, $v);
}
$keys = [];
foreach ($mergeArr as $k => $v) {
$keys[] = $v[$key];
}
$uniqueKeys = array_flip(array_flip($keys));
$newArray = [];
foreach ($uniqueKeys as $k => $v) {
$newArray[] = $mergeArr[$k];
}
return $newArray;
}
/**
* 初始化当前所选的应用/版本数据
* @param $appKey
*/
public function getCurrentApps(string $appKey):array
{
$config = Config::get("apidoc")?Config::get("apidoc"):Config::get("apidoc.");
if (!(!empty($config['apps']) && count($config['apps']) > 0)) {
throw new ErrorException("no config apps", 500);
}
if (strpos($appKey, '_') !== false) {
$keyArr = explode("_", $appKey);
} else {
$keyArr = [$appKey];
}
$currentApps = $this->getTreeNodesByKeys($config['apps'], $keyArr, 'folder', 'items');
if (!$currentApps) {
throw new ErrorException("appKey error", 412, [
'appKey' => $appKey
]);
}
return $currentApps;
}
/**
* 处理apps配置参数
* @param array $apps
* @return array
*/
public function handleAppsConfig(array $apps):array
{
$appsConfig = [];
foreach ($apps as $app) {
if (!empty($app['password'])) {
unset($app['password']);
$app['hasPassword'] = true;
}
if (!empty($app['items']) && count($app['items']) > 0) {
$app['items'] = $this->handleAppsConfig($app['items']);
}
$appsConfig[] = $app;
}
return $appsConfig;
}
/**
* 驼峰转下划线
*
* @param string $value
* @param string $delimiter
* @return string
*/
public static function snake(string $value, string $delimiter = '_'): string
{
$key = $value;
if (isset(static::$snakeCache[$key][$delimiter])) {
return static::$snakeCache[$key][$delimiter];
}
if (!ctype_lower($value)) {
$value = preg_replace('/\s+/u', '', $value);
$value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value));
}
return static::$snakeCache[$key][$delimiter] = $value;
}
/**
* 字符串转小写
*
* @param string $value
* @return string
*/
public static function lower(string $value): string
{
return mb_strtolower($value, 'UTF-8');
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 添加模型的字段
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class AddField extends Annotation
{
/**
* 字段名
* @var string
*/
public $name;
/**
* 类型
* @var string
*/
public $type = 'string';
/**
* 默认值
* @var string
*/
public $default;
/**
* 描述
* @var string
*/
public $desc;
/**
* 必须
* @var bool
*/
public $require = false;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 作者
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class Author extends Annotation
{}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 描述
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD","CLASS"})
*/
class Desc extends Annotation
{}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 指定获取模型的字段
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class Field extends Annotation
{}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 分组
* @package hg\apidoc\annotation
* @Annotation
* @Target({"CLASS"})
*/
class Group extends Annotation
{}

View File

@@ -0,0 +1,40 @@
<?php
namespace hg\apidoc\annotation;
/**
* 请求头
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class Header extends ParamBase
{
/**
* 必须
* @var bool
*/
public $require = false;
/**
* 类型
* @var string
*/
public $type;
/**
* 引入
* @var string
*/
public $ref;
/**
* 描述
* @var string
*/
public $desc;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* Url
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class Method extends Annotation
{}

View File

@@ -0,0 +1,27 @@
<?php
namespace hg\apidoc\annotation;
/**
* 请求参数
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD","ANNOTATION"})
*/
final class Param extends ParamBase
{
/**
* 必须
* @var bool
*/
public $require = false;
/**
* 引入
* @var string
*/
public $ref;
}

View File

@@ -0,0 +1,62 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
abstract class ParamBase extends Annotation
{
/**
* 类型
* @Enum({"string", "integer", "int", "boolean", "array", "double", "object", "tree", "file","float","date","time","datetime"})
* @var string
*/
public $type = 'string';
/**
* 默认值
* @var string
*/
public $default;
/**
* 描述
* @var string
*/
public $desc;
/**
* 为tree类型时指定children字段
* @var string
*/
public $childrenField = '';
/**
* 为tree类型时指定children字段说明
* @var string
*/
public $childrenDesc = 'children';
/**
* 为array类型时指定子节点类型
* @Enum({"string", "int", "boolean", "array", "object"})
* @var string
*/
public $childrenType = '';
/**
* 指定引入的字段
* @var string
*/
public $field;
/**
* 指定从引入中过滤的字段
* @var string
*/
public $withoutField;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 参数类型
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class ParamType extends Annotation
{}

View File

@@ -0,0 +1,34 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 返回参数
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD","ANNOTATION"})
*/
final class Returned extends ParamBase
{
/**
* 必须
* @var bool
*/
public $require = false;
/**
* 引入
* @var string
*/
public $ref;
/**
* 是否替换全局响应体中的参数
* @var bool
*/
public $replaceGlobal = false;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation\Enum;
final class Route extends Rule
{
/**
* 请求类型
* @Enum({"GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD"})
* @var string
*/
public $method = "GET";
}

View File

@@ -0,0 +1,77 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
abstract class Rule extends Annotation
{
/**
* @var string|array
*/
public $middleware;
/**
* 后缀
* @var string
*/
public $ext;
/**
* @var string
*/
public $deny_ext;
/**
* @var bool
*/
public $https;
/**
* @var string
*/
public $domain;
/**
* @var bool
*/
public $complete_match;
/**
* @var string|array
*/
public $cache;
/**
* @var bool
*/
public $ajax;
/**
* @var bool
*/
public $pjax;
/**
* @var bool
*/
public $json;
/**
* @var array
*/
public $filter;
/**
* @var array
*/
public $append;
public function getOptions()
{
return array_intersect_key(get_object_vars($this), array_flip([
'middleware', 'ext', 'deny_ext', 'https', 'domain', 'complete_match', 'cache', 'ajax', 'pjax', 'json', 'filter', 'append',
]));
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 排序
* @package hg\apidoc\annotation
* @Annotation
* @Target({"CLASS"})
*/
class Sort extends Annotation
{}

14
vendor/hg/apidoc/src/annotation/Tag.php vendored Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* Tag
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class Tag extends Annotation
{}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 标题
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD","CLASS"})
*/
class Title extends Annotation
{}

14
vendor/hg/apidoc/src/annotation/Url.php vendored Normal file
View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* Url
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class Url extends Annotation
{}

View File

@@ -0,0 +1,14 @@
<?php
namespace hg\apidoc\annotation;
use Doctrine\Common\Annotations\Annotation;
/**
* 排除模型的字段
* @package hg\apidoc\annotation
* @Annotation
* @Target({"METHOD"})
*/
class WithoutField extends Annotation
{}

62
vendor/hg/apidoc/src/config.php vendored Normal file
View File

@@ -0,0 +1,62 @@
<?php
return [
// 文档标题
'title' => 'APi接口文档',
// 文档描述
'desc' => '',
// 版权申明
'copyright' => 'Powered By hg-code',
// 默认作者
'default_author'=>'',
// 默认请求类型
'default_method'=>'GET',
// 设置应用/版本(必须设置)
'apps' => [
['title'=>'v1.0','path'=>'app\controller','folder'=>'v1'],
],
// 控制器分组
'groups' => [],
// 指定公共注释定义的文件地址
'definitions' => "app\controller\Definitions",
//指定生成文档的控制器
'controllers' => [],
// 过滤,不解析的控制器
'filter_controllers' => [],
// 缓存配置
'cache' => [
// 是否开启缓存
'enable' => false,
// 缓存文件路径
'path' => '../runtime/apidoc/',
// 是否显示更新缓存按钮
'reload' => true,
// 最大缓存文件数
'max' => 5, //最大缓存数量
],
// 权限认证配置
'auth' => [
// 是否启用密码验证
'enable' => false,
// 验证密码
'password' => "123456",
// 密码加密盐
'secret_key' => "apidoc#hg_code",
],
// 统一的请求Header
'headers'=>[],
// 统一的请求参数Parameters
'parameters'=>[],
// 统一的请求响应体,仅显示在文档提示中
'responses'=>[
['name'=>'code','desc'=>'状态码','type'=>'int'],
['name'=>'message','desc'=>'操作描述','type'=>'string'],
['name'=>'data','desc'=>'业务数据','main'=>true,'type'=>'object'],
],
// md文档
'docs' => [
'menu_title' => '开发文档',
'menus' => []
],
'crud'=>[]
];

383
vendor/hg/apidoc/src/crud/CreateCrud.php vendored Normal file
View File

@@ -0,0 +1,383 @@
<?php
namespace hg\apidoc\crud;
use hg\apidoc\exception\ErrorException;
use think\Db as Db5;
use think\facade\Config;
use think\facade\Db;
use think\facade\App;
use hg\apidoc\Utils;
class CreateCrud
{
protected $config;
protected $currentApps;
protected $controller_layer = "";
public function __construct()
{
$config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
$this->controller_layer = Config::get('route.controller_layer',"controller");
if (!empty($config) && !empty($config['crud'])){
$this->config = $config['crud'];
}else{
throw new ErrorException("no config crud",501);
}
}
/**
* 创建Crud
* @param $params
* @return array
*/
public function create($params){
$appKey = $params['appKey'];
$currentApps = (new Utils())->getCurrentApps($appKey);
// $currentApp = $currentApps[count($currentApps) - 1];
$data = $this->renderTemplateData($params,$currentApps);
$res = [];
// 创建数据表
if (!empty($params['model']['table'])){
$sqlRes = $this->createModelTable($params['model'],$params['title']);
if ($sqlRes == true){
$res[]="创建数据表成功";
}else{
// $msg= $sqlRes?$sqlRes:"数据表创建失败,请检查配置";
throw new ErrorException("datatable crud error",500);
}
}
// 生成文件
$keys = array_keys($this->config);
foreach ($keys as $index=>$key) {
// 添加路由
if (
$key==="route" &&
!empty($this->config['route']) &&
!empty($this->config['route']['template']) &&
!empty($this->config['route']['path'])
){
$tmp_path = (new Utils())->replaceCurrentAppTemplate($this->config['route']['template'],$currentApps);
$tempPath = $tmp_path.".txt";
$str_tmp = Utils::getFileContent($tempPath);
if (!empty($str_tmp)){
$tmp_content = Utils::replaceTemplate($str_tmp,$data);
$tmp_content = (new Utils())->replaceCurrentAppTemplate($tmp_content,$currentApps);
$routePathStr = (new Utils())->replaceCurrentAppTemplate($this->config['route']['path'],$currentApps);
$routePathStr = str_replace("\\","/",$routePathStr);
$routePath = App::getAppPath().$routePathStr;
$routeContent = Utils::getFileContent($routePath);
$routeContent.="\r\n".$tmp_content."\r\n";
Utils::createFile($routePath,$routeContent);
$res[]="添加路由成功";
}
}else{
$currentConfig = $this->config[$key];
$currentParam = $params[$key];
$tmp_path = (new Utils())->replaceCurrentAppTemplate($currentParam['template'],$currentApps);
$tempPath = $tmp_path.".txt";
$str_tmp = Utils::getFileContent($tempPath);
$file_content = Utils::replaceTemplate($str_tmp,$data);
$file_content = (new Utils())->replaceCurrentAppTemplate($file_content,$currentApps);
$namespacePath = str_replace("\\","/",$currentParam['path']);
$fileName = $data[$key.'.file_name'];
$filePath = '../'.$namespacePath.'/'.$fileName.'.php';
$fp=Utils::createFile($filePath,$file_content);
if ($fp){
$res[]="创建文件成功 path:".$filePath;
}
}
}
return $res;
}
/**
* 生成模板变量的数据
* @param $params
* @return array
*/
public function renderTemplateData($params,array $currentApps){
// 模板参数
$api_group = "";
if (!empty($params['group'])){
$api_group = '@Apidoc\Group("'.$params['group'].'")';
}
$data=[
'title'=>!empty($params['title'])?$params['title']:"",
'api_group'=>$api_group,
];
$keys = array_keys($this->config);
foreach ($keys as $index=>$key){
$currentConfig = $this->config[$key];
//验证模板是否存在
$tmp_path = (new Utils())->replaceCurrentAppTemplate($currentConfig['template'],$currentApps);
if(!file_exists($tmp_path.'.txt')){
throw new ErrorException("template not found",404,[
'template'=>$currentConfig['template']
]);
}
if ($key==="route"){
continue;
}
$currentParam = $params[$key];
if(!preg_match("/^[A-Z]{1}[A-Za-z0-9]{1,32}$/",$currentParam['class_name'])){
throw new ErrorException("classname error",412,[
'classname'=>$currentParam['class_name']
]);
}
$currentParamPath = str_replace("\\","/",$currentParam['path']);
// 验证目录是否存在
if(!is_dir('../'.$currentParamPath)){
throw new ErrorException("path not found",404,[
'path'=>$currentParamPath
]);
}
$appPath = App::getAppPath();
$appPathArr = explode("\\", $appPath);
$appFolder = $appPathArr[count($appPathArr)-1]?$appPathArr[count($appPathArr)-1]:$appPathArr[count($appPathArr)-2];
$namespace = str_replace($appFolder, App::getNamespace(), $currentParam['path']);
if ($key==="controller"){
$pathArr = explode("\\", $namespace);
$notArr = array(App::getNamespace(), $this->controller_layer);
$url = "/";
foreach ($pathArr as $pathItem){
if (!in_array($pathItem,$notArr)){
$url.=$pathItem."/";
}
}
$data['folder']=$url;
$data['api_class_name']=lcfirst($currentParam['class_name']);
$data['api_url']=$url.lcfirst($currentParam['class_name']);
}else if ($key==="model" && !empty($currentParam['table'])){
// 模型
// 获取主键
foreach ($currentParam['table'] as $item){
if ($item['main_key']==true){
$data['main_key.field'] = $item['field'];
$data['main_key.type'] = $item['type'];
$data['main_key.desc'] = $item['desc'];
break;
}
}
$tp_version = \think\facade\App::version();
if (substr($tp_version, 0, 2) == '5.') {
$table_prefix = Config::get('database.prefix');
}else{
$driver = Config::get('database.default');
$table_prefix = Config::get('database.connections.' . $driver . '.prefix');
}
$data[$currentParam['name'] . '.table_prefix'] = $table_prefix;
$table_name = Utils::snake($currentParam['class_name']);
$data[$currentParam['name'].'.table_name']=$table_name;
}
$namespace = str_replace($appFolder, App::getNamespace(), $currentParam['path']);
$data[$currentParam['name'].'.class_name']=$currentParam['class_name'];
$data[$currentParam['name'].'.namespace']=$namespace;
// 验证文件是否已存在
$fileName = $currentParam['class_name'];
if (!empty($currentConfig['file_name'])){
$fileName = (new Utils())->replaceTemplate($currentConfig['file_name'],$data);
}
$filePath = '../'.$currentParamPath.'/'.$fileName.'.php';
$data[$currentParam['name'].'.file_name']=$fileName;
if(file_exists($filePath)){
throw new ErrorException("file already exists",500,[
'filepath'=>$filePath
]);
}
$data[$currentParam['name'].'.use_path']=$namespace."\\".$fileName;
$data[$currentParam['name'].'.use_alias']=$fileName.ucwords($currentParam['name']);
}
// 字段过滤数据
if (!empty($params['model']['table'])){
$checkKeys = ['list','detail','add','edit'];
foreach ($checkKeys as $checkKey){
$itemArr = ['field'=>[],'withoutField'=>[]];
foreach ($params['model']['table'] as $item){
if ($item[$checkKey]){
$itemArr['field'][]=$item['field'];
}else{
$itemArr['withoutField'][]=$item['field'];
}
}
$data[$checkKey.'.field']=implode(",", $itemArr['field']);
$data[$checkKey.'.withoutField']=implode(",", $itemArr['withoutField']);
}
// 查询条件、验证规则
$query_where='$where=[];'."\r\n";
$query_annotation = "";
$validate_rule = "["."\r\n";
$validate_message = "["."\r\n";
$validate_fields = [];
$addRequireFields = [];
foreach ($params['model']['table'] as $i=>$item){
if ($item['query']){
$itemArr['field'][]=$item['field'];
$query_where.= ' if(!empty($param[\''.$item['field'].'\'])){'."\r\n";
$query_where.= ' $where[] = [\''.$item['field'].'\',\'=\',$param[\''.$item['field'].'\']];'."\r\n";
$query_where.= ' }'."\r\n";
$fh = empty($query_annotation)?'* ':' * ';
$require = $item['not_null']==true?'true':'false';
$rn="";
if (($i+1)<count($params['model']['table'])){
$rn="\r\n";
}
$query_annotation.=$fh.'@Apidoc\Param("'.$item['field'].'",type="'.$item['type'].'",require='.$require.',desc="'.$item['desc'].'")'.$rn;
}
// 验证规则
if (!empty($this->config['validate'])){
// 存在配置验证规则
if (!empty($item['validate']) && $this->config['validate']['rules']){
$rulesConfig = $this->config['validate']['rules'];
$currentRuleConfig = "";
foreach ($rulesConfig as $rule){
if ($rule['rule'] === $item['validate']){
$currentRuleConfig = $rule;
break;
}
}
if (!empty($currentRuleConfig)){
$validate_rule.=' \''.$item['field'].'\' => \''.$item['validate'].'\','."\r\n";
if (!empty($currentRuleConfig['message']) ){
if (is_array($currentRuleConfig['message']) && count($currentRuleConfig['message'])>0){
foreach ($currentRuleConfig['message'] as $ruleKey=>$ruleMessage){
if ($ruleKey=='0'){
$ruleKeyStr = $item['field'];
}else{
$ruleKeyStr = Utils::replaceTemplate($ruleKey,$item);
}
$ruleMessageStr = Utils::replaceTemplate($ruleMessage,$item);
$validate_message.=' \''.$ruleKeyStr.'\' => \''.$ruleMessageStr.'\','."\r\n";
}
}else{
$ruleMessageStr = Utils::replaceTemplate($currentRuleConfig['message'],$item);
$validate_message.=' \''.$item['field'].'\' => \''.$ruleMessageStr.'\','."\r\n";
}
}
$validate_fields[]=$item['field'];
if($item['field'] !== $data['main_key.field']){
$addRequireFields[]=$item['field'];
}
}
}else if($item['not_null']){
$validate_fields[]=$item['field'];
if($item['field'] !== $data['main_key.field']){
$addRequireFields[]=$item['field'];
}
// 根据not_null生成必填
$validate_rule.=' \''.$item['field'].'\' => \'require\','."\r\n";
$validate_message.=' \''.$item['field'].'\' => \''.$item['field'].'不可为空\','."\r\n";
}
}
}
$validate_rule.=' ];'."\r\n";
$validate_message.=' ];'."\r\n";
if (!empty($this->config['validate'])) {
$data['validate.rule'] = $validate_rule;
$data['validate.message'] = $validate_message;
$data['validate.scene.edit'] = json_encode($validate_fields);
$data['validate.scene.add'] = json_encode($addRequireFields)=='[]'?'[\'\']':json_encode($addRequireFields);
$data['validate.scene.delete'] = '[\'' . $data['main_key.field'] . '\']';
}
$data['query.where']=$query_where;
$data['query.annotation']=$query_annotation;
}
return $data;
}
/**
* 创建数据表
* @return mixed
*/
public function createModelTable($params,$title=""){
$data = $params['table'];
if (empty($title)){
$title =$params['class_name'];
}
$driver = Config::get('database.default');
$table_prefix=Config::get('database.connections.'.$driver.'.prefix');
$table_name = $table_prefix.Utils::snake($params['class_name']);
$table_data = '';
$main_keys = '';
foreach ($data as $item){
$table_field="`".$item['field']."` ".$item['type'];
if (!empty($item['length'])){
$table_field.="(".$item['length'].")";
}
if ($item['main_key']){
$main_keys.=$item['field'];
$table_field.=" NOT NULL";
}else if ($item['not_null']){
$table_field.=" NOT NULL";
}
if ($item['incremental']==true){
$table_field.=" AUTO_INCREMENT";
}
if (!empty($item['default']) || $item['default']=="0"){
$table_field.=" DEFAULT '".$item['default']."'";
}
$table_field.=" COMMENT '".$item['desc']."',";
$table_data.=$table_field;
}
$sql = "CREATE TABLE IF NOT EXISTS `$table_name` (
$table_data
PRIMARY KEY (`$main_keys`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='$title' AUTO_INCREMENT=1 ;";
$tp_version = \think\facade\App::version();
if (substr($tp_version, 0, 2) == '5.'){
Db5::startTrans();
try {
Db5::query($sql);
// 提交事务
Db5::commit();
return true;
} catch (\Exception $e) {
// 回滚事务
Db5::rollback();
return $e->getMessage();
}
}else{
Db::startTrans();
try {
Db::query($sql);
// 提交事务
Db::commit();
return true;
} catch (\Exception $e) {
// 回滚事务
Db::rollback();
return $e->getMessage();
}
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace hg\apidoc\exception;
use think\Exception;
use think\exception\HttpException;
class AuthException extends HttpException
{
protected $exceptions = [
'password error' => ['code' => 4001, 'msg' => '密码不正确,请重新输入'],
'password not found' => ['code' => 4002, 'msg' => '密码不可为空'],
'token error' => ['code' => 4003, 'msg' => '不合法的Token'],
'token not found' => ['code' => 4004, 'msg' => '不存在Token'],
];
public function __construct(string $exceptionCode)
{
$exception = $this->getException($exceptionCode);
parent::__construct(401, $exception['msg'], null, [], $exception['code']);
}
public function getException($exceptionCode)
{
if (isset($this->exceptions[$exceptionCode])) {
return $this->exceptions[$exceptionCode];
}
throw new Exception('exceptionCode "' . $exceptionCode . '" Not Found');
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace hg\apidoc\exception;
use hg\apidoc\Utils;
use think\Exception;
use think\exception\HttpException;
class ErrorException extends HttpException
{
protected $exceptions = [
'appkey not found' => ['code' => 4005, 'msg' => '缺少必要参数appKey'],
'mdPath not found' => ['code' => 4006, 'msg' => '缺少必要参数path'],
'appKey error' => ['code' => 4007, 'msg' => '不存在 folder为${appKey}的apps配置'],
'template not found' => ['code' => 4008, 'msg' => '${template}模板不存在'],
'path not found' => ['code' => 4009, 'msg' => '${path}目录不存在'],
'classname error' => ['code' => 4010, 'msg' => '${classname}文件名不合法'],
'no config apps' => ['code' => 5000, 'msg' => 'apps配置不可为空'],
'no debug' => ['code' => 5001, 'msg' => '请在debug模式下使用该功能'],
'no config crud' => ['code' => 5002, 'msg' => 'crud未配置'],
'datatable crud error' => ['code' => 5003, 'msg' => '数据表创建失败,请检查配置'],
'file already exists' => ['code' => 5004, 'msg' => '${filepath}文件已存在'],
];
public function __construct(string $exceptionCode, int $statusCode = 412, array $data = [])
{
$exception = $this->getException($exceptionCode);
$msg = Utils::replaceTemplate($exception['msg'], $data);
parent::__construct($statusCode, $msg, null, [], $exception['code']);
}
public function getException($exceptionCode)
{
if (isset($this->exceptions[$exceptionCode])) {
return $this->exceptions[$exceptionCode];
}
throw new Exception('exceptionCode "' . $exceptionCode . '" Not Found');
}
}

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use hg\apidoc\Utils;
use think\facade\Config;
class CacheApiData
{
protected $config = [];
public function __construct()
{
$this->config = Config::get('apidoc');
}
/**
* 获取缓存目录
* @param string $appKey
* @return string
*/
protected function getCacheFolder(string $appKey):string
{
$config = $this->config;
$currentApps = (new Utils())->getCurrentApps($appKey);
$configPath = !empty($config['cache']) && !empty($config['cache']['path']) ? $config['cache']['path'] : '../runtime/apidoc/';
$cacheAppFolder = "";
if (!empty($currentApps) && count($currentApps) > 0) {
foreach ($currentApps as $keyIndex => $appItem) {
$cacheAppFolder .= $appItem['folder'] . "/";
}
}
$cacheFolder = $configPath . $cacheAppFolder;
return $cacheFolder;
}
/**
* 获取指定目录下缓存文件名列表
* @param string $folder
* @return array
*/
public function getCacheFileList(string $folder):array
{
$filePaths = glob($folder . '*.json');
$cacheFiles = [];
if (count($filePaths) > 0) {
foreach ($filePaths as $item) {
$cacheFiles[] = str_replace(".json", "", basename($item));
}
}
return $cacheFiles;
}
/**
* 获取接口缓存数据
* @param string $appKey
* @param string $cacheFileName
* @return array|false
*/
public function get(string $appKey, string $cacheFileName)
{
$cacheFolder = $this->getCacheFolder($appKey);
$cacheFileList = $this->getCacheFileList($cacheFolder);
if (!file_exists($cacheFolder)) {
return false;
}
if (empty($cacheFileName) && count($cacheFileList) > 0) {
// 默认最后一个缓存文件
$cacheFileName = $cacheFileList[count($cacheFileList) - 1];
}
$cacheFilePath = $cacheFolder . "/" . $cacheFileName . '.json';
if (file_exists($cacheFilePath)) {
// 存在缓存文件
$fileContent = file_get_contents($cacheFilePath);
if (empty($fileContent)) {
return false;
}
$json = json_decode($fileContent);
if (is_object($json)) {
$json = [
"data" => $json->data,
"tags" => $json->tags,
"groups" => $json->groups,
];
}
return [
'name' => $cacheFileName,
'data' => $json,
'list' => $cacheFileList
];
}
return false;
}
/**
* 设置接口缓存
* @param string $appKey
* @param array $json
* @return array|false
*/
public function set(string $appKey, array $json):array
{
if (empty($json)) {
return false;
}
$config = $this->config;
$fileName = date("Y-m-d H_i_s");
$fileContent = json_encode($json);
$cacheFolder = $this->getCacheFolder($appKey);
$path = $cacheFolder . $fileName . ".json";
Utils::createFile($path, $fileContent);
$filePaths = $this->getCacheFileList($cacheFolder);
if ($config['cache']['max'] && count($filePaths) >= $config['cache']['max']) {
//达到最大数量,删除第一个
$filePath = $cacheFolder . $filePaths[0] . ".json";
Utils::delFile($filePath);
}
return [
"name" => $fileName,
"data" => $json,
"list" => $this->getCacheFileList($cacheFolder)
];
}
}

View File

@@ -0,0 +1,663 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use Doctrine\Common\Annotations\AnnotationReader;
use hg\apidoc\Utils;
use ReflectionClass;
use Symfony\Component\ClassLoader\ClassMapGenerator;
use think\annotation\route\Resource;
use think\annotation\Route;
use hg\apidoc\annotation\Group;
use hg\apidoc\annotation\Sort;
use hg\apidoc\annotation\Param;
use hg\apidoc\annotation\Title;
use hg\apidoc\annotation\Desc;
use hg\apidoc\annotation\Author;
use hg\apidoc\annotation\Tag;
use hg\apidoc\annotation\Header;
use hg\apidoc\annotation\Returned;
use hg\apidoc\annotation\ParamType;
use hg\apidoc\annotation\Url;
use hg\apidoc\annotation\Method;
use think\annotation\route\Group as RouteGroup;
use think\facade\App;
use think\facade\Config;
class ParseAnnotation
{
protected $config = [];
protected $reader;
//tags当前应用/版本所有的tag
protected $tags = array();
//groups,当前应用/版本的分组name
protected $groups = array();
protected $controller_layer = "";
public function __construct()
{
$this->reader = new AnnotationReader();
$this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
$this->controller_layer = Config::get('route.controller_layer',"controller");
}
/**
* 生成api接口数据
* @param string $appKey
* @return array
*/
public function renderApiData(string $appKey): array
{
$config = $this->config;
$currentApps = (new Utils())->getCurrentApps($appKey);
$currentApp = $currentApps[count($currentApps) - 1];
if (!empty($config['controllers']) && count($config['controllers']) > 0) {
// 配置的控制器列表
$controllers = $this->getConfigControllers($currentApp['path']);
} else {
// 默认读取所有的
$controllers = $this->getDirControllers($currentApp['path']);
}
$apiData = [];
if (!empty($controllers) && count($controllers) > 0) {
foreach ($controllers as $class) {
$classData = $this->parseController($class);
if ($classData !== false) {
$apiData[] = $classData;
}
}
}
$json = array(
"data" => $apiData,
"tags" => $this->tags,
"groups" => $this->groups
);
return $json;
}
/**
* 获取生成文档的控制器列表
* @param string $path
* @return array
*/
protected function getConfigControllers(string $path): array
{
$config = $this->config;
$controllers = [];
$configControllers = $config['controllers'];
if (!empty($configControllers) && count($configControllers) > 0) {
foreach ($configControllers as $item) {
if ( strpos($item, $path) !== false && class_exists($item)) {
$controllers[] = $item;
}
}
}
return $controllers;
}
/**
* 获取目录下的控制器列表
* @param string $path
* @return array
*/
protected function getDirControllers(string $path): array
{
if ($path) {
if (strpos(App::getRootPath(), '/') !== false) {
$pathStr = str_replace("\\", "/", $path);
} else {
$pathStr = $path;
}
$dir = App::getRootPath() . $pathStr;
} else {
$dir = App::getRootPath() . $this->controller_layer;
}
$controllers = [];
if (is_dir($dir)) {
$controllers = $this->scanDir($dir, $path);
}
return $controllers;
}
/**
* 处理指定目录下的控制器
* @param string $dir
* @param string $appPath
* @return array
*/
protected function scanDir(string $dir, string $appPath): array
{
$list = [];
foreach (ClassMapGenerator::createMap($dir) as $class => $path) {
if (
!isset($this->config['filter_controllers']) ||
(isset($this->config['filter_controllers']) && !in_array($class, $this->config['filter_controllers'])) &&
$this->config['definitions'] != $class
) {
if (strpos($class, '\\') === false) {
$list[] = $appPath . "\\" . $class;
} else {
$list[] = $class;
}
}
}
return $list;
}
protected function parseController($class)
{
$config = $this->config;
$data = [];
$refClass = new ReflectionClass($class);
$classTextAnnotations = $this->parseTextAnnotation($refClass);
if (in_array("NotParse", $classTextAnnotations)) {
return false;
}
$title = $this->reader->getClassAnnotation($refClass, Title::class);
$group = $this->reader->getClassAnnotation($refClass, Group::class);
$sort = $this->reader->getClassAnnotation($refClass, Sort::class);
$routeGroup = $this->reader->getClassAnnotation($refClass, RouteGroup::class);
$controllersNameArr = explode("\\", $class);
$controllersName = $controllersNameArr[count($controllersNameArr) - 1];
$data['controller'] = $controllersName;
$data['group'] = !empty($group->value) ? $group->value : null;
$data['sort'] = !empty($sort->value) ? $sort->value : null;
if (!empty($data['group']) && !in_array($data['group'], $this->groups)) {
$this->groups[] = $data['group'];
}
$data['title'] = !empty($title) && !empty($title->value) ? $title->value : "";
if (empty($title)) {
if (!empty($classTextAnnotations) && count($classTextAnnotations) > 0) {
$data['title'] = $classTextAnnotations[0];
} else {
$data['title'] = $controllersName;
}
}
$methodList = [];
$filter_method = !empty($config['filter_method']) ? $config['filter_method'] : [];
$data['menu_key'] = $data['controller'] . "_" . mt_rand(10000, 99999);
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) {
if (!empty($refMethod->name) && !in_array($refMethod->name, $filter_method)) {
$methodItem = $this->parseAnnotation($refMethod, true,"controller");
if (!count((array)$methodItem)) {
continue;
}
$textAnnotations = $this->parseTextAnnotation($refMethod);
// 标注不解析的方法
if (in_array("NotParse", $textAnnotations)) {
continue;
}
// 无标题,且有文本注释
if (empty($methodItem['title']) && !empty($textAnnotations) && count($textAnnotations) > 0) {
$methodItem['title'] = $textAnnotations[0];
}
// 添加统一headers请求头参数
if (!empty($config['headers']) && !in_array("NotHeaders", $textAnnotations)) {
if (!empty($methodItem['header'])) {
$methodItem['header'] = Utils::arrayMergeAndUnique("name", $config['headers'], $methodItem['header']);
} else {
$methodItem['header'] = $config['headers'];
}
}
// 添加统一params请求参数
if (!empty($config['parameters']) && !in_array("NotParameters", $textAnnotations)) {
if (!empty($methodItem['param'])) {
$methodItem['param'] = Utils::arrayMergeAndUnique("name", $config['parameters'], $methodItem['param']);
} else {
$methodItem['param'] = $config['parameters'];
}
}
// 添加responses统一响应体
if (
!empty($config['responses']) &&
!is_string($config['responses']) &&
!in_array("NotResponses", $textAnnotations)
) {
// 显示在响应体中
$returned = [];
$hasMian = false;
if (isset($config['responses']['data']) && !$config['responses']['show_responses']) {
$responsesData = [];
} else if (isset($config['responses']['data']) && $config['responses']['show_responses'] === true) {
$responsesData = $config['responses']['data'];
} else {
$responsesData = $config['responses'];
}
// 合并统一响应体及响应参数相同的字段
$returnData = [];
$resKeys = [];
foreach ($responsesData as $resItem) {
$resKeys[]=$resItem['name'];
}
foreach ($methodItem['return'] as $returnItem){
if (!(in_array($returnItem['name'],$resKeys) && $returnItem['source']==='controller' && !empty($returnItem['replaceGlobal']))){
$returnData[]=$returnItem;
}
}
foreach ($responsesData as $resItem) {
$resData = $resItem;
$globalFind = Utils::getArrayFind($methodItem['return'],function ($item)use ($resItem){
if ($item['name'] == $resItem['name'] && $item['source']==='controller' && !empty($item['replaceGlobal'])){
return true;
}
return false;
});
if (!empty($globalFind)){
$resData = array_merge($resItem,$globalFind);
}else if (!empty($resData['main']) && $resData['main'] === true) {
$resData['params'] = $returnData;
$resData['resKeys']=$resKeys;
$hasMian = true;
}
$resData['find'] =$globalFind;
$returned[] = $resData;
}
if (!$hasMian) {
$returned = Utils::arrayMergeAndUnique("name", $returned, $methodItem['return']);
}
$methodItem['return'] = $returned;
}
// 默认method
if (empty($methodItem['method'])) {
$methodItem['method'] = !empty($config['default_method']) ? $config['default_method'] : 'GET';
}
// 默认default_author
if (empty($methodItem['author']) && !empty($config['default_author']) && !in_array("NotDefaultAuthor", $textAnnotations)) {
$methodItem['author'] = $config['default_author'];
}
// Tags
if (!empty($methodItem['tag'])) {
if (strpos($methodItem['tag'], ' ') !== false) {
$tagArr = explode(" ", $methodItem['tag']);
foreach ($tagArr as $tag) {
if (!in_array($tag, $this->tags)) {
$this->tags[] = $tag;
}
}
} else if (!in_array($methodItem['tag'], $this->tags)) {
$this->tags[] = $methodItem['tag'];
}
}
// 无url,自动生成
if (empty($methodItem['url'])) {
$methodItem['url'] = $this->autoCreateUrl($refMethod);
} else if (!empty($routeGroup->value)) {
// 路由分组url加上分组
$methodItem['url'] = '/' . $routeGroup->value . '/' . $methodItem['url'];
}
if (!empty($methodItem['url']) && substr($methodItem['url'], 0, 1) != "/") {
$methodItem['url'] = "/" . $methodItem['url'];
}
$methodItem['name'] = $refMethod->name;
$methodItem['menu_key'] = $methodItem['method'] . "_" . $refMethod->name . "_" . mt_rand(10000, 99999);
$methodList[] = $methodItem;
}
}
$data['children'] = $methodList;
return $data;
}
/**
* 自动生成url
* @param $method
* @return string
*/
protected function autoCreateUrl($method): string
{
if (Config::get("controller_auto_search") || !empty($this->config['controller_auto_search'])){
$pathArr = explode("\\", $method->class );
}else{
$searchString = $this->controller_layer . '\\';
$substr = substr(strstr($method->class, $searchString), strlen($searchString));
$pathArr = explode("\\", str_replace($substr, str_replace('\\', '.', $substr), $method->class));
}
// 控制器地址
$filterPathNames = array(App::getNamespace(), $this->controller_layer);
$classPathArr = [];
foreach ($pathArr as $item) {
if (!in_array($item, $filterPathNames)) {
if (!empty($this->config['auto_url_rule'])){
switch ($this->config['auto_url_rule']) {
case 'lcfirst':
$classPathArr[] = lcfirst($item);
break;
case 'ucfirst':
$classPathArr[] = ucfirst($item);
break;
default:
$classPathArr[] = $item;
}
}else{
$classPathArr[] = $item;
}
}
}
$classPath = implode('/', $classPathArr);
return '/' . $classPath . '/' . $method->name;
}
/**
* ref引用
* @param $refPath
* @param bool $enableRefService
* @return false|string[]
*/
protected function renderRef(string $refPath, bool $enableRefService = true): array
{
$res = ['type' => 'model'];
// 通用定义引入
if (strpos($refPath, '\\') === false) {
$config = $this->config;
$refPath = $config['definitions'] . '\\' . $refPath;
$data = $this->renderService($refPath);
$res['type'] = "service";
$res['data'] = $data;
return $res;
}
// 模型引入
$modelData = (new ParseModel($this->reader))->renderModel($refPath);
if ($modelData !== false) {
$res['data'] = $modelData;
return $res;
}
if ($enableRefService === false) {
return false;
}
$data = $this->renderService($refPath);
$res['type'] = "service";
$res['data'] = $data;
return $res;
}
/**
* 解析注释引用
* @param $refPath
* @return array
* @throws \ReflectionException
*/
protected function renderService(string $refPath)
{
$pathArr = explode("\\", $refPath);
$methodName = $pathArr[count($pathArr) - 1];
unset($pathArr[count($pathArr) - 1]);
$classPath = implode("\\", $pathArr);
$classReflect = new \ReflectionClass($classPath);
$methodName = trim($methodName);
$refMethod = $classReflect->getMethod($methodName);
$res = $this->parseAnnotation($refMethod, true);
return $res;
}
/**
* 处理Param/Returned的字段名name、params子级参数
* @param $values
* @return array
*/
protected function handleParamValue($values, string $field = 'param'): array
{
$name = "";
$params = [];
if (!empty($values) && is_array($values) && count($values) > 0) {
foreach ($values as $item) {
if (is_string($item)) {
$name = $item;
} else if (is_object($item)) {
if (!empty($item->ref)) {
$refRes = $this->renderRef($item->ref, true);
$params = $this->handleRefData($params, $refRes, $item, $field);
} else {
$param = [
"name" => "",
"type" => $item->type,
"desc" => $item->desc,
"default" => $item->default,
"require" => $item->require,
"childrenType"=> $item->childrenType
];
$children = $this->handleParamValue($item->value);
$param['name'] = $children['name'];
if (count($children['params']) > 0) {
$param['params'] = $children['params'];
}
$params[] = $param;
}
}
}
} else {
$name = $values;
}
return ['name' => $name, 'params' => $params];
}
/**
* 解析方法注释
* @param $refMethod
* @param bool $enableRefService 是否终止service的引入
* @param string $source 注解来源
* @return array
*/
protected function parseAnnotation($refMethod, bool $enableRefService = true,$source=""): array
{
$data = [];
if ($annotations = $this->reader->getMethodAnnotations($refMethod)) {
$headers = [];
$params = [];
$returns = [];
foreach ($annotations as $annotation) {
switch (true) {
case $annotation instanceof Param:
$params = $this->handleParamAndReturned($params,$annotation,'param',$enableRefService);
break;
case $annotation instanceof Returned:
$returns = $this->handleParamAndReturned($returns,$annotation,'return',$enableRefService,$source);
break;
case $annotation instanceof Header:
if (!empty($annotation->ref)) {
$refRes = $this->renderRef($annotation->ref, $enableRefService);
$headers = $this->handleRefData($headers, $refRes, $annotation, 'header');
} else {
$param = [
"name" => $annotation->value,
"desc" => $annotation->desc,
"require" => $annotation->require,
"type" => $annotation->type,
"default" => $annotation->default,
];
$headers[] = $param;
}
break;
case $annotation instanceof Route:
if (empty($data['method'])) {
$data['method'] = $annotation->method;
}
if (empty($data['url'])) {
$data['url'] = $annotation->value;
}
break;
case $annotation instanceof Author:
$data['author'] = $annotation->value;
break;
case $annotation instanceof Title:
$data['title'] = $annotation->value;
break;
case $annotation instanceof Desc:
$data['desc'] = $annotation->value;
break;
case $annotation instanceof ParamType:
$data['paramType'] = $annotation->value;
break;
case $annotation instanceof Url:
$data['url'] = $annotation->value;
break;
case $annotation instanceof Method:
$data['method'] = $annotation->value;
break;
case $annotation instanceof Tag:
$data['tag'] = $annotation->value;
break;
}
}
if ($headers && count($headers) > 0) {
$data['header'] = $headers;
}
$data['param'] = $params;
$data['return'] = $returns;
}
return $data;
}
/**
* 处理请求参数与返回参数
* @param $params
* @param $annotation
* @param string $type
* @param false $enableRefService
* @param string $source 注解来源
* @return array
*/
protected function handleParamAndReturned($params,$annotation,$type="param",$enableRefService=false,$source=""){
if (!empty($annotation->ref)) {
$refRes = $this->renderRef($annotation->ref, $enableRefService);
$params = $this->handleRefData($params, $refRes, $annotation, $type,$source);
} else {
$param = [
"name" => "",
"type" => $annotation->type,
"desc" => $annotation->desc,
"default" => $annotation->default,
"require" => $annotation->require,
"childrenType" => $annotation->childrenType,
"source" => $source,
"replaceGlobal" =>!empty($annotation->replaceGlobal)?$annotation->replaceGlobal:""
];
$children = $this->handleParamValue($annotation->value, $type);
$param['name'] = $children['name'];
if (count($children['params']) > 0) {
$param['params'] = $children['params'];
if ($annotation->type === 'tree' && !empty($annotation->childrenField)) {
// 类型为tree的
$param['params'][] = [
'params' => $children['params'],
'name' => $annotation->childrenField,
'type' => 'array',
'desc' => $annotation->childrenDesc,
];
}
}
// 合并同级已有的字段
$params = Utils::arrayMergeAndUnique("name", $params, [$param]);
}
return $params;
}
/**
* 解析非注解文本注释
* @param $refMethod
* @return array|false
*/
protected function parseTextAnnotation($refMethod): array
{
$annotation = $refMethod->getDocComment();
if (empty($annotation)) {
return [];
}
if (preg_match('#^/\*\*(.*)\*/#s', $annotation, $comment) === false)
return [];
$comment = trim($comment [1]);
if (preg_match_all('#^\s*\*(.*)#m', $comment, $lines) === false)
return [];
$data = [];
foreach ($lines[1] as $line) {
$line = trim($line);
if (!empty ($line) && strpos($line, '@') !== 0) {
$data[] = $line;
}
}
return $data;
}
/**
* 处理param、returned 参数
* @param $params
* @param $refRes
* @param $annotation
* @param string|null $source 注解来源
* @return array
*/
protected function handleRefData($params, $refRes, $annotation, string $field,$source=""): array
{
if ($refRes['type'] === "model" && count($refRes['data']) > 0) {
// 模型引入
$data = $refRes['data'];
} else if ($refRes['type'] === "service" && !empty($refRes['data']) && !empty($refRes['data'][$field])) {
// service引入
$data = $refRes['data'][$field];
} else {
return $params;
}
// 过滤field
if (!empty($annotation->field)) {
$data = (new Utils())->filterParamsField($data, $annotation->field, 'field');
}
// 过滤withoutField
if (!empty($annotation->withoutField)) {
$data = (new Utils())->filterParamsField($data, $annotation->withoutField, 'withoutField');
}
if (!empty($annotation->value)) {
$item = [
'name' => $annotation->value,
'desc' => $annotation->desc,
'type' => $annotation->type,
'require' => $annotation->require,
'default' => $annotation->default,
'params' => $data,
'source'=>$source,
"replaceGlobal" =>!empty($annotation->replaceGlobal)?$annotation->replaceGlobal:""
];
$children = $this->handleParamValue($annotation->value, 'param');
$item['name'] = $children['name'];
if (count($children['params']) > 0) {
$item['params'] = Utils::arrayMergeAndUnique("name",$data,$children['params']);
if ($annotation->type === 'tree' && !empty($annotation->childrenField)) {
// 类型为tree的
$item['params'][] = [
'params' => $item['params'],
'name' => $annotation->childrenField,
'type' => 'array',
'desc' => $annotation->childrenDesc,
];
}
}
$params[] = $item;
} else {
$params = array_merge($params, $data);
}
return $params;
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use think\facade\App;
use hg\apidoc\Utils;
use think\facade\Config;
class ParseMarkdown
{
protected $config = [];
public function __construct()
{
$this->config = Config::get('apidoc')?Config::get('apidoc'):Config::get('apidoc.');
}
/**
* 获取md文档菜单
* @return array
*/
public function getDocsMenu(): array
{
$config = $this->config;
$docData = [];
if (!empty($config['docs']) && !empty($config['docs']['menus']) && count($config['docs']['menus']) > 0) {
$docData = $this->handleDocsMenuData($config['docs']['menus']);
}
return $docData;
}
/**
* 处理md文档菜单数据
* @param array $menus
* @return array
*/
protected function handleDocsMenuData(array $menus): array
{
$list = [];
foreach ($menus as $item) {
if (!empty($item['items']) && count($item['items']) > 0) {
$item['items'] = $this->handleDocsMenuData($item['items']);
$item['group'] = 'markdown_doc';
$item['menu_key'] = "md_group_" . mt_rand(10000, 99999);
$list[] = $item;
} else {
$item['type'] = 'md';
$item['menu_key'] = "md_" . mt_rand(10000, 99999);
$list[] = $item;
}
}
return $list;
}
/**
* 获取md文档内容
* @param string $appKey
* @param string $path
* @return string
*/
public function getContent(string $appKey, string $path): string
{
$currentApps = (new Utils())->getCurrentApps($appKey);
$mdPath = (new Utils())->replaceCurrentAppTemplate($path, $currentApps);
$filePath = App::getRootPath() . $mdPath . '.md';
$contents = Utils::getFileContent($filePath);
return $contents;
}
}

View File

@@ -0,0 +1,236 @@
<?php
declare(strict_types = 1);
namespace hg\apidoc\parseApi;
use Doctrine\Common\Annotations\Reader;
use think\Db as Db5;
use think\facade\Db;
use hg\apidoc\annotation\Field;
use hg\apidoc\annotation\WithoutField;
use hg\apidoc\annotation\AddField;
use think\helper\Str;
use hg\apidoc\Utils;
class ParseModel
{
protected $reader;
public function __construct(Reader $reader)
{
$this->reader = $reader;
}
/**
* 生成模型数据
* @param string $path
* @return array|false
* @throws \ReflectionException
*/
public function renderModel(string $path)
{
$modelClassArr = explode("\\", $path);
$modelActionName = $modelClassArr[count($modelClassArr) - 1];
$modelClassName = $modelClassArr[count($modelClassArr) - 2];
unset($modelClassArr[count($modelClassArr) - 1]);
$modelClassPath = implode("\\", $modelClassArr);
$classReflect = new \ReflectionClass($modelClassPath);
$modelActionName = trim($modelActionName);
$methodAction = $classReflect->getMethod($modelActionName);
// 获取所有模型属性
$propertys = $classReflect->getDefaultProperties();
// 获取表字段
$model = $this->getModel($methodAction, $modelClassName);
if (!is_callable(array($model, 'getTable'))) {
return false;
}
$table = $this->getTableDocument($model, $propertys);
// 模型注释-field
if ($fieldAnnotations = $this->reader->getMethodAnnotation($methodAction, Field::class)) {
$table = (new Utils())->filterParamsField($table, $fieldAnnotations->value, 'field');
}
// 模型注释-withoutField
if ($fieldAnnotations = $this->reader->getMethodAnnotation($methodAction, WithoutField::class)) {
$table = (new Utils())->filterParamsField($table, $fieldAnnotations->value, 'withoutField');
}
// 模型注释-addField
if ($annotations = $this->reader->getMethodAnnotations($methodAction)) {
foreach ($annotations as $annotation) {
switch (true) {
case $annotation instanceof AddField:
$param = [
"name" => "",
"desc" => $annotation->desc,
"require" => $annotation->require,
"type" => $annotation->type,
"default" => $annotation->default
];
$children = $this->handleParamValue($annotation->value);
$param['name'] = $children['name'];
if (count($children['params']) > 0) {
$param['params'] = $children['params'];
}
$isExists = false;
$newTable = [];
foreach ($table as $item) {
if ($param['name'] === $item['name']) {
$isExists = true;
$newTable[] = $param;
} else {
$newTable[] = $item;
}
}
$table = $newTable;
if (!$isExists) {
$table[] = $param;
}
break;
}
}
}
return $table;
}
/**
* 处理字段参数
* @param $values
* @return array
*/
protected function handleParamValue($values): array
{
$name = "";
$params = [];
if (!empty($values) && is_array($values) && count($values) > 0) {
foreach ($values as $item) {
if (is_string($item)) {
$name = $item;
} else if (is_object($item)) {
$param = [
"name" => "",
"type" => $item->type,
"desc" => $item->desc,
"default" => $item->default,
"require" => $item->require,
];
$children = $this->handleParamValue($item->value);
$param['name'] = $children['name'];
if (count($children['params']) > 0) {
$param['params'] = $children['params'];
}
$params[] = $param;
}
}
} else {
$name = $values;
}
return ['name' => $name, 'params' => $params];
}
/**
* 获取模型实例
* @param $method
* @param $modelClassName
* @return mixed|null
*/
public function getModel($method, $modelClassName)
{
if (!empty($method->class)) {
$relationModelClass = $this->getIncludeClassName($method->class, $modelClassName);
if ($relationModelClass) {
$modelInstance = new $relationModelClass();
return $modelInstance;
} else {
return null;
}
} else {
return null;
}
}
/**
* 获取模型类
* @param $mainClass
* @param $class
* @return string
* @throws \ReflectionException
*/
protected function getIncludeClassName($mainClass, $class)
{
$classReflect = new \ReflectionClass($mainClass);
$possibleClass = $classReflect->getNamespaceName() . "\\" . $class;
if (class_exists($possibleClass)) {
return $possibleClass;
} else {
return "";
}
}
/**
* 获取模型注解数据
* @param $model
* @param $propertys
* @return array
*/
public function getTableDocument($model,array $propertys):array
{
$tp_version = \think\facade\App::version();
if (substr($tp_version, 0, 2) == '5.'){
$createSQL = Db5::query("show create table " . $model->getTable())[0]['Create Table'];
}else{
$createSQL = Db::query("show create table " . $model->getTable())[0]['Create Table'];
}
// $createSQL = Db::query("show create table " . $model->getTable())[0]['Create Table'];
preg_match_all("#[^KEY]`(.*?)` (.*?) (.*?),\n#", $createSQL, $matches);
$fields = $matches[1];
$types = $matches[2];
$contents = $matches[3];
$fieldComment = [];
//组织注释
for ($i = 0; $i < count($matches[0]); $i++) {
$key = $fields[$i];
$type = $types[$i];
$default = "";
$require = "0";
$desc = "";
$content = $contents[$i];
if (strpos($type, '(`') !== false) {
continue;
}
if (strpos($content, 'COMMENT') !== false) {
// 存在字段注释
preg_match_all("#COMMENT\s*'(.*?)'#", $content, $edscs);
if (!empty($edscs[1]) && !empty($edscs[1][0]))
$desc = $edscs[1][0];
}
if (strpos($content, 'DEFAULT') !== false) {
// 存在字段默认值
preg_match_all("#DEFAULT\s*'(.*?)'#", $content, $defaults);
$default = $defaults[1] && is_array($defaults[1])?$defaults[1][0]:"";
}
if (strpos($content, 'NOT NULL') !== false) {
// 必填字段
$require = "1";
}
$name = $key;
// 转换字段名为驼峰命名(用于输出)
if (isset($propertys['convertNameToCamel']) && $propertys['convertNameToCamel'] === true) {
$name = Str::camel($key);
}
$fieldComment[] = [
"name" => $name,
"type" => $type,
"desc" => $desc,
"default" => $default,
"require" => $require,
];
}
return $fieldComment;
}
}

3
vendor/itxq/api-doc-php/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/.idea
/vendor/
/composer.lock

191
vendor/itxq/api-doc-php/LICENSE vendored Normal file
View File

@@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright
owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities
that control, are controlled by, or are under common control with that entity.
For the purposes of this definition, "control" means (i) the power, direct or
indirect, to cause the direction or management of such entity, whether by
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising
permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including
but not limited to software source code, documentation source, and configuration
files.
"Object" form shall mean any form resulting from mechanical transformation or
translation of a Source form, including but not limited to compiled object code,
generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made
available under the License, as indicated by a copyright notice that is included
in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that
is based on (or derived from) the Work and for which the editorial revisions,
annotations, elaborations, or other modifications represent, as a whole, an
original work of authorship. For the purposes of this License, Derivative Works
shall not include works that remain separable from, or merely link (or bind by
name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version
of the Work and any modifications or additions to that Work or Derivative Works
thereof, that is intentionally submitted to Licensor for inclusion in the Work
by the copyright owner or by an individual or Legal Entity authorized to submit
on behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems, and
issue tracking systems that are managed by, or on behalf of, the Licensor for
the purpose of discussing and improving the Work, but excluding communication
that is conspicuously marked or otherwise designated in writing by the copyright
owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work or a
Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of
this License; and
You must cause any modified files to carry prominent notices stating that You
changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute,
all copyright, patent, trademark, and attribution notices from the Source form
of the Work, excluding those notices that do not pertain to any part of the
Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any
Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those notices
that do not pertain to any part of the Derivative Works, in at least one of the
following places: within a NOTICE text file distributed as part of the
Derivative Works; within the Source form or documentation, if provided along
with the Derivative Works; or, within a display generated by the Derivative
Works, if and wherever such third-party notices normally appear. The contents of
the NOTICE file are for informational purposes only and do not modify the
License. You may add Your own attribution notices within Derivative Works that
You distribute, alongside or as an addendum to the NOTICE text from the Work,
provided that such additional attribution notices cannot be construed as
modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate
notice, with the fields enclosed by brackets "{}" replaced with your own
identifying information. (Don't include the brackets!) The text should be
enclosed in the appropriate comment syntax for the file format. We also
recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
Copyright 2018 IT小强xqitw.cn
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

49
vendor/itxq/api-doc-php/README.md vendored Normal file
View File

@@ -0,0 +1,49 @@
# Api-Doc-PHP
### 主要功能:
+ 根据接口注释自动生成接口文档
### 演示地址
[【Gitee Pages:】http://itxq.gitee.io/api-doc-php](http://itxq.gitee.io/api-doc-php)
### 开源地址:
[【GigHub:】https://github.com/itxq/api-doc-php](https://github.com/itxq/api-doc-php)
[【码云:】https://gitee.com/itxq/api-doc-php](https://github.com/itxq/api-doc-php)
### 扩展安装:
+ 方法一composer命令 `composer require itxq/api-doc-php`
+ 方法二:直接下载压缩包,然后进入项目中执行 composer命令 `composer update` 来生成自动加载文件
### 引用扩展:
+ 当你的项目不支持composer自动加载时可以使用以下方式来引用该扩展包
```
// 引入扩展(具体路径请根据你的目录结构自行修改)
require_once __DIR__ . '/vendor/autoload.php';
```
### 使用扩展:
```
// 引入扩展(具体路径请根据你的目录结构自行修改)
require_once __DIR__ . '/../vendor/autoload.php';
// 加载测试API类1
require_once __DIR__ . '/Api.php';
// 加载测试API类2
require_once __DIR__ . '/Api2.php';
$config = [
'class' => ['Api', 'Api2'], // 要生成文档的类
'filter_method' => ['__construct'], // 要过滤的方法名称
];
$api = new \itxq\apidoc\BootstrapApiDoc($config);
$doc = $api->getHtml();
exit($doc);
```
### 具体效果可运行test目录下的`index.php`查看

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

20
vendor/itxq/api-doc-php/composer.json vendored Normal file
View File

@@ -0,0 +1,20 @@
{
"name" : "itxq/api-doc-php",
"description": "api-doc-php",
"type" : "library",
"license" : "Apache-2.0",
"authors" : [
{
"name" : "IT小强xqitw.cn",
"email": "360237521@qq.com"
}
],
"require" : {
"php": ">=5.6.0"
},
"autoload" : {
"psr-4": {
"itxq\\apidoc\\": "src"
}
}
}

126
vendor/itxq/api-doc-php/src/ApiDoc.php vendored Normal file
View File

@@ -0,0 +1,126 @@
<?php
/**
* ==================================================================
* 文 件 名: ApiDoc.php
* 概 要: ApiDoc生成
* 作 者: IT小强
* 创建时间: 2018/6/5 9:40
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
namespace itxq\apidoc;
use itxq\apidoc\lib\ParseComment;
/**
* ApiDoc生成
* Class ApiDoc
* @package itxq\apidoc
*/
class ApiDoc
{
/**
* @var array - 结构化的数组
*/
private $ApiTree = [];
/**
* @var array - 要生成API的Class类名
*/
private $class = [];
/**
* @var array - 忽略生成的类方法名
*/
private $filterMethod = ['__construct'];
/**
* ApiDoc 构造函数.
* @param array $config - 配置信息
*/
public function __construct($config) {
// 需要解析的类
if (isset($config['class'])) {
$this->class = array_merge($this->class, $config['class']);
}
// 忽略生成的类方法
if (isset($config['filter_method'])) {
$this->filterMethod = array_merge($this->filterMethod, $config['filter_method']);
}
}
/**
* 获取API文档数据
* @param int $type - 方法过滤,默认只获取 public类型 方法
* ReflectionMethod::IS_STATIC
* ReflectionMethod::IS_PUBLIC
* ReflectionMethod::IS_PROTECTED
* ReflectionMethod::IS_PRIVATE
* ReflectionMethod::IS_ABSTRACT
* ReflectionMethod::IS_FINAL
* @return array
*/
public function getApiDoc($type = \ReflectionMethod::IS_PUBLIC) {
foreach ($this->class as $classItem) {
$actionInfo = $this->_getActionComment($classItem, $type);
if (count($actionInfo) >= 1) {
$this->ApiTree[$classItem] = $this->_getClassComment($classItem);
$this->ApiTree[$classItem]['action'] = $actionInfo;
}
}
return $this->ApiTree;
}
/**
* 获取类的注释
* @param $class - 类名称(存在命名空间时要完整写入) eg: $class = 'itxq\\apidoc\\ApiDoc';
* @return array - 返回格式为数组(未获取到注释时返回空数组)
*/
private function _getClassComment($class) {
try {
$reflection = new \ReflectionClass($class);
$classDocComment = $reflection->getDocComment();
} catch (\Exception $exception) {
return [];
}
$parse = new ParseComment();
return $parse->parseCommentToArray($classDocComment);
}
/**
* 获取指定类下方法的注释
* @param $class - 类名称(存在命名空间时要完整写入) eg: $class = 'itxq\\apidoc\\ApiDoc';
* @param int $type - 方法过滤,默认只获取 public类型 方法
* ReflectionMethod::IS_STATIC
* ReflectionMethod::IS_PUBLIC
* ReflectionMethod::IS_PROTECTED
* ReflectionMethod::IS_PRIVATE
* ReflectionMethod::IS_ABSTRACT
* ReflectionMethod::IS_FINAL
* @return array - 返回格式为数组(未获取到注释时返回空数组)
*/
private function _getActionComment($class, $type = \ReflectionMethod::IS_PUBLIC) {
try {
$reflection = new \ReflectionClass($class);
//只允许生成public方法
$method = $reflection->getMethods($type);
} catch (\Exception $exception) {
return [];
}
$comments = [];
foreach ($method as $action) {
try {
$parse = new ParseComment();
$actionComments = $parse->parseCommentToArray($action->getDocComment());
if (count($actionComments) >= 1 && !in_array($action->name, $this->filterMethod)) {
$comments[$action->name] = $actionComments;
}
} catch (\Exception $exception) {
continue;
}
}
return $comments;
}
}

View File

@@ -0,0 +1,357 @@
<?php
/**
* ==================================================================
* 文 件 名: BootstrapApiDoc.php
* 概 要: BootstrapAPI文档生成
* 作 者: IT小强
* 创建时间: 2018/6/6 13:57
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
namespace itxq\apidoc;
use itxq\apidoc\lib\Tools;
/**
* BootstrapAPI文档生成
* Class BootstrapApiDoc
* @package itxq\apidoc
*/
class BootstrapApiDoc extends ApiDoc
{
/**
* @var string - Bootstrap CSS文件路径
*/
private $bootstrapCss = __DIR__ . '/../assets/css/bootstrap.min.css';
/**
* @var string - Bootstrap JS文件路径
*/
private $bootstrapJs = __DIR__ . '/../assets/js/bootstrap.min.js';
/**
* @var string - jQuery Js文件路径
*/
private $jQueryJs = __DIR__ . '/../assets/js/jquery.min.js';
/**
* @var string - 自定义CSS
*/
private $customCss = '<style type="text/css">
::-webkit-scrollbar {width: 5px;}
.navbar-collapse.collapse.show::-webkit-scrollbar {width: 0; height: 0;background-color: rgba(255, 255, 255, 0);}
::-webkit-scrollbar-track {background-color: rgba(255, 255, 255, 0.2);-webkit-border-radius: 2em;-moz-border-radius: 2em;border-radius: 2em;}
::-webkit-scrollbar-thumb {background-color: rgba(0, 0, 0, 0.8);-webkit-border-radius: 2em;-moz-border-radius: 2em;border-radius: 2em;}
::-webkit-scrollbar-button {-webkit-border-radius: 2em;-moz-border-radius: 2em;border-radius: 2em;height: 0;background-color: rgba(0, 0, 0, 0.9);}
::-webkit-scrollbar-corner {background-color: rgba(0, 0, 0, 0.9);}
#list-tab-left-nav{display: none;}
.doc-content{margin-top: 75px;}
.class-item .class-title {text-indent: 0.6em;border-left: 5px solid lightseagreen;font-size: 24px;margin: 15px 0;}
.action-item .action-title {text-indent: 0.6em;border-left: 3px solid #F0AD4E;font-size: 20px;margin: 8px 0;}
.table-item {background-color:#FFFFFF;padding-top: 10px;margin-bottom:10px;border: solid 1px #ccc;border-radius: 5px;}
.list-group-item-sub{padding: .5rem 1.25rem;}
.copyright-content{margin: 10px 0;}
</style>';
/**
* @var string - 自定义JS
*/
private $customJs = '<script type="text/javascript">
$(\'a[href*="#"]:not([href="#"])\').click(function() {
if (location.pathname.replace(/^\//, \'\') == this.pathname.replace(/^\//, \'\') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $("[name=\' + this.hash.slice(1) +\']");
if (target.length) {
var topOffset = target.offset().top - 60;
$("html, body").animate({
scrollTop: topOffset
}, 800);
return false;
}
}
});
</script>';
/**
* Bootstrap 构造函数.
* @param array $config - 配置信息
*/
public function __construct($config) {
parent::__construct($config);
// bootstrapJs文件路径
$this->bootstrapJs = Tools::getSubValue('bootstrap_js', $config, $this->bootstrapJs);
// jQueryJs文件路径
$this->jQueryJs = Tools::getSubValue('jquery_js', $config, $this->jQueryJs);
// 自定义js
$this->customJs .= Tools::getSubValue('custom_js', $config, '');
// bootstrapCSS文件路径
$this->bootstrapCss = Tools::getSubValue('bootstrap_css', $config, $this->bootstrapCss);
// 自定义CSS
$this->customCss .= Tools::getSubValue('custom_css', $config, '');
// 合并CSS
$this->_getCss();
// 合并JS
$this->_getJs();
}
/**
* 输出HTML
* @param int $type - 方法过滤,默认只获取 public类型 方法
* ReflectionMethod::IS_STATIC
* ReflectionMethod::IS_PUBLIC
* ReflectionMethod::IS_PROTECTED
* ReflectionMethod::IS_PRIVATE
* ReflectionMethod::IS_ABSTRACT
* ReflectionMethod::IS_FINAL
* @return string
*/
public function getHtml($type = \ReflectionMethod::IS_PUBLIC) {
$data = $this->getApiDoc($type);
$html = <<<EXT
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<!-- 禁止浏览器初始缩放 -->
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, maximum-scale=1, user-scalable=0">
<title>API文档 By Api-Doc-PHP</title>
{$this->customCss}
</head>
<body>
<div class="container-fluid">
<nav class="navbar navbar-expand-sm navbar-dark bg-dark fixed-top">
<a class="navbar-brand" href="#">API文档</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarColor01" >
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarColor01">
{$this->_getTopNavList($data)}
</div>
</nav>
<div class="row">
<div class="col-lg-12">{$this->_getDocList($data)}</div>
</div>
<div class="row">
<div class="col-lg-12 text-center copyright-content">
Copyright 2016 - 2018 <a href="http://www.xqitw.cn">小强IT屋</a> 版权所有
</div>
</div>
</div>
{$this->customJs}
</body>
</html>
EXT;
if (isset($_GET['download']) && $_GET['download'] === 'api_doc_php') {
Tools::downloadFile($html);
return true;
}
return $html;
}
/**
* 解析return 并生成HTML
* @param array $data
* @return string
*/
private function _getReturnData($data = []) {
$html = '';
if (!is_array($data) || count($data) < 1) {
return $html;
}
$html .= '<div class="table-item col-md-12"><p class="table-title"><span class="btn btn-sm btn-success">返回参数</span></p>';
$html .= '<table class="table"><tr><td>参数</td><td>类型</td><td>描述</td></tr>';
foreach ($data as $v) {
$html .= '<tr>
<td>' . Tools::getSubValue('return_name', $v, '') . '</td>
<td>' . Tools::getSubValue('return_type', $v, '') . '</td>
<td>' . Tools::getSubValue('return_title', $v, '') . '</td>
</tr>';
}
$html .= '</table></div>';
return $html;
}
/**
* 解析param 并生成HTML
* @param array $data
* @return string
*/
private function _getParamData($data = []) {
$html = '';
if (!is_array($data) || count($data) < 1) {
return $html;
}
$html .= '<div class="table-item col-md-12"><p class="table-title"><span class="btn btn-sm btn-danger">请求参数</span></p>';
$html .= '<table class="table"><tr><td>参数</td><td>类型</td><td>描述</td><td>默认值</td><td>是否必须</td></tr>';
foreach ($data as $v) {
$html .= '<tr>
<td>' . Tools::getSubValue('param_name', $v, '') . '</td>
<td>' . Tools::getSubValue('param_type', $v, '') . '</td>
<td>' . Tools::getSubValue('param_title', $v, '') . '</td>
<td>' . Tools::getSubValue('param_default', $v, '无默认值') . '</td>
<td>' . Tools::getSubValue('param_require', $v, '非必须') . '</td>
</tr>';
}
$html .= '</table></div>';
return $html;
}
/**
* 解析code 并生成HTML
* @param array $data
* @return string
*/
private function _getCodeData($data = []) {
$html = '';
if (!is_array($data) || count($data) < 1) {
return $html;
}
$html .= '<div class="table-item col-md-12"><p class="table-title"><span class="btn btn-sm btn-warning">状态码说明</span></p>';
$html .= '<table class="table"><tr><td>状态码</td><td>描述</td></tr>';
foreach ($data as $v) {
$html .= '<tr>
<td>' . Tools::getSubValue('code', $v, '') . '</td>
<td>' . Tools::getSubValue('content', $v, '暂无说明') . '</td>
</tr>';
}
$html .= '</table></div>';
return $html;
}
/**
* 获取指定接口操作下的文档信息
* @param $className - 类名
* @param $actionName - 操作名
* @param $actionItem - 接口数据
* @return string
*/
private function _getActionItem($className, $actionName, $actionItem) {
$html = <<<EXT
<div class="list-group-item list-group-item-action action-item col-md-12" id="{$className}_{$actionName}">
<h4 class="action-title">API - {$actionItem['title']}</h4>
<p>请求方式:
<span class="btn btn-info btn-sm">{$actionItem['method']}</span>
</p>
<p>请求地址:<a href="{$actionItem['url']}">{$actionItem['url']}</a></p>
{$this->_getParamData(Tools::getSubValue('param', $actionItem, []))}
{$this->_getReturnData(Tools::getSubValue('return', $actionItem, []))}
{$this->_getCodeData(Tools::getSubValue('code', $actionItem, []))}
</div>
EXT;
return $html;
}
/**
* 获取指定API类的文档HTML
* @param $className - 类名称
* @param $classItem - 类数据
* @return string
*/
private function _getClassItem($className, $classItem) {
$title = Tools::getSubValue('title', $classItem, '未命名');
$actionHtml = '';
if (isset($classItem['action']) && is_array($classItem['action']) && count($classItem['action']) >= 1) {
foreach ($classItem['action'] as $actionName => $actionItem) {
$actionHtml .= $this->_getActionItem($className, $actionName, $actionItem);
}
}
$html = <<<EXT
<div class="class-item" id="{$className}">
<h2 class="class-title">{$title}</h2>
<div class="list-group">{$actionHtml}</div>
</div>
EXT;
return $html;
}
/**
* 获取API文档HTML
* @param array $data - 文档数据
* @return string
*/
private function _getDocList($data) {
$html = '';
if (count($data) < 1) {
return $html;
}
$html .= '<div class="doc-content">';
foreach ($data as $className => $classItem) {
$html .= $this->_getClassItem($className, $classItem);
}
$html .= '</div>';
return $html;
}
/**
* 获取顶部导航HTML
* @param $data -API文档数据
* @return string
*/
private function _getTopNavList($data) {
$html = '<ul class="navbar-nav" id="navbar-nav-top-nav">';
foreach ($data as $className => $classItem) {
$title = Tools::getSubValue('title', $classItem, '未命名');
$html .= '<li class="nav-item dropdown">';
$html .= '<a class="nav-link dropdown-toggle" href="#" id="' . $className . '-nav" data-toggle="dropdown">' . $title . '</a>';
$html .= '<div class="dropdown-menu" aria-labelledby="' . $className . '-nav">';
foreach ($classItem['action'] as $actionName => $actionItem) {
$title = Tools::getSubValue('title', $actionItem, '未命名');
$id = $className . '_' . $actionName;
$html .= '<a class="dropdown-item" href="#' . $id . '">' . $title . '</a>';
}
$html .= '</div></li>';
}
$html .= ' <li class="nav-item"><a class="nav-link" href="?download=api_doc_php">下载文档</a></li>';
$html .= '</ul>';
return $html;
}
/**
* 获取文档CSS
* @return string
*/
private function _getCss() {
$path = realpath($this->bootstrapCss);
if (!$path || !is_file($path)) {
return $this->customCss;
}
$bootstrapCss = file_get_contents($path);
if (empty($bootstrapCss)) {
return $this->customCss;
}
$this->customCss = '<style type="text/css">' . $bootstrapCss . '</style>' . $this->customCss;
// $this->customCss = ' <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet">' . $this->customCss;
return $this->customCss;
}
/**
* 获取文档JS
* @return string
*/
private function _getJs() {
// $js = '<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js" type="text/javascript"></script>';
// $js .= '<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js" type="text/javascript"></script>';
// $this->customJs = $js . $this->customJs;
// return $this->customJs;
$bootstrapJs = realpath($this->bootstrapJs);
$jQueryJs = realpath($this->jQueryJs);
if (!$bootstrapJs || !$jQueryJs || !is_file($bootstrapJs) || !is_file($jQueryJs)) {
$this->customJs = '';
return $this->customCss;
}
$bootstrapJs = file_get_contents($bootstrapJs);
$jQueryJs = file_get_contents($jQueryJs);
if (empty($bootstrapJs) || empty($jQueryJs)) {
$this->customJs = '';
return $this->customJs;
}
$js = '<script type="text/javascript">' . $jQueryJs . '</script>' . '<script type="text/javascript">' . $bootstrapJs . '</script>';
$this->customJs = $js . $this->customJs;
return $this->customJs;
}
}

View File

@@ -0,0 +1,87 @@
<?php
/**
* ==================================================================
* 文 件 名: ParseComment.php
* 概 要: 注释解析
* 作 者: IT小强
* 创建时间: 2018/6/5 10:38
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
namespace itxq\apidoc\lib;
/**
* 注释解析
* Class ParseComment
* @package itxq\apidoc\lib
*/
class ParseComment
{
/**
* @var array - 注释解析后的数组
*/
private $commentParams = [];
/**
* 将注释按行解析并以数组格式返回
* @param $comment - 原始注释字符串
* @return bool|array
*/
public function parseCommentToArray($comment) {
$comments = [];
if (empty($comment)) {
return $comments;
}
// 获取注释
if (preg_match('#^/\*\*(.*)\*/#s', $comment, $matches) === false) {
return $comments;
}
$matches = trim($matches[1]);
// 按行分割注释
if (preg_match_all('#^\s*\*(.*)#m', $matches, $lines) === false) {
return $comments;
}
$comments = $lines[1];
// 去除无用的注释
foreach ($comments as $k => $v) {
$comments[$k] = $v = trim($v);
if (strpos($v, '@') !== 0) {
continue;
}
$_parse = $this->_parseCommentLine($v);
if (!$_parse) {
continue;
}
$_type = $_parse['type'];
$_content = isset($_parse['content']) ? $_parse['content'] : '';
if (in_array($_type, ['param', 'code', 'return'])) {
if (!isset($this->commentParams[$_type])) {
$this->commentParams[$_type] = [];
}
unset($_parse['type']);
$this->commentParams[$_type][] = $_parse;
} else {
$this->commentParams[$_type] = $_content;
}
}
return $this->commentParams;
}
/**
* 解析注释中的参数
* @param $line - 注释行
* @return bool|array - 解析后的数组解析失败返回false
*/
private function _parseCommentLine($line) {
$line = explode(' ', $line);
$line[0] = substr($line[0], 1);
$class = new ParseLine();
$action = 'parseLine' . Tools::underlineToHump($line[0]);
if (!method_exists($class, $action)) {
$action = 'parseLineTitle';
}
return $class->$action($line);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* ==================================================================
* 文 件 名: ParseLine.php
* 概 要: 按行解析注释参数
* 作 者: IT小强
* 创建时间: 2018/6/5 10:34
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
namespace itxq\apidoc\lib;
/**
* 按行解析注释参数
* Class ParseLine
* @package itxq\apidoc\lib
*/
class ParseLine
{
/**
* 解析 title|url
* @param $line
* @return array
*/
public function parseLineTitle($line) {
return ['type' => isset($line[0]) ? $line[0] : '', 'content' => isset($line[1]) ? $line[1] : ''];
}
/**
* 解析 param
* @param $line
* @return array
*/
public function parseLineParam($line) {
return [
'type' => isset($line[0]) ? $line[0] : '',
'param_type' => isset($line[1]) ? $line[1] : '',
'param_name' => isset($line[2]) ? $line[2] : '',
'param_title' => isset($line[3]) ? $line[3] : '',
'param_default' => isset($line[4]) ? $line[4] : '',
'param_require' => isset($line[5]) ? $line[5] : '',
];
}
/**
* 解析 code
* @param $line
* @return array
*/
public function parseLineCode($line) {
return [
'type' => isset($line[0]) ? $line[0] : '',
'code' => isset($line[1]) ? $line[1] : '',
'content' => isset($line[2]) ? $line[2] : '',
];
}
/**
* 解析 return
* @param $line
* @return array
*/
public function parseLineReturn($line) {
return [
'type' => isset($line[0]) ? $line[0] : '',
'return_type' => isset($line[1]) ? $line[1] : '',
'return_name' => isset($line[2]) ? $line[2] : '',
'return_title' => isset($line[3]) ? $line[3] : '',
];
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* ==================================================================
* 文 件 名: Tools.php
* 概 要:
* 作 者: IT小强
* 创建时间: 2018/6/6 8:47
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
namespace itxq\apidoc\lib;
/**
* 工具类
* Class Tools
* @package itxq\apidoc\lib
*/
class Tools
{
/**
* 下划线命名转驼峰命名
* @param $str - 下划线命名字符串
* @param $isFirst - 是否为大驼峰(即首字母也大写)
* @return mixed
*/
public static function underlineToHump($str, $isFirst = false) {
$str = preg_replace_callback('/([\-\_]+([a-z]{1}))/i', function ($matches) {
return strtoupper($matches[2]);
}, $str);
if ($isFirst) {
$str = ucfirst($str);
}
return $str;
}
/**
* 驼峰命名转下划线命名
* @param $str
* @return mixed
*/
public static function humpToUnderline($str) {
$str = preg_replace_callback('/([A-Z]{1})/', function ($matches) {
return '_' . strtolower($matches[0]);
}, $str);
$str = preg_replace('/^\_/', '', $str);
return $str;
}
/**
* 获取数组、对象下标对应值,不存在时返回指定的默认值
* @param string|integer $name - 下标(键名)
* @param array|object $data - 原始数组/对象
* @param mixed $default - 指定默认值
* @return mixed
*/
public static function getSubValue($name, $data, $default = '') {
if (is_object($data)) {
$value = isset($data->$name) ? $data->$name : $default;
} else if (is_array($data)) {
$value = isset($data[$name]) ? $data[$name] : $default;
} else {
$value = $default;
}
return $value;
}
/**
* 文件下载
* @param string - $docHtml - API文档HTML内容
*/
public static function downloadFile($docHtml) {
set_time_limit(0);
//下载文件需要用到的头
header('Content-type: application/octet-stream');
header('Accept-Ranges: bytes');
header('Content-Disposition: attachment; filename=api-doc_' . date('Y-m-d') . '.html');
echo $docHtml;
exit();
}
}

49
vendor/itxq/api-doc-php/test/Api.php vendored Normal file
View File

@@ -0,0 +1,49 @@
<?php
/**
* ==================================================================
* 文 件 名: Api.php
* 概 要:
* 作 者: IT小强
* 创建时间: 2018/6/5 9:43
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
/**
* @title 登录注册
* Class Api
*/
class Api
{
/**
* @title 用户登录API
* @url https://wwww.baidu.com/login
* @method POST
* @param string username 账号 空 必须
* @param string password 密码 空 必须
* @code 1 成功
* @code 2 失败
* @return int code 状态码(具体参见状态码说明)
* @return string msg 提示信息
*/
public function login() {
return json_encode(['code' => 1, 'msg' => '登录成功']);
}
/**
* @title 用户注册API
* @url https://wwww.baidu.com/reg
* @method POST
* @param string username 账号 空 必须
* @param string password 密码 空 必须
* @param string password2 重复密码 空 必须
* @code 1 成功
* @code 2 失败
* @return int code 状态码(具体参见状态码说明)
* @return string msg 提示信息
*/
public function reg() {
return json_encode(['code' => 1, 'msg' => '注册成功']);
}
}

33
vendor/itxq/api-doc-php/test/Api2.php vendored Normal file
View File

@@ -0,0 +1,33 @@
<?php
/**
* ==================================================================
* 文 件 名: Api2.php
* 概 要:
* 作 者: IT小强
* 创建时间: 2018/6/6 9:17
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
/**
* @title 用户相关
* Class Api
*/
class Api2
{
/**
* @title 获取用户信息
* @url https://wwww.baidu.com/getuserinfo
* @method GET
* @param int uid 用户ID 0 必须
* @param string token 令牌 空 必须
* @code 1 成功
* @code 2 失败
* @return int code 状态码(具体参见状态码说明)
* @return string msg 提示信息
*/
public function getUserInfo() {
return json_encode(['code' => 1, 'msg' => '获取信息成功', 'data' => ['uid' => 1, 'username' => 'admin']]);
}
}

22
vendor/itxq/api-doc-php/test/index.php vendored Normal file
View File

@@ -0,0 +1,22 @@
<?php
/**
* ==================================================================
* 文 件 名: index.php
* 概 要: API文档 By Api-Doc-PHP
* 作 者: IT小强
* 创建时间: 2018/6/5 9:48
* 修改时间:
* copyright (c) 2016 - 2018 mail@xqitw.cn
* ==================================================================
*/
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/Api.php'; // 加载测试API类1
require_once __DIR__ . '/Api2.php'; // 加载测试API类2
$config = [
'class' => ['Api', 'Api2'], // 要生成文档的类
'filter_method' => ['__construct'], // 要过滤的方法名称
];
$api = new \itxq\apidoc\BootstrapApiDoc($config);
$doc = $api->getHtml();
exit($doc);

View File

@@ -0,0 +1,28 @@
<?php
/**
* Serbian PHPMailer language file: refer to English translation for definitive list
* @package PHPMailer
* @author Александар Јевремовић <ajevremovic@gmail.com>
* @author Miloš Milanović <mmilanovic016@gmail.com>
*/
$PHPMAILER_LANG['authenticate'] = 'SMTP greška: autentifikacija nije uspela.';
$PHPMAILER_LANG['connect_host'] = 'SMTP greška: povezivanje sa SMTP serverom nije uspelo.';
$PHPMAILER_LANG['data_not_accepted'] = 'SMTP greška: podaci nisu prihvaćeni.';
$PHPMAILER_LANG['empty_message'] = 'Sadržaj poruke je prazan.';
$PHPMAILER_LANG['encoding'] = 'Nepoznato kodiranje: ';
$PHPMAILER_LANG['execute'] = 'Nije moguće izvršiti naredbu: ';
$PHPMAILER_LANG['file_access'] = 'Nije moguće pristupiti datoteci: ';
$PHPMAILER_LANG['file_open'] = 'Nije moguće otvoriti datoteku: ';
$PHPMAILER_LANG['from_failed'] = 'SMTP greška: slanje sa sledećih adresa nije uspelo: ';
$PHPMAILER_LANG['recipients_failed'] = 'SMTP greška: slanje na sledeće adrese nije uspelo: ';
$PHPMAILER_LANG['instantiate'] = 'Nije moguće pokrenuti mail funkciju.';
$PHPMAILER_LANG['invalid_address'] = 'Poruka nije poslata. Neispravna adresa: ';
$PHPMAILER_LANG['mailer_not_supported'] = ' majler nije podržan.';
$PHPMAILER_LANG['provide_address'] = 'Definišite bar jednu adresu primaoca.';
$PHPMAILER_LANG['signing'] = 'Greška prilikom prijave: ';
$PHPMAILER_LANG['smtp_connect_failed'] = 'Povezivanje sa SMTP serverom nije uspelo.';
$PHPMAILER_LANG['smtp_error'] = 'Greška SMTP servera: ';
$PHPMAILER_LANG['variable_set'] = 'Nije moguće zadati niti resetovati promenljivu: ';
$PHPMAILER_LANG['extension_missing'] = 'Nedostaje proširenje: ';

View File

@@ -0,0 +1,44 @@
<?php
/**
* PHPMailer - PHP email creation and transport class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* OAuthTokenProvider - OAuth2 token provider interface.
* Provides base64 encoded OAuth2 auth strings for SMTP authentication.
*
* @see OAuth
* @see SMTP::authenticate()
*
* @author Peter Scopes (pdscopes)
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
*/
interface OAuthTokenProvider
{
/**
* Generate a base64-encoded OAuth token ensuring that the access token has not expired.
* The string to be base 64 encoded should be in the form:
* "user=<user_email_address>\001auth=Bearer <access_token>\001\001"
*
* @return string
*/
public function getOauth64();
}

16
vendor/psr/cache/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.0.1 - 2016-08-06
### Fixed
- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr
- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr
- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell
- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell
## 1.0.0 - 2015-12-11
Initial stable release; reflects accepted PSR-6 specification

19
vendor/psr/cache/LICENSE.txt vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright (c) 2015 PHP Framework Interoperability Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

9
vendor/psr/cache/README.md vendored Normal file
View File

@@ -0,0 +1,9 @@
PSR Cache
=========
This repository holds all interfaces defined by
[PSR-6](http://www.php-fig.org/psr/psr-6/).
Note that this is not a Cache implementation of its own. It is merely an
interface that describes a Cache implementation. See the specification for more
details.

25
vendor/psr/cache/composer.json vendored Normal file
View File

@@ -0,0 +1,25 @@
{
"name": "psr/cache",
"description": "Common interface for caching libraries",
"keywords": ["psr", "psr-6", "cache"],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"require": {
"php": ">=5.3.0"
},
"autoload": {
"psr-4": {
"Psr\\Cache\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
}
}

10
vendor/psr/cache/src/CacheException.php vendored Normal file
View File

@@ -0,0 +1,10 @@
<?php
namespace Psr\Cache;
/**
* Exception interface for all exceptions thrown by an Implementing Library.
*/
interface CacheException
{
}

View File

@@ -0,0 +1,105 @@
<?php
namespace Psr\Cache;
/**
* CacheItemInterface defines an interface for interacting with objects inside a cache.
*
* Each Item object MUST be associated with a specific key, which can be set
* according to the implementing system and is typically passed by the
* Cache\CacheItemPoolInterface object.
*
* The Cache\CacheItemInterface object encapsulates the storage and retrieval of
* cache items. Each Cache\CacheItemInterface is generated by a
* Cache\CacheItemPoolInterface object, which is responsible for any required
* setup as well as associating the object with a unique Key.
* Cache\CacheItemInterface objects MUST be able to store and retrieve any type
* of PHP value defined in the Data section of the specification.
*
* Calling Libraries MUST NOT instantiate Item objects themselves. They may only
* be requested from a Pool object via the getItem() method. Calling Libraries
* SHOULD NOT assume that an Item created by one Implementing Library is
* compatible with a Pool from another Implementing Library.
*/
interface CacheItemInterface
{
/**
* Returns the key for the current cache item.
*
* The key is loaded by the Implementing Library, but should be available to
* the higher level callers when needed.
*
* @return string
* The key string for this cache item.
*/
public function getKey();
/**
* Retrieves the value of the item from the cache associated with this object's key.
*
* The value returned must be identical to the value originally stored by set().
*
* If isHit() returns false, this method MUST return null. Note that null
* is a legitimate cached value, so the isHit() method SHOULD be used to
* differentiate between "null value was found" and "no value was found."
*
* @return mixed
* The value corresponding to this cache item's key, or null if not found.
*/
public function get();
/**
* Confirms if the cache item lookup resulted in a cache hit.
*
* Note: This method MUST NOT have a race condition between calling isHit()
* and calling get().
*
* @return bool
* True if the request resulted in a cache hit. False otherwise.
*/
public function isHit();
/**
* Sets the value represented by this cache item.
*
* The $value argument may be any item that can be serialized by PHP,
* although the method of serialization is left up to the Implementing
* Library.
*
* @param mixed $value
* The serializable value to be stored.
*
* @return static
* The invoked object.
*/
public function set($value);
/**
* Sets the expiration time for this cache item.
*
* @param \DateTimeInterface|null $expiration
* The point in time after which the item MUST be considered expired.
* If null is passed explicitly, a default value MAY be used. If none is set,
* the value should be stored permanently or for as long as the
* implementation allows.
*
* @return static
* The called object.
*/
public function expiresAt($expiration);
/**
* Sets the expiration time for this cache item.
*
* @param int|\DateInterval|null $time
* The period of time from the present after which the item MUST be considered
* expired. An integer parameter is understood to be the time in seconds until
* expiration. If null is passed explicitly, a default value MAY be used.
* If none is set, the value should be stored permanently or for as long as the
* implementation allows.
*
* @return static
* The called object.
*/
public function expiresAfter($time);
}

View File

@@ -0,0 +1,138 @@
<?php
namespace Psr\Cache;
/**
* CacheItemPoolInterface generates CacheItemInterface objects.
*
* The primary purpose of Cache\CacheItemPoolInterface is to accept a key from
* the Calling Library and return the associated Cache\CacheItemInterface object.
* It is also the primary point of interaction with the entire cache collection.
* All configuration and initialization of the Pool is left up to an
* Implementing Library.
*/
interface CacheItemPoolInterface
{
/**
* Returns a Cache Item representing the specified key.
*
* This method must always return a CacheItemInterface object, even in case of
* a cache miss. It MUST NOT return null.
*
* @param string $key
* The key for which to return the corresponding Cache Item.
*
* @throws InvalidArgumentException
* If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown.
*
* @return CacheItemInterface
* The corresponding Cache Item.
*/
public function getItem($key);
/**
* Returns a traversable set of cache items.
*
* @param string[] $keys
* An indexed array of keys of items to retrieve.
*
* @throws InvalidArgumentException
* If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown.
*
* @return array|\Traversable
* A traversable collection of Cache Items keyed by the cache keys of
* each item. A Cache item will be returned for each key, even if that
* key is not found. However, if no keys are specified then an empty
* traversable MUST be returned instead.
*/
public function getItems(array $keys = array());
/**
* Confirms if the cache contains specified cache item.
*
* Note: This method MAY avoid retrieving the cached value for performance reasons.
* This could result in a race condition with CacheItemInterface::get(). To avoid
* such situation use CacheItemInterface::isHit() instead.
*
* @param string $key
* The key for which to check existence.
*
* @throws InvalidArgumentException
* If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown.
*
* @return bool
* True if item exists in the cache, false otherwise.
*/
public function hasItem($key);
/**
* Deletes all items in the pool.
*
* @return bool
* True if the pool was successfully cleared. False if there was an error.
*/
public function clear();
/**
* Removes the item from the pool.
*
* @param string $key
* The key to delete.
*
* @throws InvalidArgumentException
* If the $key string is not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown.
*
* @return bool
* True if the item was successfully removed. False if there was an error.
*/
public function deleteItem($key);
/**
* Removes multiple items from the pool.
*
* @param string[] $keys
* An array of keys that should be removed from the pool.
* @throws InvalidArgumentException
* If any of the keys in $keys are not a legal value a \Psr\Cache\InvalidArgumentException
* MUST be thrown.
*
* @return bool
* True if the items were successfully removed. False if there was an error.
*/
public function deleteItems(array $keys);
/**
* Persists a cache item immediately.
*
* @param CacheItemInterface $item
* The cache item to save.
*
* @return bool
* True if the item was successfully persisted. False if there was an error.
*/
public function save(CacheItemInterface $item);
/**
* Sets a cache item to be persisted later.
*
* @param CacheItemInterface $item
* The cache item to save.
*
* @return bool
* False if the item could not be queued or if a commit was attempted and failed. True otherwise.
*/
public function saveDeferred(CacheItemInterface $item);
/**
* Persists any deferred cache items.
*
* @return bool
* True if all not-yet-saved items were successfully saved or there were none. False otherwise.
*/
public function commit();
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Psr\Cache;
/**
* Exception interface for invalid cache arguments.
*
* Any time an invalid argument is passed into a method it must throw an
* exception class which implements Psr\Cache\InvalidArgumentException.
*/
interface InvalidArgumentException extends CacheException
{
}

View File

@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\ClassLoader;
/**
* ApcClassLoader implements a wrapping autoloader cached in APC for PHP 5.3.
*
* It expects an object implementing a findFile method to find the file. This
* allows using it as a wrapper around the other loaders of the component (the
* ClassLoader for instance) but also around any other autoloaders following
* this convention (the Composer one for instance).
*
* // with a Symfony autoloader
* use Symfony\Component\ClassLoader\ClassLoader;
*
* $loader = new ClassLoader();
* $loader->addPrefix('Symfony\Component', __DIR__.'/component');
* $loader->addPrefix('Symfony', __DIR__.'/framework');
*
* // or with a Composer autoloader
* use Composer\Autoload\ClassLoader;
*
* $loader = new ClassLoader();
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* $cachedLoader = new ApcClassLoader('my_prefix', $loader);
*
* // activate the cached autoloader
* $cachedLoader->register();
*
* // eventually deactivate the non-cached loader if it was registered previously
* // to be sure to use the cached one.
* $loader->unregister();
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Kris Wallsmith <kris@symfony.com>
*/
class ApcClassLoader
{
private $prefix;
/**
* A class loader object that implements the findFile() method.
*
* @var object
*/
protected $decorated;
/**
* Constructor.
*
* @param string $prefix The APC namespace prefix to use
* @param object $decorated A class loader object that implements the findFile() method
*
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function __construct($prefix, $decorated)
{
if (!function_exists('apcu_fetch')) {
throw new \RuntimeException('Unable to use ApcClassLoader as APC is not installed.');
}
if (!method_exists($decorated, 'findFile')) {
throw new \InvalidArgumentException('The class finder must implement a "findFile" method.');
}
$this->prefix = $prefix;
$this->decorated = $decorated;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
*
* @return bool|null True, if loaded
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
require $file;
return true;
}
}
/**
* Finds a file by class name while caching lookups to APC.
*
* @param string $class A class name to resolve to file
*
* @return string|null
*/
public function findFile($class)
{
$file = apcu_fetch($this->prefix.$class, $success);
if (!$success) {
apcu_store($this->prefix.$class, $file = $this->decorated->findFile($class) ?: null);
}
return $file;
}
/**
* Passes through all unknown calls onto the decorated object.
*/
public function __call($method, $args)
{
return call_user_func_array(array($this->decorated, $method), $args);
}
}

Some files were not shown because too many files have changed in this diff Show More