Init docs

This commit is contained in:
李铭昕 2019-03-08 12:04:46 +08:00
parent 8c2f8728d9
commit 2de7281402
140 changed files with 494 additions and 12044 deletions

0
.nojekyll Normal file
View File

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Hyperf
A coroutine framework that focuses on hyperspeed and flexibility, specifically used for build microservices or middlewares.
- [中文文档](zh/README.md)
- [English](en/README.md)

View File

@ -1,107 +0,0 @@
{
"name": "hyperflex/hyperflex",
"description": "A coroutine framework that focuses on hyperspeed and flexible, specifically use for build microservices or middlewares.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperflex"
],
"support": {
},
"require": {
"php": ">=7.1",
"psr/http-server-middleware": "^1.0",
"psr/container": "^1.0",
"psr/log": "^1.0",
"psr/http-message": "^1.0.1",
"psr/http-server-middleware": "^1.0",
"fig/http-message-util": "^1.1.2",
"nikic/fast-route": "^1.3",
"nikic/php-parser": "^4.1",
"doctrine/annotations": "^1.6",
"doctrine/instantiator": "^1.0",
"php-di/phpdoc-reader": "^2.0.1",
"google/protobuf": "^3.6.1",
"grpc/grpc": "^1.15",
"symfony/finder": "^4.1",
"symfony/console": "^4.2",
"swoft/http-message": "^1.0"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0",
"php-di/php-di": "^6.0",
"doctrine/common": "@stable"
},
"replace": {
"hyperflex/config": "self.version",
"hyperflex/di": "self.version",
"hyperflex/dispatcher": "self.version",
"hyperflex/grpc-server": "self.version",
"hyperflex/http-server": "self.version",
"hyperflex/framework": "self.version",
"hyperflex/memory": "self.version",
"hyperflex/utils": "self.version"
},
"suggest": {
},
"autoload": {
"files": [
"src/utils/src/Functions.php"
],
"psr-4": {
"Hyperflex\\Config\\": "src/config/src/",
"Hyperflex\\Di\\": "src/di/src/",
"Hyperflex\\Dispatcher\\": "src/dispatcher/src/",
"Hyperflex\\GrpcServer\\": "src/grpc-server/src/",
"Hyperflex\\HttpServer\\": "src/http-server/src/",
"Hyperflex\\Framework\\": "src/framework/src/",
"Hyperflex\\Memory\\": "src/memory/src/",
"Hyperflex\\Utils\\": "src/utils/src/"
}
},
"autoload-dev": {
"psr-4": {
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": [
"Hyperflex\\Config\\ConfigProvider",
"Hyperflex\\Di\\ConfigProvider",
"Hyperflex\\Dispatcher\\ConfigProvider",
"Hyperflex\\Framework\\ConfigProvider",
"Hyperflex\\GrpcServer\\ConfigProvider",
"Hyperflex\\HttpServer\\ConfigProvider",
"Hyperflex\\Memory\\ConfigProvider",
"Hyperflex\\Utils\\ConfigProvider"
]
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
},
"minimum-stability": "dev"
}

4
en/README.md Normal file
View File

@ -0,0 +1,4 @@
# Introduction
[components](https://github.com/hyperf-cloud/hyperf)

125
en/amqp.md Normal file
View File

@ -0,0 +1,125 @@
# AMQP
[https://github.com/hyperf-cloud/amqp](https://github.com/hyperf-cloud/amqp)
## Default Config
~~~php
<?php
return [
'default' => [
'host' => 'localhost',
'port' => 5672,
'user' => 'guest',
'password' => 'guest',
'vhost' => '/',
'pool' => [
'min_connections' => 1,
'max_connections' => 10,
'connect_timeout' => 10.0,
'wait_timeout' => 3.0,
'heartbeat' => -1,
],
'params' => [
'insist' => false,
'login_method' => 'AMQPLAIN',
'login_response' => null,
'locale' => 'en_US',
'connection_timeout' => 3.0,
'read_write_timeout' => 3.0,
'context' => null,
'keepalive' => false,
'heartbeat' => 0,
],
],
];
~~~
## Deliver Message
Use generator command to create a producer.
~~~
php bin/hyperf.php gen:amqp-producer DemoProducer
~~~
We can modify the Producer annotation to replace exchange and routingKey.
Payloload is the data that is finally delivered to the message queue, so we can rewrite the _construct method easyly,just make sure payload is assigned.
~~~php
<?php
declare(strict_types=1);
namespace App\Amqp\Producers;
use Hyperf\Amqp\Annotation\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
use App\Models\User;
/**
* DemoProducer
* @Producer(exchange="hyperf", routingKey="hyperf")
*/
class DemoProducer extends ProducerMessage
{
public function __construct($id)
{
$user = User::where('id', $id)->first();
$this->payload = [
'id' => $id,
'data' => $user->toArray()
];
}
}
~~~
Get the Producer instance through container, and you can deliver the message. It is not reasonable for the following examples to use Application Context directly to get the Producer. For the specific use of container, see the di module.
~~~php
<?php
use Hyperf\Amqp\Producer;
use App\Amqp\Producers\DemoProducer;
use Hyperf\Utils\ApplicationContext;
$message = new DemoProducer(1);
$producer = ApplicationContext::getContainer()->get(Producer::class);
$result = $producer->produce($message);
~~~
## Consume Message
Use generator command to create a consumer.
~~~
php bin/hyperf.php gen:amqp-consumer DemoConsumer
~~~
we can modify the Consumer annotation to replace exchange, routingKey and queue.
And $data is parsed metadata.
~~~php
<?php
declare(strict_types=1);
namespace App\Amqp\Consumers;
use Hyperf\Amqp\Annotation\Consumer;
use Hyperf\Amqp\Message\ConsumerMessage;
use Hyperf\Amqp\Result;
/**
* @Consumer(exchange="hyperf", routingKey="hyperf", queue="hyperf", nums=1)
*/
class DemoConsumer extends ConsumerMessage
{
public function consume($data): string
{
print_r($data);
return Result::ACK;
}
}
~~~
The framework automatically creates the process according to Consumer annotations, and the process will be pulled up again after unexpected exit.

79
en/cache.md Normal file
View File

@ -0,0 +1,79 @@
# Cache
[https://github.com/hyperf-cloud/cache](https://github.com/hyperf-cloud/cache)
## Config
~~~php
<?php
return [
'default' => [
'driver' => Hyperf\Cache\Driver\RedisDriver::class,
'packer' => Hyperf\Cache\Packer\PhpSerializer::class,
],
];
~~~
## How to use
Components provide Cacheable annotation to configure cache prefix, expiration times, listeners, and cache groups.
For example, UserService provides a user method to query user information. When the Cacheable annotation is added, the Redis cache is automatically generated with the key value of `user:id` and the timeout time of 9000 seconds. When querying for the first time, it will be fetched from DB, and when querying later, it will be fetched from Cache.
~~~php
<?php
namespace App\Services;
use App\Models\User;
use Hyperf\Cache\Annotation\Cacheable;
class UserService
{
/**
* @Cacheable(key="user", ttl=9000, listener="user-update")
*/
public function user($id)
{
$user = User::query()->where('id',$id)->first();
if($user){
return $user->toArray();
}
return null;
}
}
~~~
## Clear Cache
Of course, if the data changes, how to delete the cache? Here we need to use the listener. Next, a new Service provides a way to help us deal with it.
~~~php
<?php
declare(strict_types=1);
namespace App\Service;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Cache\Listener\DeleteListenerEvent;
use Psr\EventDispatcher\EventDispatcherInterface;
class SystemService
{
/**
* @Inject
* @var EventDispatcherInterface
*/
protected $dispatcher;
public function flushCache($userId)
{
$this->dispatcher->dispatch(new DeleteListenerEvent('user-update', [$userId]));
return true;
}
}
~~~

0
en/core/README.md Normal file
View File

View File

0
en/model/README.md Normal file
View File

0
en/quick_start/README.md Normal file
View File

View File

41
index.html Normal file
View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css">
</head>
<body>
<nav>
<a href="#/zh/">中文</a>
<a href="#/en/">EN</a>
</nav>
<div id="app"></div>
<script>
window.$docsify = {
name: 'Hyperf',
repo: 'hyperf-cloud/hyperf',
loadSidebar: 'summary.md',
autoHeader: true,
loadNavbar: true,
// basePath: '/zh/',
// fallbackLanguages: ['zh', 'en'],
mergeNavbar: true,
themeColor: '#3F51B5',
auto2top: true,
subMaxLevel: 2,
alias: {
// '/.*/summary.md': '/summary.md'
}
}
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-php.min.js"></script>
</body>
</html>

View File

@ -1,65 +0,0 @@
{
"name": "hyperflex/config",
"description": "An independent component that provide configuration container.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperflex",
"config",
"configuration"
],
"support": {
},
"require": {
"php": ">=7.1",
"hyperflex/framework": "dev-master",
"hyperflex/utils": "dev-master"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0"
},
"suggest": {
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperflex\\Config\\": "src"
}
},
"autoload-dev": {
"psr-4": {
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": "Hyperflex\\Config\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
}
}

View File

@ -1,62 +0,0 @@
<?php
namespace Hyperflex\Config;
use Hyperflex\Framework\Contracts\ConfigInterface;
use Hyperflex\Utils\Arr;
class Config implements ConfigInterface
{
/**
* @var array
*/
private $configs = [];
/**
* Config constructor.
*
* @param $configs
*/
public function __construct($configs)
{
$this->configs = $configs;
}
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $key Identifier of the entry to look for.
* @param mixed $default Default value of the entry when does not found.
* @return mixed Entry.
*/
public function get(string $key, $default = null)
{
return Arr::get($this->configs, $key, $default);
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* @param string $key Identifier of the entry to look for.
* @return bool
*/
public function has(string $key)
{
return Arr::has($this->configs, $key);
}
/**
* Set a value to the container by its identifier.
*
* @param string $key Identifier of the entry to set.
* @param mixed $value The value that save to container.
* @return void
*/
public function set(string $key, $value)
{
Arr::set($this->configs, $key, $value);
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace Hyperflex\Config;
use Hyperflex\Utils\Composer;
use Psr\Container\ContainerInterface;
use Symfony\Component\Finder\Finder;
class ConfigFactory
{
public function __invoke(ContainerInterface $container)
{
$configPath = BASE_PATH . '/config/';
$config = $this->readConfig($configPath . 'config.php');
$serverConfig = $this->readConfig($configPath . 'server.php');
$autoloadConfig = $this->readPaths([BASE_PATH . '/config/autoload']);
$merged = array_replace_recursive(ProviderConfig::load(), $serverConfig, $config, ...$autoloadConfig);
return new Config($merged);
}
private function readConfig(string $configPath): array
{
$config = [];
if (file_exists($configPath) && is_readable($configPath)) {
$config = require $configPath;
}
return is_array($config) ? $config : [];
}
private function readPaths(array $paths)
{
$configs = [];
$finder = new Finder();
$finder->files()->in($paths)->name('*.php');
foreach ($finder as $file) {
$configs[] = [
$file->getBasename('.php') => require $file->getRealPath(),
];
}
return $configs;
}
}

View File

@ -1,25 +0,0 @@
<?php
namespace Hyperflex\Config;
use Hyperflex\Framework\Contracts\ConfigInterface;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
ConfigInterface::class => ConfigFactory::class,
],
'scan' => [
'paths' => [
'vendor/hyperflex/config/src'
]
],
];
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace Hyperflex\Config;
use Hyperflex\Utils\Arr;
use Hyperflex\Utils\Composer;
use function is_string;
use function class_exists;
use function method_exists;
use function array_replace_recursive;
/**
* Provider config allow the components set the configs to application.
*/
class ProviderConfig
{
/**
* @var array
*/
private static $privoderConfigs = [];
/**
* Load and merge all provider configs from components.
* Notice that this method will cached the config result into a static property,
* call ProviderConfig::clear() method if you want to reset the static property.
*/
public static function load(): array
{
if (! static::$privoderConfigs) {
$config = [];
$providers = Composer::getMergedExtra('hyperflex')['config'];
foreach ($providers ?? [] as $provider) {
if (is_string($provider) && class_exists($provider) && method_exists($provider, '__invoke')) {
$providerConfig = (new $provider)();
$config = array_merge_recursive($config, $providerConfig);
}
}
static::$privoderConfigs = Arr::unique($config);
unset($config, $providerConfig);
}
return static::$privoderConfigs;
}
public function clear(): void
{
static::$privoderConfigs = [];
}
}

View File

@ -1,69 +0,0 @@
{
"name": "hyperflex/di",
"description": "A DI for Hyperflex.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperflex",
"di",
"annotation"
],
"support": {
},
"require": {
"php": ">=7.1",
"hyperflex/framework": "dev-master",
"nikic/php-parser": "^4.1",
"doctrine/annotations": "^1.6",
"symfony/finder": "^4.1",
"php-di/phpdoc-reader": "^2.0.1",
"doctrine/instantiator": "^1.0"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0"
},
"suggest": {
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperflex\\Di\\": "src"
}
},
"autoload-dev": {
"psr-4": {
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": "Hyperflex\\Di\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
abstract class AbstractAnnotation implements AnnotationInterface
{
/**
* @var array
*/
public $value;
public function __construct($value = null)
{
$this->value = $value;
}
public function collect(string $className, ?string $target): void
{
if (isset($this->value)) {
AnnotationCollector::collectClass($className, static::class, $this->value);
}
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
use Hyperflex\Di\MetadataCollector;
class AnnotationCollector extends MetadataCollector
{
/**
* @var array
*/
protected static $container = [];
public static function collectClass(string $class, string $annotation, $value): void
{
static::$container[$class]['_c'][$annotation] = $value;
}
public static function collectProperty(string $class, string $property, string $annotation, $value): void
{
static::$container[$class]['_p'][$property][$annotation] = $value;
}
}

View File

@ -1,14 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
interface AnnotationInterface
{
/**
* @return string Collect the annotation metadata to a container that you wants.
*/
public function collect(string $className, ?string $target): void;
}

View File

@ -1,29 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
use Doctrine\Instantiator\Instantiator;
use Hyperflex\Di\Aop\ArroundInterface;
/**
* @Annotation
* @Target({"CLASS"})
*/
class Aspect extends AbstractAnnotation
{
public function collect(string $className, ?string $target): void
{
// @TODO Add order property.
if (class_exists($className)) {
// Create the aspect instance without invoking their constructor.
$instantitor = new Instantiator();
$instance = $instantitor->instantiate($className);
switch ($instance) {
case $instance instanceof ArroundInterface:
AspectCollector::setArround($className, $instance->classes, $instance->annotations);
break;
}
}
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
use Hyperflex\Di\MetadataCollector;
class AspectCollector extends MetadataCollector
{
/**
* @var array
*/
protected static $container = [];
public static function setBefore(string $aspect, array $classes, array $annotations)
{
$before = static::get('before');
$before['classes'][$aspect] = array_replace($before['classes'][$aspect] ?? [], $classes);
$before['annotations'][$aspect] = array_replace($before['annotations'][$aspect] ?? [], $annotations);
static::set('before', $before);
}
public static function setArround(string $aspect, array $classes, array $annotations)
{
$arround = static::get('arround');
$arround['classes'][$aspect] = array_replace($arround['classes'][$aspect] ?? [], $classes);
$arround['annotations'][$aspect] = array_replace($arround['annotations'][$aspect] ?? [], $annotations);
static::set('arround', $arround);
}
}

View File

@ -1,37 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
use Hyperflex\Di\ReflectionManager;
use PhpDocReader\PhpDocReader;
/**
* @Annotation
* @Target({"PROPERTY"})
*/
class Inject extends AbstractAnnotation
{
/**
* @var PhpDocReader
*/
private $docReader;
public function __construct($value = null)
{
parent::__construct($value);
$this->docReader = new PhpDocReader();
}
public function collect(string $className, ?string $target): void
{
if (! $this->value) {
$this->value = $this->docReader->getPropertyClass(ReflectionManager::reflectClass($className)->getProperty($target));
}
if (isset($this->value)) {
AnnotationCollector::collectProperty($className, $target, static::class, $this->value);
}
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
class MetadataHolder implements MetadataHolderInterface
{
/**
* @var array
*/
private $container = [];
/**
* Retrieve the metadata via key.
*/
public function get(string $key, $default = null)
{
return $this->container[$key] ?? $default;
}
/**
* Set the metadata to holder.
*/
public function set(string $key, $value): void
{
$this->container[$key] = $value;
}
/**
* Serialize the all metadata to a string.
*/
public function serialize(): string
{
return serialize($this->container);
}
/**
* Deserialize the serialized metadata and set the metadata to holder.
*/
public function deserialize(string $metadata): bool
{
$data = unserialize($metadata);
$this->container = $data;
return true;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
interface MetadataHolderInterface
{
/**
* Retrieve the metadata via key.
*/
public function get(string $key, $default = null);
/**
* Set the metadata to holder.
*/
public function set(string $key, $value): void;
/**
* Serialize the all metadata to a string.
*/
public function serialize(): string;
/**
* Deserialize the serialized metadata and set the metadata to holder.
*/
public function deserialize(string $metadata): bool;
}

View File

@ -1,83 +0,0 @@
<?php
namespace Hyperflex\Di\Annotation;
use App\Controllers\IndexController;
use Doctrine\Common\Annotations\AnnotationReader;
use Doctrine\Common\Annotations\AnnotationRegistry;
use Hyperflex\Contracts\ConfigInterface;
use Hyperflex\Di\Aop\Ast;
use Hyperflex\Di\Aop\AstCollector;
use Hyperflex\Di\ReflectionManager;
use PhpDocReader\PhpDocReader;
use Symfony\Component\Finder\Finder;
class Scanner
{
/**
* @var Ast
*/
private $parser;
/**
* @var array
*/
private $ignoreAnnotations = [];
public function __construct(array $ignoreAnnotations = ['mixin'])
{
$this->parser = new Ast();
$this->ignoreAnnotations = $ignoreAnnotations;
// TODO: this method is deprecated and will be removed in doctrine/annotations 2.0
AnnotationRegistry::registerLoader('class_exists');
}
public function scan(array $paths)
{
$finder = new Finder();
$finder->files()->in($paths)->name('*.php');
array_walk($this->ignoreAnnotations, function ($value) {
AnnotationReader::addGlobalIgnoredName($value);
});
$reader = new AnnotationReader();
$classColletion = [];
foreach ($finder as $file) {
try {
$stmts = $this->parser->parse($file->getContents());
$className = $this->parser->parseClassByStmts($stmts);
if (! $className) {
continue;
}
AstCollector::set($className, $stmts);
$classColletion[] = $className;
} catch (\RuntimeException $e) {
continue;
}
}
// Because the annotation class should loaded before use it, so load file via $finder previous, and then parse annotation here.
foreach ($classColletion as $className) {
$reflectionClass = ReflectionManager::reflectClass($className);
$annotations = $reader->getClassAnnotations($reflectionClass);
foreach ($annotations as $annotation) {
if ($annotation instanceof AbstractAnnotation) {
$annotation->collect($className, null);
}
}
$properties = $reflectionClass->getProperties();
foreach ($properties as $property) {
$propertyAnnotations = $reader->getPropertyAnnotations($property);
if (! empty($propertyAnnotations)) {
foreach ($propertyAnnotations as $propertyAnnotation) {
$propertyAnnotation instanceof AnnotationInterface && $propertyAnnotation->collect($className, $property->getName());
}
}
}
unset($classColletion);
}
}
}

View File

@ -1,12 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
interface ArroundInterface
{
public function process(ProceedingJoinPoint $proceedingJoinPoint);
}

View File

@ -1,84 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use App\Controllers\IndexController;
use Hyperflex\Utils\Composer;
use PhpParser\Node;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Parser as AstParserInterface;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use PhpParser\PrettyPrinterAbstract;
class Ast
{
/**
* @var \PhpParser\Parser
*/
private $astParser;
/**
* @var PrettyPrinterAbstract
*/
private $printer;
public function __construct()
{
$parserFactory = new ParserFactory();
$this->astParser = $parserFactory->create(ParserFactory::ONLY_PHP7);
$this->printer = new Standard();
}
public function parse(string $code): ?array
{
return $this->astParser->parse($code);
}
public function proxy(string $className, string $proxyClassName)
{
$stmts = AstCollector::get($className, value(function () use ($className) {
$code = $this->getCodeByClassName($className);
return $stmts = $this->astParser->parse($code);
}));
$traverser = new NodeTraverser();
// @TODO Allow user modify or replace node vistor.
$traverser->addVisitor(new ProxyClassNameVistor($proxyClassName));
$traverser->addVisitor(new ProxyCallVistor());
$modifiedStmts = $traverser->traverse($stmts);
$code = $this->printer->prettyPrintFile($modifiedStmts);
return $code;
}
public function parseClassByStmts(array $stmts): string
{
$namespace = $className = '';
foreach ($stmts as $stmt) {
if ($stmt instanceof Namespace_) {
$namespace = $stmt->name->toString();
foreach ($stmt->stmts as $node) {
if ($node instanceof Class_) {
$className = $node->name->toString();
break;
}
}
}
}
return ($namespace && $className) ? $namespace . '\\' . $className : '';
}
private function getCodeByClassName(string $className)
{
$file = Composer::getLoader()->findFile($className);
if (! $file) {
return '';
}
return file_get_contents($file);
}
}

View File

@ -1,16 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use Hyperflex\Di\MetadataCollector;
class AstCollector extends MetadataCollector
{
/**
* @var array
*/
protected static $container = [];
}

View File

@ -1,17 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use PhpParser\Parser as ParserInterface;
use PhpParser\ParserFactory;
class AstParserFactory
{
public function __invoke(): ParserInterface
{
$parserFactory = new ParserFactory();
return $parserFactory->create(ParserFactory::ONLY_PHP7);
}
}

View File

@ -1,28 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use Closure;
use Hyperflex\Di\Exception\InvalidDefinitionException;
class Pipeline extends \Hyperflex\Utils\Pipeline
{
protected function carry(): Closure
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if (is_string($pipe) && class_exists($pipe)) {
$pipe = $this->container->get($pipe);
}
if (! $passable instanceof ProceedingJoinPoint) {
throw new InvalidDefinitionException('$passable must is a ProceedingJoinPoint object.');
}
$passable->pipe = $stack;
return method_exists($pipe, $this->method) ? $pipe->{$this->method}($passable) : $pipe($passable);
};
};
}
}

View File

@ -1,73 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use Closure;
class ProceedingJoinPoint
{
/**
* @var string
*/
public $className;
/**
* @var string
*/
public $method;
/**
* @var mixed[]
*/
public $arguments;
/**
* @var mixed
*/
public $result;
/**
* @var Closure
*/
public $originalMethod;
/**
* @var Closure
*/
public $pipe;
public function __construct(Closure $originalMethod, string $className, string $method, array $arguments)
{
$this->originalMethod = $originalMethod;
$this->className = $className;
$this->method = $method;
$this->arguments = $arguments;
}
public function process()
{
$closure = $this->pipe;
return $closure($this);
}
public function processOriginalMethod()
{
$this->pipe = null;
$closure = $this->originalMethod;
if (count($this->arguments['keys']) > 1) {
$arguments = value(function () {
$result = [];
foreach ($this->arguments['order'] as $order) {
$result[] = $this->arguments['keys'][$order];
}
return $result;
});
} else {
$arguments = array_values($this->arguments['keys']);
}
return $closure(...$arguments);
}
}

View File

@ -1,216 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Expr\ClassConstFetch;
use PhpParser\Node\Expr\Closure;
use PhpParser\Node\Expr\FuncCall;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\PropertyFetch;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Expr\StaticPropertyFetch;
use PhpParser\Node\Expr\Variable;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\MagicConst\Function_ as MagicConstFunction;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\Stmt\Return_;
use PhpParser\Node\Stmt\TraitUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\NodeVisitorAbstract;
use Psr\Container\ContainerInterface;
class ProxyCallVistor extends NodeVisitorAbstract
{
/**
* Determine if the class used proxy trait.
*
* @var bool
*/
private $useProxyTrait = false;
/**
* Define the proxy handler trait here.
*
* @var array
*/
private $proxyTraits
= [
ProxyTrait::class,
];
/**
* @var Identifier
*/
private $class;
/**
* @var Name|null
*/
private $extends;
public function beforeTraverse(array $nodes)
{
foreach ($nodes as $namespace) {
if (! $namespace instanceof Namespace_) {
return;
}
// Add current class namespace.
$usedNamespace = [
$namespace->name->toCodeString(),
];
foreach ($namespace->stmts as $class) {
switch ($class) {
case $class instanceof Use_:
// Collect the namespace which the current class imported.
foreach ($class->uses as $classUse) {
$usedNamespace[] = $classUse->name->toCodeString();
}
break;
case $class instanceof Class_:
$this->class = $class->name;
if ($class->extends) {
$this->extends = $class->extends;
}
// Determine if the current class has used the proxy trait.
foreach ($class->stmts as $subNode) {
if ($subNode instanceof TraitUse) {
/** @var Name $trait */
foreach ($subNode->traits as $trait) {
if ($this->isMatchUseTrait($usedNamespace, $trait->toCodeString())) {
$this->useProxyTrait = true;
}
}
}
}
break;
}
}
}
}
public function leaveNode(Node $node)
{
switch ($node) {
case $node instanceof ClassMethod:
if ($node->name->toString() === '__construct') {
return $node;
}
// Rewrite the method to proxy call method.
return $this->rewriteMethod($node);
break;
case $node instanceof Class_:
// Add use proxy traits.
$stmts = $node->stmts;
array_unshift($stmts, $this->buildProxyCallTraitUseStatement());
$node->stmts = $stmts;
unset($stmts);
return $node;
break;
case $node instanceof StaticPropertyFetch && $this->extends:
// Rewrite parent::$staticProperty to ParentClass::$staticProperty.
if ($node->class->toString() === 'parent') {
$node->class = new Name($this->extends->toCodeString());
return $node;
}
break;
}
}
/**
* @param array $namespaces The namespaces that the current class imported.
* @param string $trait The full namespace of trait or the trait name.
*/
private function isMatchUseTrait(array $namespaces, string $trait): bool
{
// @TODO use $this->proxyTraits.
$proxyTrait = ProxyTrait::class;
$trait = ltrim($trait, '\\');
if ($trait === $proxyTrait) {
return true;
}
foreach ($namespaces as $namespace) {
if (ltrim($namespace, '\\') . '\\' . $trait === $proxyTrait) {
return true;
}
}
return false;
}
/**
* Build `use ProxyTrait;`
*/
private function buildProxyCallTraitUseStatement(): TraitUse
{
$traits = [];
foreach ($this->proxyTraits as $proxyTrait) {
if (! is_string($proxyTrait) || ! trait_exists($proxyTrait)) {
continue;
}
// Add backslash prefix if the proxy trait does not start with backslash.
$proxyTrait[0] !== '\\' && $proxyTrait = '\\' . $proxyTrait;
$traits[] = new Name($proxyTrait);
}
return new TraitUse($traits);
}
/**
* Rewrite a normal class method to a proxy call method,
* include normal class method and static method.
*/
private function rewriteMethod(ClassMethod $node): ClassMethod
{
// Build the static proxy call method base on the original method.
$class = $this->class->toString();
$node->stmts = [
new Return_(new StaticCall(new Name('self'), '__proxyCall', [
// OriginalClass::class
new ClassConstFetch(new Name($class), new Identifier('class')),
// __FUNCTION__
new MagicConstFunction(),
// self::getParamMap(OriginalClass::class, __FUNCTION, func_get_args())
new StaticCall(new Name('self'), 'getParamsMap', [
new ClassConstFetch(new Name($class), new Identifier('class')),
new MagicConstFunction(),
new FuncCall(new Name('func_get_args'))
]),
// A closure that wrapped original method code.
new Closure([
'uses' => $this->getParametersWithoutTypehint($node),
'stmts' => $node->stmts,
]),
]))
];
return $node;
}
/**
* Get the parameters of method, without parameter typehint.
*/
private function getParametersWithoutTypehint(ClassMethod $node): array
{
$parametersWithoutTypehint = value(function () use ($node) {
// Remove the parameter typehint, otherwise will cause syntax error.
$params = [];
foreach ($node->getParams() as $param) {
/** @var \PhpParser\Node\Param $param */
// Should create a new param node, modify the original node will change the original status.
$newParam = clone $param;
$newParam->type = null;
$params[] = $newParam;
}
return $params;
});
return $parametersWithoutTypehint;
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
class ProxyClassNameVistor extends NodeVisitorAbstract
{
/**
* @var string
*/
private $proxyClassName;
public function __construct(string $proxyClassName)
{
if (strpos($proxyClassName, '\\') !== false) {
$exploded = explode('\\', $proxyClassName);
$proxyClassName = end($exploded);
}
$this->proxyClassName = $proxyClassName;
}
public function leaveNode(Node $node)
{
// Rewirte the class name and extends the original class.
if ($node instanceof Node\Stmt\Class_) {
$node->extends = $node->name;
$node->name = new Node\Identifier($this->proxyClassName);
return $node;
}
}
}

View File

@ -1,84 +0,0 @@
<?php
namespace Hyperflex\Di\Aop;
use Closure;
use Doctrine\Instantiator\Instantiator;
use Hyperflex\Di\Annotation\AspectCollector;
use Hyperflex\Di\ReflectionManager;
use Hyperflex\Framework\Hyperflex;
trait ProxyTrait
{
protected static function __proxyCall(
string $originalClassName,
string $method,
array $arguments,
Closure $closure
) {
echo $originalClassName . '::' . $method . '.pre' . PHP_EOL;
$proceedingJoinPoint = new ProceedingJoinPoint($closure, $originalClassName, $method, $arguments);
$result = self::handleArround($proceedingJoinPoint);
echo $originalClassName . '::' . $method . '.post' . PHP_EOL;
unset($proceedingJoinPoint);
return $result;
}
/**
* @TODO This method will be called everytime, should optimize it later.
*/
protected static function getParamsMap(string $className, string $method, array $args): array
{
$map = [
'keys' => [],
'order' => [],
];
$reflectMethod = ReflectionManager::reflectMethod($className, $method);
$reflectParameters = $reflectMethod->getParameters();
foreach ($reflectParameters as $key => $reflectionParameter) {
$map['keys'][$reflectionParameter->getName()] = $args[$key];
$map['order'][] = $reflectionParameter->getName();
}
return $map;
}
private static function handleArround(ProceedingJoinPoint $proceedingJoinPoint)
{
$arround = AspectCollector::get('arround');
if ($aspects = self::isMatchClassName($arround['classes'] ?? [], $proceedingJoinPoint->className, $proceedingJoinPoint->method)) {
$pipeline = new Pipeline(Hyperflex::getContainer());
return $pipeline->via('process')->through($aspects)->send($proceedingJoinPoint)->then(function (ProceedingJoinPoint $proceedingJoinPoint) {
return $proceedingJoinPoint->processOriginalMethod();
});
} else {
return $proceedingJoinPoint->processOriginalMethod();
}
}
private static function isMatchClassName(array $aspects, string $className, string $method)
{
// @TODO Handle wildcard character
$matchAspect = [];
foreach ($aspects as $aspect => $item) {
foreach ($item as $class) {
if (strpos($class, '::') !== false) {
[$expectedClass, $expectedMethod] = explode('::', $class);
if ($expectedClass === $className && $expectedMethod === $method) {
$matchAspect[] = $aspect;
break;
}
} else {
if ($class === $className) {
$matchAspect[] = $aspect;
break;
}
}
}
}
return $matchAspect;
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Hyperflex\Di;
use Hyperflex\Di\Aop\AstParserFactory;
use PhpParser\Parser;
use PhpParser\PrettyPrinter\Standard;
use PhpParser\PrettyPrinterAbstract;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
Parser::class => AstParserFactory::class,
PrettyPrinterAbstract::class => Standard::class,
],
'scan' => [
'paths' => [
"vendor/hyperflex/di/src"
],
],
];
}
}

View File

@ -1,192 +0,0 @@
<?php
namespace Hyperflex\Di;
use DI\FactoryInterface;
use Hyperflex\Di\Definition\DefinitionInterface;
use Hyperflex\Di\Exception\DependencyException;
use Hyperflex\Di\Exception\NotFoundException;
use Hyperflex\Di\Resolver\ResolverDispatcher;
use Hyperflex\Dispatcher\Exceptions\InvalidArgumentException;
use Invoker\InvokerInterface;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
class Container implements ContainerInterface
{
/**
* Map of entries that are already resolved.
*
* @var array
*/
private $resolvedEntries = [];
/**
* Map of definitions that are already fetched (local cache).
*
* @var (Definition|null)[]
*/
private $fetchedDefinitions = [];
/**
* @var Definition\DefinitionSourceInterface
*/
private $definitionSource;
/**
* @var Resolver\ResolverInterface
*/
private $definitionResolver;
/**
* @TODO Extract ProxyFactory to a Interface.
* @var ProxyFactory
*/
private $proxyFactory;
/**
* Container constructor.
*
* @param Definition\DefinitionSourceInterface $definitionSource
*/
public function __construct(Definition\DefinitionSourceInterface $definitionSource)
{
$this->definitionSource = $definitionSource;
$this->definitionResolver = new ResolverDispatcher($this);
$this->proxyFactory = new ProxyFactory($this);
// Auto-register the container.
$this->resolvedEntries = [
self::class => $this,
ContainerInterface::class => $this,
FactoryInterface::class => $this,
InvokerInterface::class => $this,
];
}
/**
* Build an entry of the container by its name.
* This method behave like get() except resolves the entry again every time.
* For example if the entry is a class then a new instance will be created each time.
* This method makes the container behave like a factory.
*
* @param string $name Entry name or a class name.
* @param array $parameters Optional parameters to use to build the entry. Use this to force specific parameters
* to specific values. Parameters not defined in this array will be resolved using
* the container.
* @throws InvalidArgumentException The name parameter must be of type string.
* @throws DependencyException Error while resolving the entry.
* @throws NotFoundException No entry found for the given name.
* @return mixed
*/
public function make(string $name, array $parameters = [])
{
// If the entry is already resolved we return it
if (isset($this->resolvedEntries[$name]) || array_key_exists($name, $this->resolvedEntries)) {
return $this->resolvedEntries[$name];
}
$definition = $this->getDefinition($name);
if (! $definition) {
throw new NotFoundException("No entry or class found for '$name'");
}
return $this->resolveDefinition($name, $definition, $parameters);
}
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $name Identifier of the entry to look for.
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
* @throws DependencyException Error while resolving the entry.
* @return mixed Entry.
*/
public function get($name)
{
$this->resolvedEntries[$name] = $value = $this->make($name);
return $value;
}
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
* `has($name)` returning true does not mean that `get($name)` will not throw an exception.
* It does however mean that `get($name)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $name Identifier of the entry to look for.
* @return bool
*/
public function has($name)
{
if (! is_string($name)) {
throw new InvalidArgumentException(sprintf('The name parameter must be of type string, %s given', is_object($name) ? get_class($name) : gettype($name)));
}
if (array_key_exists($name, $this->resolvedEntries)) {
return true;
}
$definition = $this->getDefinition($name);
if ($definition === null) {
return false;
}
return true;
}
private function getDefinition(string $name)
{
// Local cache that avoids fetching the same definition twice
if (! array_key_exists($name, $this->fetchedDefinitions)) {
$this->fetchedDefinitions[$name] = $this->definitionSource->getDefinition($name);
}
return $this->fetchedDefinitions[$name];
}
/**
* Resolves a definition.
* Checks for circular dependencies while resolving the definition.
*
* @throws DependencyException Error while resolving the entry.
* @return mixed
*/
private function resolveDefinition(string $entryName, DefinitionInterface $definition, array $parameters = [])
{
return $this->definitionResolver->resolve($definition, $parameters);
}
protected function setDefinition(string $name, DefinitionInterface $definition)
{
// Clear existing entry if it exists
if (array_key_exists($name, $this->resolvedEntries)) {
unset($this->resolvedEntries[$name]);
}
$this->fetchedDefinitions = []; // Completely clear this local cache
$this->definitionSource->addDefinition($definition);
}
public function getProxyFactory(): ProxyFactory
{
return $this->proxyFactory;
}
/**
* Init defined dependencies, not include dynamic definition.
*
* @throws DependencyException Error while resolving the entry.
*/
public function initDependencies(): void
{
$definitions = $this->definitionSource->getDefinitions();
foreach ($definitions as $key => $definition) {
$this->get($key);
}
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
interface DefinitionInterface
{
/**
* Returns the name of the entry in the container.
*/
public function getName(): string;
/**
* Set the name of the entry in the container.
*/
public function setName(string $name);
/**
* Determine if the definition need to transfer to a proxy class.
*/
public function isNeedProxy(): bool;
/**
* Definitions can be cast to string for debugging information.
*/
public function __toString(): string;
}

View File

@ -1,238 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
use App\Controllers\IndexController;
use Hyperflex\Di\Annotation\AnnotationCollector;
use Hyperflex\Di\Annotation\AspectCollector;
use Hyperflex\Di\Annotation\Inject;
use Hyperflex\Di\Annotation\Scanner;
use Hyperflex\Di\ReflectionManager;
use ReflectionFunctionAbstract;
use Symfony\Component\Finder\Finder;
use function class_exists;
use function count;
use function explode;
use function fclose;
use function feof;
use function fgets;
use function file_exists;
use function file_put_contents;
use function filemtime;
use function fopen;
use function implode;
use function interface_exists;
use function is_array;
use function is_callable;
use function is_dir;
use function is_readable;
use function is_string;
use function md5;
use function method_exists;
use function print_r;
use function trim;
class DefinitionSource implements DefinitionSourceInterface
{
/**
* Path of annotation meta data cache
*
* @var string
*/
private $cachePath = BASE_PATH . '/runtime/container/annotations.cache';
/**
* @var array
*/
private $source;
/**
* @var Scanner
*/
private $scanner;
public function __construct(array $source, array $scanDir, Scanner $scanner)
{
// Format relative paths into absolute paths
$scanDir = array_map(function ($value) {
return BASE_PATH . '/' . $value;
}, $scanDir);
$this->scanner = $scanner;
// Scan the specified paths and collect the ast and annotations.
$this->scan($scanDir);
$this->source = $this->normalizeSource($source);
}
/**
* Returns the DI definition for the entry name.
*
* @return DefinitionInterface|null
*/
public function getDefinition(string $name): ?DefinitionInterface
{
if (! isset($this->source[$name])) {
$this->source[$name] = $this->autowire($name);
}
return $this->source[$name];
}
/**
* @return array Definitions indexed by their name.
*/
public function getDefinitions(): array
{
return $this->source;
}
public function addDefinition(string $name, array $definition): self
{
$this->source[$name] = $definition;
return $this;
}
public function clearDefinitions(): void
{
$this->source = [];
}
/**
* Read the type-hinting from the parameters of the function.
*/
private function getParametersDefinition(ReflectionFunctionAbstract $constructor): array
{
$parameters = [];
foreach ($constructor->getParameters() as $index => $parameter) {
// Skip optional parameters
if ($parameter->isOptional()) {
continue;
}
$parameterClass = $parameter->getClass();
if ($parameterClass) {
$parameters[$index] = new Reference($parameterClass->getName());
}
}
return $parameters;
}
/**
* Normaliaze the user definition source to a standard definition souce.
*/
private function normalizeSource(array $source): array
{
$definitions = [];
foreach ($source as $identifier => $definition) {
if (is_string($definition) && class_exists($definition)) {
if (method_exists($definition, '__invoke')) {
$definitions[$identifier] = new FactoryDefinition($identifier, $definition, []);
} else {
$definitions[$identifier] = $this->autowire($identifier, new ObjectDefinition($identifier, $definition));
}
} elseif (is_array($definition) && is_callable($definition)) {
$definitions[$identifier] = new FactoryDefinition($identifier, $definition, []);
}
}
return $definitions;
}
private function autowire(string $name, ObjectDefinition $definition = null): ObjectDefinition
{
$className = $definition ? $definition->getClassName() : $name;
if (! class_exists($className) && ! interface_exists($className)) {
return $definition;
}
$definition = $definition ? : new ObjectDefinition($name);
// Constructor
$class = ReflectionManager::reflectClass($className);
$constructor = $class->getConstructor();
if ($constructor && $constructor->isPublic()) {
$constructorInjection = new MethodInjection('__construct', $this->getParametersDefinition($constructor));
$definition->completeConstructorInjection($constructorInjection);
}
// Properties
$propertiesMetadata = AnnotationCollector::get($className);
if (isset($propertiesMetadata['_p'])) {
foreach ($propertiesMetadata['_p'] as $propertyName => $value) {
if (! isset($value[Inject::class])) {
continue;
}
$propertyInjection = new PropertyInjection($propertyName, new Reference($value[Inject::class]));
$definition->addPropertyInjection($propertyInjection);
}
}
if ($className === IndexController::class) {
$definition->setNeedProxy(true);
}
return $definition;
}
private function scan(array $paths): bool
{
$pathsHash = md5(implode(',', $paths));
if ($this->hasAvailableCache($paths, $pathsHash, $this->cachePath)) {
$this->printLn('Detected an available cache, skip the scan process.');
list(, $annotationMetadata, $aspectMetadata) = explode(PHP_EOL, file_get_contents($this->cachePath));
// Deserialize metadata when the cache is valid
AnnotationCollector::deserialize($annotationMetadata);
AspectCollector::deserialize($aspectMetadata);
return false;
}
$this->printLn('Scanning ...');
$this->scanner->scan($paths);
if (! file_exists($this->cachePath)) {
$exploded = explode('/', $this->cachePath);
unset($exploded[count($exploded) - 1]);
$dirPath = implode('/', $exploded);
if (! is_dir($dirPath)) {
mkdir($dirPath, 0755, true);
}
}
$data = implode(PHP_EOL, [$pathsHash, AnnotationCollector::serialize(), AspectCollector::serialize()]);
file_put_contents($this->cachePath, $data);
$this->printLn('Scan completed.');
return true;
}
private function hasAvailableCache(array $paths, string $pathsHash, string $filename): bool
{
if (! file_exists($filename) || ! is_readable($filename)) {
return false;
}
$handler = fopen($filename, 'r');
while (! feof($handler)) {
$line = fgets($handler);
if (trim($line) !== $pathsHash) {
return false;
}
break;
}
fclose($handler);
$cacheLastModified = filemtime($filename) ?? 0;
$finder = new Finder();
$finder->files()->in($paths)->name('*.php');
foreach ($finder as $file) {
if ($file->getMTime() > $cacheLastModified) {
return false;
}
}
return true;
}
private function printLn(string $message): void
{
print_r($message . PHP_EOL);
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
use Hyperflex\Di\Exception\InvalidDefinitionException;
interface DefinitionSourceInterface
{
/**
* Returns the DI definition for the entry name.
*
* @throws InvalidDefinitionException An invalid definition was found.
* @return array|null
*/
public function getDefinition(string $name);
/**
* @return array Definitions indexed by their name.
*/
public function getDefinitions(): array;
/**
* @return $this
*/
public function addDefinition(string $name, array $definition);
public function clearDefinitions(): void;
}

View File

@ -1,96 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
use Hyperflex\Di\Aop\AstCollector;
class FactoryDefinition implements DefinitionInterface
{
/**
* @var string
*/
private $name;
/**
* @var callable
*/
private $factory;
/**
* @var mixed[]
*/
private $parameters = [];
/**
* @var array|null
*/
private $ast;
/**
* @var bool
*/
private $needProxy = false;
public function __construct(string $name, $factory, array $parameters = [])
{
$this->name = $name;
$this->factory = $factory;
$this->parameters = $parameters;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* @return callable|string
*/
public function getFactory()
{
return $this->factory;
}
/**
* @return mixed[]
*/
public function getParameters(): array
{
return $this->parameters;
}
public function getAst(): array
{
if (null === $this->ast) {
$this->ast = AstCollector::get($this->getFactory(), []);
}
return $this->ast;
}
public function __toString(): string
{
return 'Factory';
}
/**
* Determine if the definition need to transfer to a proxy class.
*/
public function isNeedProxy(): bool
{
return $this->needProxy;
}
public function setNeedProxy($needProxy): self
{
$this->needProxy = $needProxy;
return $this;
}
}

View File

@ -1,75 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
class MethodInjection implements DefinitionInterface
{
/**
* @var string
*/
private $methodName;
/**
* @var mixed[]
*/
private $parameters = [];
public function __construct(string $methodName, array $parameters = [])
{
$this->parameters = $parameters;
$this->methodName = $methodName;
}
public function getName(): string
{
return '';
}
public function setName(string $name)
{
// The name does not matter for method injections, so do nothing.
}
/**
* @return mixed[]
*/
public function getParameters() : array
{
return $this->parameters;
}
public function merge(self $definition)
{
// In case of conflicts, the current definition prevails.
$this->parameters = $this->parameters + $definition->parameters;
}
public function __toString(): string
{
return sprintf('method(%s)', $this->methodName);
}
/**
* Reset the target should be resolved.
* If it is the FactoryDefinition, then the target means $factory property,
* If it is the ObjectDefinition, then the target means $className property.
*
* @param mixed $value
*/
public function setTarget($value)
{
$this->methodName = $value;
}
/**
* Determine if the definition need to transfer to a proxy class.
*/
public function isNeedProxy(): bool
{
// Method injection does not has proxy.
return false;
}
}

View File

@ -1,176 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
use Hyperflex\Di\ReflectionManager;
class ObjectDefinition implements DefinitionInterface
{
/**
* @var string
*/
private $name;
/**
* @var string|null
*/
private $className;
/**
* @var MethodInjection
*/
protected $constructorInjection;
/**
* @var PropertyInjection[]
*/
protected $propertyInjections = [];
protected $methodInjections = [];
/**
* @var bool
*/
private $classExists = false;
/**
* @var bool
*/
private $instantiable = false;
/**
* @var bool
*/
private $needProxy = false;
/**
* @var string
*/
private $proxyClassName;
public function __construct(string $name, string $className = null)
{
$this->name = $name;
$this->setClassName($className ?? $name);
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function setClassName(string $className = null)
{
$this->className = $className;
$this->updateCache();
}
public function getClassName(): string
{
if ($this->className !== null) {
return $this->className;
}
return $this->name;
}
public function isClassExists(): bool
{
return $this->classExists;
}
public function isInstantiable(): bool
{
return $this->instantiable;
}
/**
* @return \Hyperflex\Di\Definition\MethodInjection|null
*/
public function getConstructorInjection()
{
return $this->constructorInjection;
}
public function setConstructorInjection(MethodInjection $injection)
{
$this->constructorInjection = $injection;
}
public function completeConstructorInjection(MethodInjection $injection)
{
if ($this->constructorInjection !== null) {
// Merge
$this->constructorInjection->merge($injection);
} else {
// Set
$this->constructorInjection = $injection;
}
}
/**
* @return PropertyInjection[]
*/
public function getPropertyInjections(): array
{
return $this->propertyInjections;
}
public function addPropertyInjection(PropertyInjection $propertyInjection)
{
$this->propertyInjections[$propertyInjection->getPropertyName()] = $propertyInjection;
}
public function getProxyClassName(): string
{
return $this->proxyClassName;
}
public function setProxyClassName($proxyClassName): self
{
$this->proxyClassName = $proxyClassName;
return $this;
}
private function updateCache()
{
$className = $this->getClassName();
$this->classExists = class_exists($className) || interface_exists($className);
if (! $this->classExists) {
$this->instantiable = false;
return;
}
$this->instantiable = ReflectionManager::reflectClass($className)->isInstantiable();
}
/**
* Determine if the definition need to transfer to a proxy class.
*/
public function isNeedProxy(): bool
{
return $this->needProxy;
}
public function setNeedProxy($needProxy): self
{
$this->needProxy = $needProxy;
return $this;
}
public function __toString(): string
{
return sprintf('Object[%s]' . $this->getClassName());
}
}

View File

@ -1,93 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
class PropertyDefinition implements DefinitionInterface
{
/**
* Property name.
*
* @var string
*/
private $propertyName;
/**
* Value that should be injected in the property.
*
* @var mixed
*/
private $value;
/**
* Use for injecting in properties of parent classes: the class name
* must be the name of the parent class because private properties
* can be attached to the parent classes, not the one we are resolving.
* @var string|null
*/
private $className;
/**
* PropertyDefinition constructor.
*
* @param string $propertyName
* @param mixed $value
* @param string|null $className
*/
public function __construct(string $propertyName, $value, ?string $className = null)
{
$this->propertyName = $propertyName;
$this->value = $value;
$this->className = $className;
}
/**
* Returns the name of the entry in the container.
*/
public function getName(): string
{
return $this->propertyName;
}
/**
* Set the name of the entry in the container.
*/
public function setName(string $name)
{
$this->propertyName = $name;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* @return string|null
*/
public function getClassName(): ?string
{
return $this->className;
}
/**
* Determine if the definition need to transfer to a proxy class.
*/
public function isNeedProxy(): bool
{
return false;
}
/**
* Definitions can be cast to string for debugging information.
*/
public function __toString(): string
{
return 'Property';
}
}

View File

@ -1,45 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
/**
* Describe an injection in a class property.
*/
class PropertyInjection
{
/**
* Property name.
* @var string
*/
private $propertyName;
/**
* Value that should be injected in the property.
* @var mixed
*/
private $value;
/**
* @param string $propertyName Property name
* @param mixed $value Value that should be injected in the property
*/
public function __construct(string $propertyName, $value)
{
$this->propertyName = $propertyName;
$this->value = $value;
}
public function getPropertyName() : string
{
return $this->propertyName;
}
/**
* @return mixed Value that should be injected in the property
*/
public function getValue()
{
return $this->value;
}
}

View File

@ -1,87 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
use Psr\Container\ContainerInterface;
class Reference implements DefinitionInterface, SelfResolvingDefinitionInterface
{
/**
* Entry name.
*
* @var string
*/
private $name = '';
/**
* Name of the target entry.
*
* @var string
*/
private $targetEntryName;
/**
* @var bool
*/
private $needProxy = false;
public function __construct(string $targetEntryName)
{
$this->targetEntryName = $targetEntryName;
}
/**
* Returns the name of the entry in the container.
*/
public function getName(): string
{
return $this->name;
}
/**
* Set the name of the entry in the container.
*/
public function setName(string $name)
{
$this->name = $name;
}
public function getTargetEntryName(): string
{
return $this->targetEntryName;
}
public function resolve(ContainerInterface $container)
{
return $container->get($this->getTargetEntryName());
}
public function isResolvable(ContainerInterface $container): bool
{
return $container->has($this->getTargetEntryName());
}
/**
* Definitions can be cast to string for debugging information.
*/
public function __toString(): string
{
return sprintf('get(%s)', $this->targetEntryName);
}
/**
* Determine if the definition need to transfer to a proxy class.
*/
public function isNeedProxy(): bool
{
return $this->needProxy;
}
public function setNeedProxy($needProxy): self
{
$this->needProxy = $needProxy;
return $this;
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Hyperflex\Di\Definition;
use Psr\Container\ContainerInterface;
interface SelfResolvingDefinitionInterface
{
/**
* Resolve the definition and return the resulting value.
*
* @return mixed
*/
public function resolve(ContainerInterface $container);
/**
* Check if a definition can be resolved.
*/
public function isResolvable(ContainerInterface $container): bool;
}

View File

@ -1,9 +0,0 @@
<?php
namespace Hyperflex\Di\Exception;
class DependencyException extends Exception
{
}

View File

@ -1,9 +0,0 @@
<?php
namespace Hyperflex\Di\Exception;
class Exception extends \Exception
{
}

View File

@ -1,14 +0,0 @@
<?php
namespace Hyperflex\Di\Exception;
use Hyperflex\Di\Definition\DefinitionInterface;
class InvalidDefinitionException extends Exception
{
public static function create(DefinitionInterface $definition, string $message, \Exception $previous = null): self
{
return new self(sprintf('%s' . PHP_EOL . 'Full definition:' . PHP_EOL . '%s', $message, (string)$definition), 0, $previous);
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Hyperflex\Di\Exception;
use Psr\Container\NotFoundExceptionInterface;
class NotFoundException extends Exception implements NotFoundExceptionInterface
{
}

View File

@ -1,65 +0,0 @@
<?php
namespace Hyperflex\Di;
use Hyperflex\Utils\Arr;
abstract class MetadataCollector implements MetadataCollectorInterface
{
/**
* Subclass MUST override this property.
*
* @var array
*/
protected static $container = [];
/**
* Retrieve the metadata via key.
*/
public static function get(string $key, $default = null)
{
return Arr::get(static::$container, $key) ?? $default;
}
/**
* Set the metadata to holder.
*/
public static function set(string $key, $value): void
{
Arr::set(static::$container, $key, $value);
}
/**
* Determine if the metadata exist.
* If exist will return true, otherwise return false.
*/
public static function has(string $key): bool
{
return Arr::has(static::$container, $key);
}
/**
* Serialize the all metadata to a string.
*/
public static function serialize(): string
{
return serialize(static::$container);
}
/**
* Deserialize the serialized metadata and set the metadata to holder.
*/
public static function deserialize(string $metadata): bool
{
$data = unserialize($metadata);
static::$container = $data;
return true;
}
public static function getContainer(): array
{
return static::$container;
}
}

View File

@ -1,30 +0,0 @@
<?php
namespace Hyperflex\Di;
interface MetadataCollectorInterface
{
/**
* Retrieve the metadata via key.
*/
public static function get(string $key, $default = null);
/**
* Set the metadata to holder.
*/
public static function set(string $key, $value): void;
/**
* Serialize the all metadata to a string.
*/
public static function serialize(): string;
/**
* Deserialize the serialized metadata and set the metadata to holder.
*/
public static function deserialize(string $metadata): bool;
}

View File

@ -1,63 +0,0 @@
<?php
namespace Hyperflex\Di;
class MethodDefinitionCollector extends MetadataCollector
{
/**
* @var array
*/
protected static $container = [];
/**
* Get the method definition from metadata container,
* If the metadata not exist in container, then will
* parse it and save into container, and then return it.
*/
public static function getOrParse(string $class, string $method): array
{
$key = $class . '::' . $method;
if (static::has($key)) {
return static::get($key);
}
$parameters = ReflectionManager::reflectMethod($class, $method)->getParameters();
$definitions = [];
foreach ($parameters as $parameter) {
$type = $parameter->getType()->getName();
switch ($type) {
case 'int':
case 'float':
case 'string':
$definitions[] = [
'type' => $type,
'name' => $parameter->getName(),
'ref' => '',
'allowsNull' => $parameter->allowsNull(),
];
break;
case 'bool':
$definitions[] = [
'type' => $type,
'name' => $parameter->getName(),
'ref' => '',
'allowsNull' => $parameter->allowsNull(),
];
break;
default:
// Object
$definitions[] = [
'type' => 'object',
'name' => $parameter->getName(),
'ref' => $parameter->getClass()->getName() ?? null,
'allowsNull' => $parameter->allowsNull(),
];
break;
}
}
static::set($key, $definitions);
return $definitions;
}
}

View File

@ -1,77 +0,0 @@
<?php
namespace Hyperflex\Di;
use Hyperflex\Di\Aop\Ast;
use Hyperflex\Di\Definition\FactoryDefinition;
use Hyperflex\Di\Definition\ObjectDefinition;
use Hyperflex\Utils\CoroutineLocker;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard;
use Psr\Container\ContainerInterface;
class ProxyFactory
{
private static $map = [];
/**
* @var Ast
*/
private $ast;
public function __construct(ContainerInterface $container)
{
$parserFactory = new ParserFactory();
$astParser = $parserFactory->create(ParserFactory::ONLY_PHP7);
$this->ast = new Ast($astParser, new Standard());
}
public function createProxyDefinition(ObjectDefinition $definition): ObjectDefinition
{
$identifier = $definition->getName();
if (isset(static::$map[$identifier])) {
return static::$map[$identifier];
}
$proxyIdentifier = null;
if ($definition instanceof FactoryDefinition) {
$proxyIdentifier = $definition->getFactory() . '_' . md5($definition->getFactory());
$proxyIdentifier && $definition->setTarget($proxyIdentifier);
$this->loadProxy($definition->getName(), $definition->getFactory());
} elseif ($definition instanceof ObjectDefinition) {
$proxyIdentifier = $definition->getClassName() . '_' . md5($definition->getClassName());
$definition->setProxyClassName($proxyIdentifier);
$this->loadProxy($definition->getClassName(), $definition->getProxyClassName());
}
static::$map[$identifier] = $definition;
return static::$map[$identifier];
}
private function loadProxy(string $className, string $proxyClassName)
{
$dir = BASE_PATH . '/runtime/container/proxy/';
if (! file_exists($dir)) {
mkdir($dir, 0755, true);
}
$proxyFileName = str_replace('\\', '_', $proxyClassName);
$path = $dir . $proxyFileName . '.proxy.php';
$key = md5($path);
if (CoroutineLocker::lock($key)) {
// @TODO handle unlink mechanism.
@unlink($path);
if (! file_exists($path)) {
$this->createProxyFile($path, $className, $proxyClassName);
}
include_once $path;
CoroutineLocker::unlock($key);
}
}
private function createProxyFile(string $path, string $className, string $proxyClassName)
{
$code = $this->ast->proxy($className, $proxyClassName);
file_put_contents($path, $code);
}
}

View File

@ -1,60 +0,0 @@
<?php
namespace Hyperflex\Di;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;
class ReflectionManager extends MetadataCollector
{
/**
* @var array
*/
protected static $container = [];
public static function reflectClass(string $className): ReflectionClass
{
if (! isset(static::$container['class'][$className])) {
if (! class_exists($className) && ! interface_exists($className)) {
throw new InvalidArgumentException("Class $className not exist");
}
static::$container['class'][$className] = new ReflectionClass($className);
}
return static::$container['class'][$className];
}
public static function reflectMethod(string $className, string $method): ReflectionMethod
{
$key = $className . '::' . $method;
if (! isset(static::$container['method'][$key])) {
// TODO check interface_exist
if (! class_exists($className)) {
throw new InvalidArgumentException("Class $className not exist");
}
static::$container['method'][$key] = static::reflectClass($className)->getMethod($method);
}
return static::$container['method'][$key];
}
public static function reflectProperty(string $className, string $property): ReflectionProperty
{
$key = $className . '::' . $property;
if (! isset(static::$container['property'][$key])) {
if (! class_exists($className)) {
throw new InvalidArgumentException("Class $className not exist");
}
static::$container['property'][$key] = static::reflectClass($className)->getProperty($property);
}
return static::$container['property'][$key];
}
public static function clear()
{
static::$container = [];
}
}

View File

@ -1,88 +0,0 @@
<?php
namespace Hyperflex\Di\Resolver;
use DI\Invoker\FactoryParameterResolver;
use Hyperflex\ApplicationFactory;
use Hyperflex\Di\Definition\DefinitionInterface;
use Hyperflex\Di\Definition\FactoryDefinition;
use Hyperflex\Di\Exception\InvalidDefinitionException;
use Invoker\Exception\NotCallableException;
use Invoker\Exception\NotEnoughParametersException;
use Invoker\Invoker;
use Invoker\ParameterResolver\AssociativeArrayResolver;
use Invoker\ParameterResolver\DefaultValueResolver;
use Invoker\ParameterResolver\NumericArrayResolver;
use Invoker\ParameterResolver\ResolverChain;
use Psr\Container\ContainerInterface;
class FactoryResolver implements ResolverInterface
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var ResolverInterface
*/
private $resolver;
/**
* @var \Invoker\InvokerInterface
*/
private $invoker;
public function __construct(ContainerInterface $container, ResolverInterface $resolver)
{
$this->container = $container;
$this->resolver = $resolver;
}
/**
* Resolve a factory definition to a value.
*
* @param FactoryDefinition $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @throws InvalidDefinitionException If the definition cannot be resolved.
* @return mixed Value obtained from the definition.
*/
public function resolve(DefinitionInterface $definition, array $parameters = [])
{
$callable = null;
try {
$callable = $definition->getFactory();
if (! method_exists($callable, '__invoke')) {
throw new NotCallableException();
}
if (is_string($callable)) {
$callable = $this->container->get($callable);
$object = $callable($this->container);
} else {
$object = call($callable, [$this->container]);
}
return $object;
} catch (NotCallableException $e) {
// Custom error message to help debugging
if (is_string($callable) && class_exists($callable) && method_exists($callable, '__invoke')) {
throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s. Invokable classes cannot be automatically resolved if autowiring is disabled on the container, you need to enable autowiring or define the entry manually.', $definition->getName(), $e->getMessage()));
}
throw new InvalidDefinitionException(sprintf('Entry "%s" cannot be resolved: factory %s', $definition->getName(), $e->getMessage()));
}
}
/**
* Check if a definition can be resolved.
*
* @param DefinitionInterface $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @return bool
*/
public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool
{
return true;
}
}

View File

@ -1,138 +0,0 @@
<?php
namespace Hyperflex\Di\Resolver;
use App\Controllers\IndexController;
use Hyperflex\Di\Container;
use Hyperflex\Di\Definition\DefinitionInterface;
use Hyperflex\Di\Definition\ObjectDefinition;
use Hyperflex\Di\Definition\PropertyInjection;
use Hyperflex\Di\Definition\Reference;
use Hyperflex\Di\Exception\DependencyException;
use Hyperflex\Di\Exception\InvalidDefinitionException;
use Hyperflex\Di\ReflectionManager;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use ReflectionMethod;
class ObjectResolver implements ResolverInterface
{
private $proxyFactory;
/**
* @var ParameterResolver
*/
private $parameterResolver;
/**
* @var ResolverInterface
*/
private $definitionResolver;
/**
* @var ContainerInterface
*/
private $container;
/**
* ObjectResolver constructor.
*
* @param Container $container
* @param ResolverInterface $definitionResolver
*/
public function __construct(ContainerInterface $container, ResolverInterface $definitionResolver)
{
$this->container = $container;
$this->definitionResolver = $definitionResolver;
$this->proxyFactory = $container->getProxyFactory();
$this->parameterResolver = new ParameterResolver($definitionResolver);
}
/**
* Resolve a definition to a value.
*
* @param DefinitionInterface $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @return mixed Value obtained from the definition.
* @throws DependencyException
* @throws InvalidDefinitionException
*/
public function resolve(DefinitionInterface $definition, array $parameters = [])
{
return $this->createInstance($definition, $parameters);
}
/**
* Check if a definition can be resolved.
*
* @param ObjectDefinition $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @return bool
*/
public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool
{
return $definition->isInstantiable();
}
private function createInstance(ObjectDefinition $definition, array $parameters)
{
// Check that the class is instantiable
if (! $definition->isInstantiable()) {
// Check that the class exists
if (! $definition->isClassExists()) {
throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class doesn\'t exist', $definition->getName()));
}
throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: the class is not instantiable', $definition->getName()));
}
$classReflection = null;
try {
$className = $definition->getClassName();
if ($definition->isNeedProxy()) {
$definition = $this->proxyFactory->createProxyDefinition($definition);
$className = $definition->getProxyClassName();
}
$classReflection = ReflectionManager::reflectClass($className);
$constructorInjection = $definition->getConstructorInjection();
$args = $this->parameterResolver->resolveParameters($constructorInjection, $classReflection->getConstructor(), $parameters);
$object = new $className(...$args);
$this->injectMethodsAndProperties($object, $definition);
} catch (NotFoundExceptionInterface $e) {
throw new DependencyException(sprintf('Error while injecting dependencies into %s: %s', $classReflection ? $classReflection->getName() : '', $e->getMessage()), 0, $e);
} catch (InvalidDefinitionException $e) {
throw InvalidDefinitionException::create($definition, sprintf('Entry "%s" cannot be resolved: %s', $definition->getName(), $e->getMessage()));
}
return $object;
}
protected function injectMethodsAndProperties($object, ObjectDefinition $objectDefinition)
{
// Property injections
foreach ($objectDefinition->getPropertyInjections() as $propertyInjection) {
$this->injectProperty($object, $propertyInjection);
}
}
private function injectProperty($object, PropertyInjection $propertyInjection)
{
$property = ReflectionManager::reflectProperty(get_class($object), $propertyInjection->getPropertyName());
if ($property->isStatic()) {
return;
}
if (! $property->isPublic()) {
$property->setAccessible(true);
}
if (! $propertyInjection->getValue() instanceof Reference) {
return;
}
/** @var Reference $reference */
$reference = $propertyInjection->getValue();
$property->setValue($object, $this->container->get($reference->getTargetEntryName()));
}
}

View File

@ -1,88 +0,0 @@
<?php
namespace Hyperflex\Di\Resolver;
use Hyperflex\Di\Definition\DefinitionInterface;
use Hyperflex\Di\Definition\MethodInjection;
use Hyperflex\Di\Exception\InvalidDefinitionException;
use ReflectionMethod;
use ReflectionParameter;
class ParameterResolver
{
/**
* @var DefinitionInterface
*/
private $definitionResolver;
public function __construct(ResolverInterface $definitionResolver)
{
$this->definitionResolver = $definitionResolver;
}
public function resolveParameters(
MethodInjection $definition = null,
ReflectionMethod $method = null,
array $parameters = []
) {
$args = [];
if (! $method) {
return $args;
}
$definitionParameters = $definition ? $definition->getParameters() : [];
foreach ($method->getParameters() as $index => $parameter) {
if (array_key_exists($parameter->getName(), $parameters)) {
$value = &$parameters[$parameter->getName()];
} elseif (array_key_exists($index, $definitionParameters)) {
$value = &$definitionParameters[$index];
} else {
if ($parameter->isDefaultValueAvailable() || $parameter->isOptional()) {
$args[] = $this->getParameterDefaultValue($parameter, $method);
continue;
}
throw new InvalidDefinitionException(sprintf(
'Parameter $%s of %s has no value defined or guessable',
$parameter->getName(),
$this->getFunctionName($method)
));
}
// Nested definitions
if ($value instanceof DefinitionInterface) {
// If the container cannot produce the entry, we can use the default parameter value
if ($parameter->isOptional() && ! $this->definitionResolver->isResolvable($value)) {
$value = $this->getParameterDefaultValue($parameter, $method);
} else {
$value = $this->definitionResolver->resolve($value);
}
}
$args[] = &$value;
}
return $args;
}
private function getParameterDefaultValue(ReflectionParameter $parameter, ReflectionMethod $function)
{
try {
return $parameter->getDefaultValue();
} catch (\ReflectionException $e) {
throw new InvalidDefinitionException(sprintf(
'The parameter "%s" of %s has no type defined or guessable. It has a default value, '
. 'but the default value can\'t be read through Reflection because it is a PHP internal class.',
$parameter->getName(),
$this->getFunctionName($function)
));
}
}
private function getFunctionName(ReflectionMethod $method) : string
{
return $method->getName() . '()';
}
}

View File

@ -1,95 +0,0 @@
<?php
namespace Hyperflex\Di\Resolver;
use App\Controllers\IndexController;
use Hyperflex\ApplicationFactory;
use Hyperflex\Di\Definition\DefinitionInterface;
use Hyperflex\Di\Definition\FactoryDefinition;
use Hyperflex\Di\Definition\ObjectDefinition;
use Hyperflex\Di\Definition\SelfResolvingDefinitionInterface;
use Psr\Container\ContainerInterface;
use RuntimeException;
class ResolverDispatcher implements ResolverInterface
{
/**
* @var ObjectResolver
*/
protected $objectResolver;
/**
* @var FactoryResolver
*/
protected $factoryResolver;
/**
* @var \Psr\Container\ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* Resolve a definition to a value.
*
* @param DefinitionInterface $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @throws InvalidDefinition If the definition cannot be resolved.
* @return mixed Value obtained from the definition.
*/
public function resolve(DefinitionInterface $definition, array $parameters = [])
{
if ($definition instanceof SelfResolvingDefinitionInterface) {
return $definition->resolve($this->container);
}
$resolver = $this->getDefinitionResolver($definition);
return $resolver->resolve($definition, $parameters);
}
/**
* Check if a definition can be resolved.
*
* @param DefinitionInterface $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @return bool
*/
public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool
{
if ($definition instanceof SelfResolvingDefinitionInterface) {
return $definition->isResolvable($this->container);
}
$resolver = $this->getDefinitionResolver($definition);
return $resolver->isResolvable($definition, $parameters);
}
/**
* Returns a resolver capable of handling the given definition.
*
* @throws RuntimeException No definition resolver was found for this type of definition.
*/
private function getDefinitionResolver(DefinitionInterface $definition): ResolverInterface
{
switch (true) {
case $definition instanceof ObjectDefinition:
if (! $this->objectResolver) {
$this->objectResolver = new ObjectResolver($this->container, $this);
}
return $this->objectResolver;
case $definition instanceof FactoryDefinition:
if (! $this->factoryResolver) {
$this->factoryResolver = new FactoryResolver($this->container, $this);
}
return $this->factoryResolver;
default:
throw new RuntimeException('No definition resolver was configured for definition of type ' . get_class($definition));
}
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Hyperflex\Di\Resolver;
use Hyperflex\Di\Definition\DefinitionInterface;
interface ResolverInterface
{
/**
* Resolve a definition to a value.
*
* @param DefinitionInterface $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @throws InvalidDefinition If the definition cannot be resolved.
* @return mixed Value obtained from the definition.
*/
public function resolve(DefinitionInterface $definition, array $parameters = []);
/**
* Check if a definition can be resolved.
*
* @param DefinitionInterface $definition Object that defines how the value should be obtained.
* @param array $parameters Optional parameters to use to build the entry.
* @return bool
*/
public function isResolvable(DefinitionInterface $definition, array $parameters = []): bool;
}

View File

@ -1,73 +0,0 @@
{
"name": "hyperflex/dispatcher",
"description": "A HTTP Server for Hyperflex.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperflex",
"dispatcher",
"middleware",
"filter"
],
"support": {
},
"require": {
"php": ">=7.1",
"psr/http-server-middleware": "^1.0",
"psr/container": "^1.0"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0"
},
"suggest": {
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperflex\\Dispatcher\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"HyperflexTest\\Dispatcher\\": "test"
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": "Hyperflex\\Dispatcher\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
},
"repositories": {
"packagist": {
"type": "composer",
"url": "https://packagist.laravel-china.org"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
bootstrap="./vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="Tests">
<directory suffix="Test.php">./test</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -1,18 +0,0 @@
<?php
namespace Hyperflex\Dispatcher;
abstract class AbstractDispatcher implements DispatcherInterface
{
/**
* @param array ...$params
* @return mixed
*/
public function dispatch(...$params)
{
return $this->handle(...$params);
}
}

View File

@ -1,23 +0,0 @@
<?php
namespace Hyperflex\Dispatcher;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
],
'scan' => [
'paths' => [
"vendor/hyperflex/dispatcher/src"
],
],
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Hyperflex\Dispatcher;
interface DispatcherInterface
{
public function dispatch(...$params);
}

View File

@ -1,9 +0,0 @@
<?php
namespace Hyperflex\Dispatcher\Exceptions;
class InvalidArgumentException extends \InvalidArgumentException
{
}

View File

@ -1,38 +0,0 @@
<?php
namespace Hyperflex\Dispatcher;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class HttpDispatcher extends AbstractDispatcher
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @inheritdoc
*/
public function dispatch(...$params): ResponseInterface
{
/**
* @var RequestInterface $request
* @var array $middlewares
* @var string $coreHandler
*/
[$request, $middlewares, $coreHandler] = $params;
$requestHandler = new HttpRequestHandler($middlewares, $coreHandler, $this->container);
$response = $requestHandler->handle($request);
return $response;
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace Hyperflex\Dispatcher;
use Hyperflex\Dispatcher\Exceptions\InvalidArgumentException;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use function array_unique;
use function is_string;
class HttpRequestHandler implements RequestHandlerInterface
{
private $middlewares = [];
private $offset = 0;
/**
* @var string
*/
private $coreHandler;
/**
* @var ContainerInterface
*/
private $container;
public function __construct(array $middlewares, string $coreHandler, ContainerInterface $container)
{
$this->middlewares = array_unique($middlewares);
$this->coreHandler = $coreHandler;
$this->container = $container;
}
/**
* Handles a request and produces a response.
* May call other collaborating code to generate the response.
*/
public function handle(ServerRequestInterface $request): ResponseInterface
{
if (! isset($this->middlewares[$this->offset]) && ! empty($this->coreHandler)) {
$handler = $this->container->get($this->coreHandler);
} else {
$handler = $this->middlewares[$this->offset];
is_string($handler) && $handler = $this->container->get($handler);
}
if (! method_exists($handler, 'process')) {
throw new InvalidArgumentException(sprintf('Invalid middleware, it have to provide a process() method.'));
}
return $handler->process($request, $this->next());
}
private function next()
{
$this->offset++;
return $this;
}
}

View File

@ -1,48 +0,0 @@
<?php
namespace HyperflexTest\Dispatcher;
use App\Middlewares\TestMiddleware;
use Hyperflex\Dispatcher\HttpDispatcher;
use Hyperflex\Utils\Context;
use HyperflexTest\Dispatcher\Middlewares\CoreMiddleware;
use PHPUnit\Framework\TestCase;
use Prophecy\Prophecy\ProphecyInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
/**
* @property ProphecyInterface container
* @property ProphecyInterface request
* @property ProphecyInterface response
*/
class HttpDispatcherTest extends TestCase
{
protected function setUp()
{
$this->container = $this->prophesize(ContainerInterface::class);
$this->request = $this->prophesize(ServerRequestInterface::class)->reveal();
$this->response = $this->prophesize(ResponseInterface::class)->reveal();
Context::set(ServerRequestInterface::class, $this->request);
Context::set(ResponseInterface::class, $this->response);
}
public function testA()
{
$this->container->get(TestMiddleware::class)->willReturn(new TestMiddleware());
$this->container->get(CoreMiddleware::class)->willReturn(new CoreMiddleware());
$middlewares = [
TestMiddleware::class,
];
$coreHandler = CoreMiddleware::class;
$dispatcher = new HttpDispatcher($middlewares, $coreHandler, $this->container->reveal());
$this->assertInstanceOf(HttpDispatcher::class, $dispatcher);
$response = $dispatcher->dispatch($this->request, $this->response);
$this->assertInstanceOf(ResponseInterface::class, $response);
}
}

View File

@ -1,26 +0,0 @@
<?php
namespace HyperflexTest\Dispatcher\Middlewares;
use Hyperflex\Utils\Context;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
class CoreMiddleware implements MiddlewareInterface
{
/**
* Process an incoming server request and return a response, optionally delegating
* response creation to a handler.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/** @var ResponseInterface $response */
$response = Context::get(ResponseInterface::class);
$response = $response->withAddedHeader('Server', 'Hyperflex');
return $response;
}
}

Binary file not shown.

View File

@ -1,77 +0,0 @@
{
"name": "hyperflex/framework",
"description": "A coroutine framework that focuses on hyperspeed and flexible, specifically use for build microservices and middlewares.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"framework",
"hyperflex",
"microservice",
"middleware"
],
"support": {
},
"require": {
"php": ">=7.1",
"ext-swoole": ">=4.2",
"fig/http-message-util": "^1.1.2",
"psr/container": "^1.0",
"psr/http-message": "^1.0.1",
"psr/http-server-middleware": "^1.0",
"psr/log": "^1.0",
"symfony/console": "^4.2",
"symfony/routing": "^4.2",
"symfony/http-foundation": "^4.2",
"hyperflex/utils": "dev-master"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0",
"symfony/console": "^4.1",
"php-di/php-di": "^6.0",
"doctrine/common": "@stable"
},
"suggest": {
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperflex\\Framework\\": "src"
}
},
"autoload-dev": {
"psr-4": {
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": "Hyperflex\\Framework\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
}
}

View File

@ -1,31 +0,0 @@
<?php
namespace Hyperflex\Framework;
use Hyperflex\Framework\Contracts\ConfigInterface;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Application;
class ApplicationFactory
{
/**
* Define the default commands here.
*
* @var array
*/
private $defaultCommands = [];
public function __invoke(ContainerInterface $container)
{
$config = $container->get(ConfigInterface::class);
$commands = $config->get('commands', []);
$commands = array_replace($this->defaultCommands, $commands);
$application = new Application();
foreach ($commands as $command) {
$application->add($container->get($command));
}
return $application;
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace Hyperflex\Framework;
interface ApplicationInterface
{
}

View File

@ -1,37 +0,0 @@
<?php
namespace Hyperflex\Framework\Bootstrap;
use Hyperflex\Memory;
use Hyperflex\Framework\Server;
use Psr\Container\ContainerInterface;
use Swoole\Server as SwooleServer;
use Hyperflex\Framework\Constants\SwooleEvent;
class ServerStartCallback
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var Server
*/
private $server;
public function __construct(ContainerInterface $container, Server $server)
{
$this->container = $container;
$this->server = $server;
}
public function onStart(SwooleServer $server)
{
Memory\LockManager::initialize(SwooleEvent::ON_WORKER_START, SWOOLE_RWLOCK);
Memory\AtomicManager::initialize(SwooleEvent::ON_WORKER_START);
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace Hyperflex\Framework\Bootstrap;
use Hyperflex\Framework\Constants\SwooleEvent;
use Hyperflex\Framework\Contracts\StdoutLoggerInterface;
use Hyperflex\Di\Container;
use Hyperflex\Memory\AtomicManager;
use Hyperflex\Memory\LockManager;
use Psr\Container\ContainerInterface;
use Swoole\Server as SwooleServer;
class WorkerStartCallback
{
/**
* @var Container
*/
private $container;
/**
* @var StdoutLoggerInterface
*/
private $logger;
public function __construct(ContainerInterface $container, StdoutLoggerInterface $logger)
{
$this->container = $container;
$this->logger = $logger;
}
/**
* Handle Swoole onWorkerStart event.
*/
public function onWorkerStart(SwooleServer $server, int $workerId)
{
// Atomic and Lock have to initializes before worker start.
$atomic = AtomicManager::get(SwooleEvent::ON_WORKER_START);
$lock = LockManager::get(SwooleEvent::ON_WORKER_START);
$isScan = false;
$lockedWorkerId = null;
if ($lock->trylock()) {
$lockedWorkerId = $workerId;
// Only running in one worker.
$this->logger->debug("Worker $lockedWorkerId got the lock.");
// @TODO Do something that you want only one worker do.
$lock->unlock();
$atomic->wakeup($server->setting['worker_num'] - 1);
} else {
$this->logger->debug("Worker $workerId wating ...");
$atomic->wait();
}
if (! $isScan || $workerId !== $lockedWorkerId) {
// @TODO Do something that the workers who does not got the lock should do.
}
$this->logger->info("Worker $workerId started.");
LockManager::clear(SwooleEvent::ON_WORKER_START);
AtomicManager::clear(SwooleEvent::ON_WORKER_START);
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Hyperflex\Framework;
use Hyperflex\Framework\ApplicationFactory;
use Hyperflex\Framework\Contracts\StdoutLoggerInterface;
use Hyperflex\Framework\Logger\StdoutLogger;
use Symfony\Component\Console\Application;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
ApplicationInterface::class => ApplicationFactory::class,
StdoutLoggerInterface::class => StdoutLogger::class,
],
'scan'=>[
'paths'=>[
'vendor/hyperflex/framework/src'
]
]
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Hyperflex\Framework\Constants;
class SwooleEvent
{
const ON_START = 'start';
const ON_WORKER_START = 'workerStart';
const ON_REQUEST = 'request';
}

View File

@ -1,36 +0,0 @@
<?php
namespace Hyperflex\Framework\Contracts;
interface ConfigInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $key Identifier of the entry to look for.
* @param mixed $default Default value of the entry when does not found.
* @return mixed Entry.
*/
public function get(string $key, $default);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* @param string $keys Identifier of the entry to look for.
* @return bool
*/
public function has(string $keys);
/**
* Set a value to the container by its identifier.
*
* @param string $key Identifier of the entry to set.
* @param mixed $value The value that save to container.
* @return void
*/
public function set(string $key, $value);
}

View File

@ -1,11 +0,0 @@
<?php
namespace Hyperflex\Framework\Contracts;
use Psr\Log\LoggerInterface;
interface StdoutLoggerInterface extends LoggerInterface
{
}

View File

@ -1,39 +0,0 @@
<?php
namespace Hyperflex\Framework\DependencyInjection;
use function \DI\factory as factory;
use function \DI\autowire as autowire;
use Hyperflex\Bootstrap\WorkerStartCallback;
use Hyperflex\Di\Definition\FactoryDefinition;
use Hyperflex\Di\Definition\MethodInjection;
use Hyperflex\Di\Definition\ObjectDefinition;
use Hyperflex\Di\Definition\Reference;
use Hyperflex\Di\ReflectionManager;
use function is_string;
use function is_array;
use function is_callable;
class Definition
{
/**
* Adapte more useful difinition syntax.
*/
public static function reorganizeDefinitions(array $definitions): array
{
foreach ($definitions as $identifier => $definition) {
if (is_string($definition) && class_exists($definition)) {
if (method_exists($definition, '__invoke')) {
$definitions[$identifier] = factory($definition);
} else {
$definitions[$identifier] = autowire($definition);
}
} elseif (is_array($definition) && is_callable($definition)) {
$definitions[$identifier] = factory($definition);
}
}
return $definitions;
}
}

View File

@ -1,51 +0,0 @@
<?php
namespace Hyperflex\Framework;
use Psr\Http\Message\ResponseInterface;
use Throwable;
abstract class ExceptionHandler
{
/**
* Determine if the exception should propagate to next handler.
*
* @var bool
*/
protected $propagationStopped = false;
/**
* Handle the exception, and return the specified result.
*/
abstract public function handle(Throwable $throwable, ResponseInterface $response);
/**
* Determine if the current exception handler should handle the exception,
*
* @return bool
* If return true, then this exception handler will handle the exception,
* If return false, then delegate to next handler.
*/
abstract public function isValid(Throwable $throwable): bool;
/**
* Stop propagate the exception to next handler.
*/
public function stopPropagation(): bool
{
$this->propagationStopped = true;
return $this->propagationStopped;
}
/**
* Is propagation stopped ?
* This will typically only be used by the handler to determine if the
* provious handler halted propagation.
*/
public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
}

View File

@ -1,59 +0,0 @@
<?php
namespace Hyperflex\Framework;
use Hyperflex\Dispatcher\AbstractDispatcher;
use Hyperflex\Utils\Context;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class ExceptionHandlerDispatcher extends AbstractDispatcher
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
/**
* @inheritdoc
*/
public function dispatch(...$params): ResponseInterface
{
return parent::dispatch(...$params);
}
/**
* @inheritdoc
*/
public function handle(...$params)
{
/**
* @var Throwable $throwable
* @var string[] $handlers
*/
[$throwable, $handlers] = $params;
$response = Context::get(ResponseInterface::class);
foreach ($handlers as $handler) {
if (! $this->container->has($handler)) {
throw new \InvalidArgumentException(sprintf('Invalid exception handler %s.', $handler));
}
$handlerInstance = $this->container->get($handler);
if (! $handlerInstance instanceof ExceptionHandler || ! $handlerInstance->isValid($throwable)) {
continue;
}
$response = $handlerInstance->handle($throwable, $response);
if ($handlerInstance->isPropagationStopped()) {
break;
}
}
return $response;
}
}

View File

@ -1,27 +0,0 @@
<?php
namespace Hyperflex\Framework;
use Psr\Container\ContainerInterface;
class Hyperflex
{
/**
* @var ContainerInterface
*/
private static $container;
public static function getContainer(): ContainerInterface
{
return self::$container;
}
public static function setContainer(ContainerInterface $container): ContainerInterface
{
self::$container = $container;
return $container;
}
}

View File

@ -1,114 +0,0 @@
<?php
namespace Hyperflex\Framework\Logger;
use Hyperflex\Framework\Contracts\ConfigInterface;
use Hyperflex\Framework\Contracts\StdoutLoggerInterface;
use Psr\Log\LogLevel;
use function printf;
use function sprintf;
use function str_replace;
use const PHP_EOL;
/**
* Default logger for logging server start and requests.
* PSR-3 logger implementation that logs to STDOUT, using a newline after each
* message. Priority is ignored.
*/
class StdoutLogger implements StdoutLoggerInterface
{
/**
* @var \Hyperflex\Contracts\ConfigInterface
*/
private $config;
public function __construct(ConfigInterface $config)
{
$this->config = $config;
}
/**
* {@inheritDoc}
*/
public function emergency($message, array $context = [])
{
$this->log(LogLevel::EMERGENCY, $message, $context);
}
/**
* {@inheritDoc}
*/
public function alert($message, array $context = [])
{
$this->log(LogLevel::ALERT, $message, $context);
}
/**
* {@inheritDoc}
*/
public function critical($message, array $context = [])
{
$this->log(LogLevel::CRITICAL, $message, $context);
}
/**
* {@inheritDoc}
*/
public function error($message, array $context = [])
{
$this->log(LogLevel::ERROR, $message, $context);
}
/**
* {@inheritDoc}
*/
public function warning($message, array $context = [])
{
$this->log(LogLevel::WARNING, $message, $context);
}
/**
* {@inheritDoc}
*/
public function notice($message, array $context = [])
{
$this->log(LogLevel::NOTICE, $message, $context);
}
/**
* {@inheritDoc}
*/
public function info($message, array $context = [])
{
$this->log(LogLevel::INFO, $message, $context);
}
/**
* {@inheritDoc}
*/
public function debug($message, array $context = [])
{
$this->log(LogLevel::DEBUG, $message, $context);
}
/**
* {@inheritDoc}
*/
public function log($level, $message, array $context = [])
{
$config = $this->config->get(StdoutLoggerInterface::class, ['log_level' => []]);
if (! in_array($level, $config['log_level'])) {
return;
}
foreach ($context as $key => $value) {
$search = sprintf('{%s}', $key);
$message = str_replace($search, $value, $message);
}
printf('%s%s', $message, PHP_EOL);
}
}

View File

@ -1,90 +0,0 @@
<?php
namespace Hyperflex\Framework;
use Hyperflex\Framework\Constants\SwooleEvent;
use Psr\Container\ContainerInterface;
use Swoole\Server as SwooleServer;
use Swoole\Server\Port;
class Server
{
/**
* @var SwooleServer
*/
protected $server;
/**
* @var array
*/
protected $settings = [];
/**
* @var array
*/
protected $events = [];
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function initConfigs(array $serverConfigs): self
{
foreach ($serverConfigs as $serverConfig) {
$server = $serverConfig['server'];
$constructor = $serverConfig['constructor'];
$callbacks = $serverConfig['callbacks'];
$settings = $serverConfig['settings'] ?? [];
if (! class_exists($server)) {
throw new \InvalidArgumentException('Server not exist.');
}
if (! $this->server) {
$this->server = new $server(...$constructor);
$callbacks = array_replace($this->defaultCallbacks(), $callbacks);
$this->registerSwooleEvents($this->server, $callbacks);
$this->server->set($settings);
} else {
$slaveServer = $this->server->addlistener(...$constructor);
$this->registerSwooleEvents($slaveServer, $callbacks);
}
}
return $this;
}
/**
* @param SwooleServer|Port $server
* @param array $events
*/
protected function registerSwooleEvents($server, array $events): void
{
foreach ($events as $event => $callback) {
if (is_array($callback)) {
$callback = [$this->container->get($callback[0]), $callback[1]];
}
$server->on($event, $callback);
}
}
public function run()
{
$this->server->start();
}
private function defaultCallbacks()
{
return [
SwooleEvent::ON_WORKER_START => function (SwooleServer $server, int $workerId) {
printf('Worker %d started.' . PHP_EOL, $workerId);
},
];
}
}

View File

@ -1,11 +0,0 @@
<?php
namespace Hyperflex\Framework;
class ServerRunner
{
private $server;
}

View File

@ -1,71 +0,0 @@
{
"name": "hyperflex/grpc-server",
"description": "A GRPC Server for Hyperflex.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperflex",
"grpc",
"grpc-server"
],
"support": {
},
"require": {
"php": ">=7.1",
"nikic/fast-route": "^1.3",
"hyperflex/framework": "dev-master",
"hyperflex/utils": "dev-master",
"hyperflex/http-server": "dev-master",
"google/protobuf": "^3.6.1",
"grpc/grpc": "^1.15"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0"
},
"suggest": {
"ext-grpc": "grpc/grpc require grpc extension.",
"ext-protobuf": "For better performance, install the protobuf C extension."
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperflex\\GrpcServer\\": "src"
}
},
"autoload-dev": {
"psr-4": {
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": "Hyperflex\\GrpcServer\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
}
}

View File

@ -1,24 +0,0 @@
<?php
namespace Hyperflex\GrpcServer;
use Hyperflex\GrpcServer\Router\Dispatcher;
use Hyperflex\GrpcServer\Router\DispatcherFactory;
class ConfigProvider
{
public function __invoke(): array
{
return [
'dependencies' => [
Server::class => ServerFactory::class,
Dispatcher::class => DispatcherFactory::class,
],
'scan' => [
'paths' => [
"vendor/hyperflex/grpc-server/src"
],
],
];
}
}

View File

@ -1,181 +0,0 @@
<?php
namespace Hyperflex\GrpcServer;
use FastRoute\Dispatcher;
use Google\Protobuf\Internal\Message;
use Hyperflex\Di\MethodDefinitionCollector;
use Hyperflex\Di\ReflectionManager;
use Hyperflex\Utils\Context;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Swoft\Http\Message\Stream\SwooleStream;
use Hyperflex\HttpServer\CoreMiddleware as HttpCoreMiddleware;
use Hyperflex\GrpcServer\Router\Dispatcher as GrpcDispatcher;
use Google\Protobuf\Internal\Message as ProtobufMessage;
use Hyperflex\GrpcServer\Utils\Parser;
class CoreMiddleware extends HttpCoreMiddleware
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var Dispatcher
*/
protected $dispatcher;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
$this->dispatcher = $container->get(GrpcDispatcher::class);
}
/**
* Process an incoming server request and return a response, optionally delegating
* response creation to a handler.
*/
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
/** @var ResponseInterface $response */
$uri = $request->getUri();
/**
* @var array $routes
* Returns array with one of the following formats:
* [self::NOT_FOUND]
* [self::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
* [self::FOUND, $handler, ['varName' => 'value', ...]]
*/
$routes = $this->dispatcher->dispatch($request->getMethod(), $uri->getPath());
switch ($routes[0]) {
case Dispatcher::FOUND:
[$controller, $action] = $this->prepareHandler($routes[1]);
$controllerInstance = $this->container->get($controller);
if (!method_exists($controller, $action)) {
$grpcMessage = sprintf('%s:%s is not implemented!', $controller, $action);
return $this->handleResponse(null, 500, '500', $grpcMessage);
}
$parameters = $this->parseParameters($controller, $action, $routes[2]);
$result = $controllerInstance->$action(...$parameters);
if (!$result instanceof Message) {
$grpcMessage = 'The result is not a valid message!';
return $this->handleResponse(null, 500, '500', $grpcMessage);
}
return $this->handleResponse($result, 200);
case Dispatcher::NOT_FOUND:
case Dispatcher::METHOD_NOT_ALLOWED:
default:
return $this->handleResponse(null, 404, '404', 'Route Not Find!');
}
}
/**
* Transfer the non-standard response content to a standard response object.
*
* @param string|array $response
*/
protected function transferToResponse($response): ResponseInterface
{
if ($response instanceof Message) {
$body = Parser::serializeMessage($response);
$response = $this->response()
->withAddedHeader('Content-Type', 'application/grpc')
->withAddedHeader('trailer', 'grpc-status, grpc-message')
->withBody(new SwooleStream($body));
$response->getSwooleResponse()->trailer('grpc-status', '0');
$response->getSwooleResponse()->trailer('grpc-message', '');
return $response;
}
if (is_string($response)) {
return $this->response()->withBody(new SwooleStream($response));
}
if (is_array($response)) {
return $this->response()
->withAddedHeader('Content-Type', 'application/json')
->withBody(new SwooleStream(json_encode($response)));
}
return $this->response()->withBody(new SwooleStream((string)$response));
}
protected function parseParameters(string $controller, string $action, array $arguments): array
{
$injections = [];
$definitions = MethodDefinitionCollector::getOrParse($controller, $action);
foreach ($definitions ?? [] as $definition) {
if (!is_array($definition)) {
throw new \RuntimeException('Invalid method definition.');
}
if (!isset($definition['type']) || !isset($definition['name'])) {
$injections[] = null;
continue;
}
$injections[] = value(function () use ($definition, $arguments) {
switch ($definition['type']) {
case 'object':
$ref = $definition['ref'];
$class = ReflectionManager::reflectClass($ref);
$parentClass = $class->getParentClass();
if ($parentClass->getName() === ProtobufMessage::class) {
$request = $this->request();
return Parser::deserializeMessage([$class->getName(), null], $request->getBody());
}
if (!$this->container->has($definition['ref']) && !$definition['allowsNull']) {
throw new \RuntimeException(sprintf('Argument %s invalid, object %s not found.', $definition['name'], $definition['ref']));
}
return $this->container->get($definition['ref']);
default:
throw new \RuntimeException('Invalid method definition detected.');
}
});
}
return $injections;
}
/**
* @return RequestInterface
*/
protected function request()
{
return Context::get(ServerRequestInterface::class);
}
/**
* Handle GRPC Response
* @param ProtobufMessage|null $message
* @param int $httpStatus
* @param string $grpcStatus
* @param string $grpcMessage
* @return ResponseInterface
*/
protected function handleResponse(?Message $message, $httpStatus = 200, string $grpcStatus = '0', string $grpcMessage = ''): ResponseInterface
{
$response = $this->response()->withStatus($httpStatus)
->withBody(new SwooleStream(Parser::serializeMessage($message)))
->withAddedHeader('Server', 'Hyperflex')
->withAddedHeader('Content-Type', 'application/grpc')
->withAddedHeader('trailer', 'grpc-status, grpc-message');
$response->getSwooleResponse()->trailer('grpc-status', $grpcStatus);
$response->getSwooleResponse()->trailer('grpc-message', $grpcMessage);
return $response;
}
}

View File

@ -1,7 +0,0 @@
<?php
namespace Hyperflex\GrpcServer\Router;
interface Dispatcher
{
}

View File

@ -1,10 +0,0 @@
<?php
namespace Hyperflex\GrpcServer\Router;
use Hyperflex\HttpServer\Router\DispatcherFactory as HttpDispatcherFactory;
class DispatcherFactory extends HttpDispatcherFactory
{
protected $routes = [BASE_PATH . '/config/grpc_routes.php'];
}

View File

@ -1,7 +0,0 @@
<?php
namespace Hyperflex\GrpcServer;
interface Server
{
}

View File

@ -1,10 +0,0 @@
<?php
namespace Hyperflex\GrpcServer;
use Hyperflex\HttpServer\ServerFactory as HttpServerFactory;
class ServerFactory extends HttpServerFactory
{
protected $coreMiddleware = CoreMiddleware::class;
}

View File

@ -1,80 +0,0 @@
<?php
namespace Hyperflex\GrpcServer\Utils;
use Google\Protobuf\Internal\Message;
class Parser
{
public static function pack(string $data): string
{
return $data = pack('CN', 0, strlen($data)) . $data;
}
public static function unpack(string $data): string
{
// it's the way to verify the package length
// 1 + 4 + data
// $len = unpack('N', substr($data, 1, 4))[1];
// assert(strlen($data) - 5 === $len);
return $data = substr($data, 5);
}
public static function serializeMessage($data)
{
if (method_exists($data, 'encode')) {
$data = $data->encode();
} elseif (method_exists($data, 'serializeToString')) {
$data = $data->serializeToString();
} else {
/** @noinspection PhpUndefinedMethodInspection */
$data = $data->serialize();
}
return self::pack($data);
}
public static function deserializeMessage($deserialize, string $value)
{
if (empty($value)) {
return null;
} else {
$value = self::unpack($value);
}
if (is_array($deserialize)) {
list($className, $deserializeFunc) = $deserialize;
/** @var $obj \Google\Protobuf\Internal\Message */
$obj = new $className();
if ($deserializeFunc && method_exists($obj, $deserializeFunc)) {
$obj->$deserializeFunc($value);
} else {
/** @noinspection PhpUndefinedMethodInspection */
$obj->mergeFromString($value);
}
return $obj;
}
return call_user_func($deserialize, $value);
}
/**
* @param \swoole_http2_response|null $response
* @param $deserialize
* @return Message[]|\Grpc\StringifyAble[]|\swoole_http2_response[]
*/
public static function parseToResultArray($response, $deserialize): array
{
if (!$response) {
return ['No response', GRPC_ERROR_NO_RESPONSE, $response];
} elseif ($response->statusCode !== 200) {
return ['Http status Error', $response->errCode ?: $response->statusCode, $response];
} else {
$grpc_status = (int)($response->headers['grpc-status'] ?? 0);
if ($grpc_status !== 0) {
return [$response->headers['grpc-message'] ?? 'Unknown error', $grpc_status, $response];
}
$data = $response->data;
$reply = self::deserializeMessage($deserialize, $data);
$status = (int)($response->headers['grpc-status'] ?? 0 ?: 0);
return [$reply, $status, $response];
}
}
}

View File

@ -1,67 +0,0 @@
{
"name": "hyperflex/http-server",
"description": "A HTTP Server for Hyperflex.",
"license": "Apache-2.0",
"keywords": [
"php",
"swoole",
"hyperflex",
"http",
"http-server"
],
"support": {
},
"require": {
"php": ">=7.1",
"nikic/fast-route": "^1.3",
"hyperflex/framework": "dev-master",
"hyperflex/utils": "dev-master",
"swoft/http-message": "^1.0"
},
"require-dev": {
"malukenho/docheader": "^0.1.6",
"mockery/mockery": "^1.0",
"phpstan/phpstan": "^0.9.2",
"phpstan/phpstan-strict-rules": "^0.9",
"phpunit/phpunit": "^7.0.0",
"hyperflex/coding-standard": "~1.0.0"
},
"suggest": {
},
"autoload": {
"files": [
],
"psr-4": {
"Hyperflex\\HttpServer\\": "src"
}
},
"autoload-dev": {
"psr-4": {
}
},
"config": {
"sort-packages": true
},
"extra": {
"branch-alias": {
},
"hyperflex": {
"config": "Hyperflex\\HttpServer\\ConfigProvider"
}
},
"bin": [
],
"scripts": {
"check": [
"@license-check",
"@cs-check",
"@test"
],
"cs-check": "phpcs",
"cs-fix": "phpcbf",
"phpstan": "phpstan analyze -l max -c phpstan.neon ./src",
"test": "phpunit --colors=always",
"test-coverage": "phpunit --colors=always --coverage-clover clover.xml",
"license-check": "docheader check src/ test/"
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace Hyperflex\HttpServer\Command;
use Hyperflex\Framework\Server;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Hyperflex\Di\Annotation\Scanner;
use Hyperflex\Contracts\ConfigInterface;
use Psr\Container\ContainerInterface;
class StartServer extends Command
{
/**
* @var ContainerInterface
*/
private $container;
/**
* @var Server
*/
private $server;
/**
* @var Scanner
*/
private $scanner;
public function __construct(ContainerInterface $container, Scanner $scanner)
{
parent::__construct('start');
$this->container = $container;
$this->scanner = $scanner;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->checkEnv($output);
$this->initServer();
$this->server->run();
}
private function initServer()
{
$config = $this->container->get(ConfigInterface::class);
$serverConfigs = $config->get('servers', []);
if (! $serverConfigs) {
throw new \InvalidArgumentException('No available server.');
}
$this->server = $this->container->get(Server::class)->initConfigs($serverConfigs);
}
private function checkEnv(OutputInterface $output)
{
if (ini_get_all('swoole')['swoole.use_shortname']['local_value'] !== 'Off') {
$output->writeln('<error>ERROR</error> Swoole short name have to disable before start server, please set swoole.use_shortname = \'Off\' into your php.ini.');
exit(0);
}
}
}

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