Merge branch 'master' into cache

This commit is contained in:
李铭昕 2019-08-07 15:48:54 +08:00
commit a1d3e79b56
26 changed files with 913 additions and 104 deletions

View File

@ -4,6 +4,7 @@
- [#321](https://github.com/hyperf-cloud/hyperf/pull/321) Added custom object support for controller parameters in http-server. - [#321](https://github.com/hyperf-cloud/hyperf/pull/321) Added custom object support for controller parameters in http-server.
- [#324](https://github.com/hyperf-cloud/hyperf/pull/324) Added NodeRequestIdGenerator, an implementation of `Hyperf\Contract\IdGeneratorInterface` - [#324](https://github.com/hyperf-cloud/hyperf/pull/324) Added NodeRequestIdGenerator, an implementation of `Hyperf\Contract\IdGeneratorInterface`
- [#336](https://github.com/hyperf-cloud/hyperf/pull/336) Added Proxy RPC Client.
- [#346](https://github.com/hyperf-cloud/hyperf/pull/346) [#348](https://github.com/hyperf-cloud/hyperf/pull/348) Added filesystem driver for `hyperf/cache`. - [#346](https://github.com/hyperf-cloud/hyperf/pull/346) [#348](https://github.com/hyperf-cloud/hyperf/pull/348) Added filesystem driver for `hyperf/cache`.
## Changed ## Changed
@ -19,6 +20,7 @@
- [#333](https://github.com/hyperf-cloud/hyperf/pull/333) Fixed Function Redis::delete() is deprecated. - [#333](https://github.com/hyperf-cloud/hyperf/pull/333) Fixed Function Redis::delete() is deprecated.
- [#334](https://github.com/hyperf-cloud/hyperf/pull/334) Fixed configuration of aliyun acm is not work expected. - [#334](https://github.com/hyperf-cloud/hyperf/pull/334) Fixed configuration of aliyun acm is not work expected.
- [#337](https://github.com/hyperf-cloud/hyperf/pull/337) Fixed 500 response when key of header is not string. - [#337](https://github.com/hyperf-cloud/hyperf/pull/337) Fixed 500 response when key of header is not string.
- [#338](https://github.com/hyperf-cloud/hyperf/pull/338) Fixed `ProviderConfig::load` will convert array when dependencies has the same key.
- [#340](https://github.com/hyperf-cloud/hyperf/pull/340) Fixed function `make` not support index-based array as parameters. - [#340](https://github.com/hyperf-cloud/hyperf/pull/340) Fixed function `make` not support index-based array as parameters.
# v1.0.9 - 2019-08-03 # v1.0.9 - 2019-08-03

View File

@ -169,6 +169,7 @@
"HyperfTest\\ConfigAliyunAcm\\": "src/config-aliyun-acm/tests/", "HyperfTest\\ConfigAliyunAcm\\": "src/config-aliyun-acm/tests/",
"HyperfTest\\ConfigApollo\\": "src/config-apollo/tests/", "HyperfTest\\ConfigApollo\\": "src/config-apollo/tests/",
"HyperfTest\\ConfigEtcd\\": "src/config-etcd/tests/", "HyperfTest\\ConfigEtcd\\": "src/config-etcd/tests/",
"HyperfTest\\Config\\": "src/config/tests/",
"HyperfTest\\Constants\\": "src/constants/tests/", "HyperfTest\\Constants\\": "src/constants/tests/",
"HyperfTest\\Consul\\": "src/consul/tests/", "HyperfTest\\Consul\\": "src/consul/tests/",
"HyperfTest\\Crontab\\": "src/crontab/tests/", "HyperfTest\\Crontab\\": "src/crontab/tests/",

View File

@ -13,6 +13,7 @@
<directory suffix="Test.php">./src/amqp/tests</directory> <directory suffix="Test.php">./src/amqp/tests</directory>
<directory suffix="Test.php">./src/async-queue/tests</directory> <directory suffix="Test.php">./src/async-queue/tests</directory>
<directory suffix="Test.php">./src/cache/tests</directory> <directory suffix="Test.php">./src/cache/tests</directory>
<directory suffix="Test.php">./src/config/tests</directory>
<directory suffix="Test.php">./src/constants/tests</directory> <directory suffix="Test.php">./src/constants/tests</directory>
<directory suffix="Test.php">./src/consul/tests</directory> <directory suffix="Test.php">./src/consul/tests</directory>
<directory suffix="Test.php">./src/database/tests</directory> <directory suffix="Test.php">./src/database/tests</directory>

1
src/config/.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/tests export-ignore

View File

@ -44,6 +44,7 @@
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"HyperfTest\\Config\\": "tests/"
} }
}, },
"config": { "config": {

View File

@ -35,23 +35,37 @@ class ProviderConfig
public static function load(): array public static function load(): array
{ {
if (! static::$privoderConfigs) { if (! static::$privoderConfigs) {
$config = []; $providers = Composer::getMergedExtra('hyperf')['config'] ?? [];
$providers = Composer::getMergedExtra('hyperf')['config']; static::$privoderConfigs = static::loadProviders($providers);
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 = $config;
unset($config, $providerConfig);
} }
return static::$privoderConfigs; return static::$privoderConfigs;
} }
public function clear(): void public static function clear(): void
{ {
static::$privoderConfigs = []; static::$privoderConfigs = [];
} }
protected static function loadProviders(array $providers): array
{
$providerConfigs = [];
foreach ($providers ?? [] as $provider) {
if (is_string($provider) && class_exists($provider) && method_exists($provider, '__invoke')) {
$providerConfigs[] = (new $provider())();
}
}
return static::merge(...$providerConfigs);
}
protected static function merge(...$arrays): array
{
$result = array_merge_recursive(...$arrays);
if (isset($result['dependencies'])) {
$dependencies = array_column($arrays, 'dependencies');
$result['dependencies'] = array_merge(...$dependencies);
}
return $result;
}
} }

View File

@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Config;
use Hyperf\Utils\Arr;
use HyperfTest\Config\Stub\FooConfigProvider;
use HyperfTest\Config\Stub\ProviderConfig;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class ProviderConfigTest extends TestCase
{
public function testProviderConfigMerge()
{
$c1 = [
'listeners' => ['L1'],
'dependencies' => [
'D1' => 'D1',
'D2' => 'D2',
],
];
$c2 = [
'listeners' => ['L2'],
'dependencies' => [
'D1' => 'D1',
'D2' => 'D3',
],
];
$c3 = [
'listeners' => ['L2'],
'dependencies' => [
'D1' => 'D1',
'D3' => 'D3',
'D4' => 'D4',
],
];
$result = ProviderConfig::merge($c1, $c2, $c3);
$this->assertSame(['D1' => 'D1', 'D2' => 'D3', 'D3' => 'D3', 'D4' => 'D4'], $result['dependencies']);
}
public function testProviderConfigNotHaveDependencies()
{
$c1 = [
'listeners' => ['L1'],
'dependencies' => [
'D1' => 'D1',
'D2' => 'D2',
],
];
$c2 = [
'listeners' => ['L2'],
];
$result = ProviderConfig::merge($c1, $c2);
$this->assertSame(['D1' => 'D1', 'D2' => 'D2'], $result['dependencies']);
$this->assertSame(['L1', 'L2'], $result['listeners']);
}
public function testProviderConfigHaveNull()
{
$c1 = [
'listeners' => ['L1'],
];
$c2 = [
'listeners' => [value(function () {
return null;
})],
];
$result = ProviderConfig::merge($c1, $c2);
$this->assertSame(['L1', null], $result['listeners']);
}
public function testProviderConfigLoadProviders()
{
$config = json_decode(file_get_contents(BASE_PATH . '/composer.json'), true);
$providers = $config['extra']['hyperf']['config'];
$res = ProviderConfig::loadProviders($providers);
$dependencies = $res['dependencies'];
$commands = $res['commands'];
$scanPaths = $res['scan']['paths'];
$publish = $res['publish'];
$listeners = $res['listeners'];
$this->assertFalse(Arr::isAssoc($commands));
$this->assertFalse(Arr::isAssoc($scanPaths));
$this->assertFalse(Arr::isAssoc($listeners));
$this->assertFalse(Arr::isAssoc($publish));
$this->assertTrue(Arr::isAssoc($dependencies));
}
public function testProviderConfigLoadProvidersHasCallable()
{
$res = ProviderConfig::loadProviders([
FooConfigProvider::class,
]);
foreach ($res['dependencies'] as $dependency) {
$this->assertTrue(is_string($dependency) || is_callable($dependency));
}
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Config\Stub;
class Foo
{
public $id;
public function __construct($id = 0)
{
$this->id = $id;
}
public static function make()
{
return new self(2);
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Config\Stub;
class FooConfigProvider
{
public function __invoke()
{
return [
'dependencies' => [
'Foo' => function () {
return new Foo(1);
},
'Foo2' => [Foo::class, 'make'],
'Foo3' => Foo::class,
],
];
}
}

View File

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Config\Stub;
class ProviderConfig extends \Hyperf\Config\ProviderConfig
{
public static function loadProviders(array $providers): array
{
return parent::loadProviders($providers);
}
public static function merge(...$arrays): array
{
return parent::merge(...$arrays);
}
}

View File

@ -49,32 +49,7 @@ class InitProxyCommand extends Command
public function handle() public function handle()
{ {
$scanDirs = $this->getScanDir(); $this->createAopProxies();
$runtime = BASE_PATH . '/runtime/container/proxy/';
if (is_dir($runtime)) {
$this->clearRuntime($runtime);
}
$classCollection = $this->scanner->scan($scanDirs);
foreach ($classCollection as $item) {
try {
$this->container->get($item);
} catch (\Throwable $ex) {
// Entry cannot be resoleved.
}
}
if ($this->container instanceof Container) {
foreach ($this->container->getDefinitionSource()->getDefinitions() as $key => $definition) {
try {
$this->container->get($key);
} catch (\Throwable $ex) {
// Entry cannot be resoleved.
}
}
}
$this->output->writeln('<info>Proxy class create success.</info>'); $this->output->writeln('<info>Proxy class create success.</info>');
} }
@ -111,4 +86,34 @@ class InitProxyCommand extends Command
return $scanDirs; return $scanDirs;
} }
private function createAopProxies()
{
$scanDirs = $this->getScanDir();
$runtime = BASE_PATH . '/runtime/container/proxy/';
if (is_dir($runtime)) {
$this->clearRuntime($runtime);
}
$classCollection = $this->scanner->scan($scanDirs);
foreach ($classCollection as $item) {
try {
$this->container->get($item);
} catch (\Throwable $ex) {
// Entry cannot be resoleved.
}
}
if ($this->container instanceof Container) {
foreach ($this->container->getDefinitionSource()->getDefinitions() as $key => $definition) {
try {
$this->container->get($key);
} catch (\Throwable $ex) {
// Entry cannot be resoleved.
}
}
}
}
} }

View File

@ -78,9 +78,9 @@ class JsonRpcHttpTransporter implements TransporterInterface
]); ]);
if ($response->getStatusCode() === 200) { if ($response->getStatusCode() === 200) {
return $response->getBody()->getContents(); return $response->getBody()->getContents();
} else {
$this->loadBalancer->removeNode($node);
} }
$this->loadBalancer->removeNode($node);
return ''; return '';
} }

View File

@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\JsonRpc;
use Hyperf\Config\Config;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\NormalizerInterface;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\Di\Annotation\Scanner;
use Hyperf\Di\Container;
use Hyperf\Di\Definition\DefinitionSource;
use Hyperf\Di\MethodDefinitionCollector;
use Hyperf\Di\MethodDefinitionCollectorInterface;
use Hyperf\JsonRpc\DataFormatter;
use Hyperf\JsonRpc\JsonRpcTransporter;
use Hyperf\JsonRpc\NormalizeDataFormatter;
use Hyperf\JsonRpc\PathGenerator;
use Hyperf\Logger\Logger;
use Hyperf\RpcClient\ProxyFactory;
use Hyperf\Utils\ApplicationContext;
use Hyperf\Utils\Packer\JsonPacker;
use Hyperf\Utils\Serializer\SerializerFactory;
use Hyperf\Utils\Serializer\SymfonyNormalizer;
use HyperfTest\JsonRpc\Stub\CalculatorProxyServiceClient;
use HyperfTest\JsonRpc\Stub\CalculatorServiceInterface;
use HyperfTest\JsonRpc\Stub\IntegerValue;
use Mockery\MockInterface;
use Monolog\Handler\StreamHandler;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Serializer\Serializer;
/**
* @internal
* @coversNothing
*/
class RpcServiceClientTest extends TestCase
{
protected function setUp()
{
parent::setUp();
}
public function testServiceClient()
{
$container = $this->createContainer();
/** @var MockInterface $transporter */
$transporter = $container->get(JsonRpcTransporter::class);
$transporter->shouldReceive('setLoadBalancer')
->andReturnSelf();
$transporter->shouldReceive('send')
->andReturn(json_encode([
'result' => 3,
]));
$service = new CalculatorProxyServiceClient($container, CalculatorServiceInterface::class, 'jsonrpc');
$ret = $service->add(1, 2);
$this->assertEquals(3, $ret);
}
public function testProxyFactory()
{
$container = $this->createContainer();
/** @var MockInterface $transporter */
$transporter = $container->get(JsonRpcTransporter::class);
$transporter->shouldReceive('setLoadBalancer')
->andReturnSelf();
$transporter->shouldReceive('send')
->andReturn(json_encode([
'result' => 3,
]));
$factory = new ProxyFactory();
$proxyClass = $factory->createProxy(CalculatorServiceInterface::class);
/** @var CalculatorServiceInterface $service */
$service = new $proxyClass($container, CalculatorServiceInterface::class, 'jsonrpc');
$ret = $service->add(1, 2);
$this->assertEquals(3, $ret);
}
public function testProxyFactoryObjectParameter()
{
$container = $this->createContainer();
/** @var MockInterface $transporter */
$transporter = $container->get(JsonRpcTransporter::class);
$transporter->shouldReceive('setLoadBalancer')
->andReturnSelf();
$transporter->shouldReceive('send')
->andReturn(json_encode([
'result' => ['value' => 3],
]));
$factory = new ProxyFactory();
$proxyClass = $factory->createProxy(CalculatorServiceInterface::class);
/** @var CalculatorServiceInterface $service */
$service = new $proxyClass($container, CalculatorServiceInterface::class, 'jsonrpc');
$ret = $service->sum(IntegerValue::newInstance(1), IntegerValue::newInstance(2));
$this->assertInstanceOf(IntegerValue::class, $ret);
$this->assertEquals(3, $ret->getValue());
}
public function createContainer()
{
$transporter = \Mockery::mock(JsonRpcTransporter::class);
$container = new Container(new DefinitionSource([
NormalizerInterface::class => SymfonyNormalizer::class,
Serializer::class => SerializerFactory::class,
DataFormatter::class => NormalizeDataFormatter::class,
MethodDefinitionCollectorInterface::class => MethodDefinitionCollector::class,
StdoutLoggerInterface::class => function () {
return new Logger('App', [new StreamHandler('php://stderr')]);
},
ConfigInterface::class => function () {
return new Config([
'services' => [
'consumers' => [
[
'name' => CalculatorServiceInterface::class,
'nodes' => [
['host' => '0.0.0.0', 'port' => 1234],
],
],
],
],
'protocols' => [
'jsonrpc' => [
'packer' => JsonPacker::class,
'transporter' => JsonRpcTransporter::class,
'path-generator' => PathGenerator::class,
'data-formatter' => DataFormatter::class,
],
],
]);
},
JsonRpcTransporter::class => function () use ($transporter) {
return $transporter;
},
], [], new Scanner()));
ApplicationContext::setContainer($container);
return $container;
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\JsonRpc\Stub;
use Hyperf\RpcClient\Proxy\AbstractProxyService;
class CalculatorProxyServiceClient extends AbstractProxyService implements CalculatorServiceInterface
{
public function add(int $a, int $b)
{
return $this->client->__call(__FUNCTION__, func_get_args());
}
public function sum(IntegerValue $a, IntegerValue $b): IntegerValue
{
return $this->client->__call(__FUNCTION__, func_get_args());
}
public function divide($value, $divider)
{
return $this->client->__call(__FUNCTION__, func_get_args());
}
}

View File

@ -17,14 +17,13 @@ use Hyperf\Consul\Health;
use Hyperf\Consul\HealthInterface; use Hyperf\Consul\HealthInterface;
use Hyperf\Contract\ConfigInterface; use Hyperf\Contract\ConfigInterface;
use Hyperf\Contract\IdGeneratorInterface; use Hyperf\Contract\IdGeneratorInterface;
use Hyperf\Contract\PackerInterface;
use Hyperf\Guzzle\ClientFactory; use Hyperf\Guzzle\ClientFactory;
use Hyperf\LoadBalancer\LoadBalancerInterface; use Hyperf\LoadBalancer\LoadBalancerInterface;
use Hyperf\LoadBalancer\LoadBalancerManager; use Hyperf\LoadBalancer\LoadBalancerManager;
use Hyperf\LoadBalancer\Node; use Hyperf\LoadBalancer\Node;
use Hyperf\Rpc\Contract\DataFormatterInterface; use Hyperf\Rpc\Contract\DataFormatterInterface;
use Hyperf\Rpc\Contract\PathGeneratorInterface; use Hyperf\Rpc\Contract\PathGeneratorInterface;
use Hyperf\Rpc\Contract\TransporterInterface; use Hyperf\Rpc\Protocol;
use Hyperf\Rpc\ProtocolManager; use Hyperf\Rpc\ProtocolManager;
use InvalidArgumentException; use InvalidArgumentException;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -61,7 +60,7 @@ abstract class AbstractServiceClient
protected $client; protected $client;
/** /**
* @var ContainerInterfaces * @var ContainerInterface
*/ */
protected $container; protected $container;
@ -71,9 +70,9 @@ abstract class AbstractServiceClient
protected $loadBalancerManager; protected $loadBalancerManager;
/** /**
* @var \Hyperf\Rpc\ProtocolManager * @var null|\Hyperf\Contract\IdGeneratorInterface
*/ */
protected $protocolManager; protected $idGenerator;
/** /**
* @var PathGeneratorInterface * @var PathGeneratorInterface
@ -85,31 +84,21 @@ abstract class AbstractServiceClient
*/ */
protected $dataFormatter; protected $dataFormatter;
/**
* @var \Hyperf\Contract\ConfigInterface
*/
protected $config;
/**
* @var null|\Hyperf\Contract\IdGeneratorInterface
*/
protected $idGenerator;
public function __construct(ContainerInterface $container) public function __construct(ContainerInterface $container)
{ {
$this->container = $container; $this->container = $container;
$this->loadBalancerManager = $container->get(LoadBalancerManager::class); $this->loadBalancerManager = $container->get(LoadBalancerManager::class);
$this->protocolManager = $container->get(ProtocolManager::class); $protocol = new Protocol($container, $container->get(ProtocolManager::class), $this->protocol);
$this->pathGenerator = $this->createPathGenerator();
$this->dataFormatter = $this->createDataFormatter();
$loadBalancer = $this->createLoadBalancer(...$this->createNodes()); $loadBalancer = $this->createLoadBalancer(...$this->createNodes());
$transporter = $this->createTransporter()->setLoadBalancer($loadBalancer); $transporter = $protocol->getTransporter()->setLoadBalancer($loadBalancer);
$this->client = make(Client::class) $this->client = make(Client::class)
->setPacker($this->createPacker()) ->setPacker($protocol->getPacker())
->setTransporter($transporter); ->setTransporter($transporter);
if ($container->has(IdGeneratorInterface::class)) { if ($container->has(IdGeneratorInterface::class)) {
$this->idGenerator = $container->get(IdGeneratorInterface::class); $this->idGenerator = $container->get(IdGeneratorInterface::class);
} }
$this->pathGenerator = $protocol->getPathGenerator();
$this->dataFormatter = $protocol->getDataFormatter();
} }
protected function __request(string $method, array $params, ?string $id = null) protected function __request(string $method, array $params, ?string $id = null)
@ -149,46 +138,6 @@ abstract class AbstractServiceClient
return $loadBalancer; return $loadBalancer;
} }
protected function createTransporter(): TransporterInterface
{
$transporter = $this->protocolManager->getTransporter($this->protocol);
if (! class_exists($transporter)) {
throw new InvalidArgumentException(sprintf('Transporter %s is not exists.', $transporter));
}
/* @var TransporterInterface $instance */
return make($transporter);
}
protected function createPacker(): PackerInterface
{
$packer = $this->protocolManager->getPacker($this->protocol);
if (! class_exists($packer)) {
throw new InvalidArgumentException(sprintf('Packer %s is not exists.', $packer));
}
/* @var PackerInterface $packer */
return $this->container->get($packer);
}
protected function createPathGenerator(): PathGeneratorInterface
{
$pathGenerator = $this->protocolManager->getPathGenerator($this->protocol);
if (! class_exists($pathGenerator)) {
throw new InvalidArgumentException(sprintf('Path Generator %s is not exists.', $pathGenerator));
}
/* @var PathGeneratorInterface $pathGenerator */
return $this->container->get($pathGenerator);
}
protected function createDataFormatter(): DataFormatterInterface
{
$dataFormatter = $this->protocolManager->getDataFormatter($this->protocol);
if (! class_exists($dataFormatter)) {
throw new InvalidArgumentException(sprintf('Data Formatter %s is not exists.', $dataFormatter));
}
/* @var DataFormatterInterface $dataFormatter */
return $this->container->get($dataFormatter);
}
/** /**
* Create nodes the first time. * Create nodes the first time.
* *

View File

@ -12,6 +12,8 @@ declare(strict_types=1);
namespace Hyperf\RpcClient; namespace Hyperf\RpcClient;
use Hyperf\RpcClient\Listener\AddConsumerDefinitionListener;
class ConfigProvider class ConfigProvider
{ {
public function __invoke(): array public function __invoke(): array
@ -21,6 +23,9 @@ class ConfigProvider
], ],
'commands' => [ 'commands' => [
], ],
'listeners' => [
AddConsumerDefinitionListener::class,
],
'scan' => [ 'scan' => [
'paths' => [ 'paths' => [
__DIR__, __DIR__,

View File

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

View File

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\RpcClient\Listener;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Di\Container;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BootApplication;
use Hyperf\RpcClient\ProxyFactory;
use Hyperf\Utils\Arr;
use Psr\Container\ContainerInterface;
class AddConsumerDefinitionListener implements ListenerInterface
{
/**
* @var ContainerInterface
*/
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function listen(): array
{
return [
BootApplication::class,
];
}
/**
* Automatic create proxy service definitions from services.consumers.
*
* @param BootApplication $event
*/
public function process(object $event)
{
/** @var Container $container */
$container = $this->container;
if ($container instanceof Container) {
$consumers = $container->get(ConfigInterface::class)->get('services.consumers', []);
$serviceFactory = $container->get(ProxyFactory::class);
$definitions = $container->getDefinitionSource();
foreach ($consumers as $consumer) {
if (empty($consumer['name'])) {
continue;
}
$serviceClass = $consumer['service'] ?? $consumer['name'];
if (! interface_exists($serviceClass)) {
continue;
}
$definitions->addDefinition(
$consumer['id'] ?? $consumer['name'],
function (ContainerInterface $container) use ($serviceFactory, $consumer, $serviceClass) {
$proxyClass = $serviceFactory->createProxy($serviceClass);
return new $proxyClass(
$container,
$consumer['name'],
$consumer['protocol'] ?? 'jsonrpc-http',
Arr::only($consumer, ['load_balancer'])
);
}
);
}
}
}
}

View File

@ -14,7 +14,6 @@ namespace Hyperf\RpcClient\Pool;
use Hyperf\Di\Container; use Hyperf\Di\Container;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Swoole\Coroutine\Channel;
class PoolFactory class PoolFactory
{ {
@ -24,7 +23,7 @@ class PoolFactory
protected $container; protected $container;
/** /**
* @var Channel[] * @var RpcClientPool[]
*/ */
protected $pools = []; protected $pools = [];
@ -33,7 +32,7 @@ class PoolFactory
$this->container = $container; $this->container = $container;
} }
public function getPool(string $name): RedisPool public function getPool(string $name): RpcClientPool
{ {
if (isset($this->pools[$name])) { if (isset($this->pools[$name])) {
return $this->pools[$name]; return $this->pools[$name];

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\RpcClient\Proxy;
use Hyperf\RpcClient\ServiceClient;
use Psr\Container\ContainerInterface;
abstract class AbstractProxyService
{
/**
* @var ServiceClient
*/
protected $client;
public function __construct(ContainerInterface $container, string $serviceName, string $protocol, array $options = [])
{
$this->client = make(ServiceClient::class, [
'container' => $container,
'serviceName' => $serviceName,
'protocol' => $protocol,
'options' => $options,
]);
}
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\RpcClient\Proxy;
use Hyperf\Utils\Composer;
use PhpParser\NodeTraverser;
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 proxy(string $className, string $proxyClassName)
{
if (! interface_exists($className)) {
throw new \InvalidArgumentException("'{$className}' should be an interface name");
}
if (strpos($proxyClassName, '\\') !== false) {
$exploded = explode('\\', $proxyClassName);
$proxyClassName = end($exploded);
}
$code = $this->getCodeByClassName($className);
$stmts = $this->astParser->parse($code);
$traverser = new NodeTraverser();
$traverser->addVisitor(new ProxyCallVisitor($proxyClassName));
$modifiedStmts = $traverser->traverse($stmts);
return $this->printer->prettyPrintFile($modifiedStmts);
}
public function getCodeByClassName(string $className): string
{
$file = Composer::getLoader()->findFile($className);
if (! $file) {
return '';
}
return file_get_contents($file);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\RpcClient\Proxy;
use PhpParser\Node;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\NodeVisitorAbstract;
class ProxyCallVisitor extends NodeVisitorAbstract
{
/**
* @var string
*/
private $classname;
public function __construct(string $classname)
{
$this->classname = $classname;
}
public function leaveNode(Node $node)
{
if ($node instanceof Interface_) {
return new Node\Stmt\Class_($this->classname, [
'stmts' => $node->stmts,
'extends' => new Node\Name\FullyQualified(AbstractProxyService::class),
'implements' => [
$node->name,
],
]);
}
if ($node instanceof Node\Stmt\ClassMethod) {
$node->stmts = [
new Node\Stmt\Return_(new Node\Expr\MethodCall(
new Node\Expr\PropertyFetch(new Node\Expr\Variable('this'), new Node\Identifier('client')),
new Node\Identifier('__call'),
[
new Node\Scalar\MagicConst\Function_(),
new Node\Expr\FuncCall(new Node\Name('func_get_args')),
]
)),
];
return $node;
}
return parent::leaveNode($node); // TODO: Change the autogenerated stub
}
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\RpcClient;
use Hyperf\RpcClient\Proxy\Ast;
use Hyperf\Utils\Coroutine\Locker;
use Hyperf\Utils\Traits\Container;
class ProxyFactory
{
use Container;
/**
* @var Ast
*/
private $ast;
public function __construct()
{
$this->ast = new Ast();
}
public function createProxy($serviceClass): string
{
if (self::has($serviceClass)) {
return (string) self::get($serviceClass);
}
$dir = BASE_PATH . '/runtime/container/proxy/';
if (! file_exists($dir)) {
mkdir($dir, 0755, true);
}
$proxyFileName = str_replace('\\', '_', $serviceClass);
$proxyClassName = $serviceClass . '_' . md5($this->ast->getCodeByClassName($serviceClass));
$path = $dir . $proxyFileName . '.proxy.php';
$key = md5($path);
// If the proxy file does not exist, then try to acquire the coroutine lock.
if (! file_exists($path) && Locker::lock($key)) {
$targetPath = $path . '.' . uniqid();
$code = $this->ast->proxy($serviceClass, $proxyClassName);
file_put_contents($targetPath, $code);
rename($targetPath, $path);
Locker::unlock($key);
}
include_once $path;
self::set($serviceClass, $proxyClassName);
return $proxyClassName;
}
}

View File

@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace Hyperf\RpcClient;
use Hyperf\Contract\IdGeneratorInterface;
use Hyperf\Contract\NormalizerInterface;
use Hyperf\Di\MethodDefinitionCollectorInterface;
use Hyperf\RpcClient\Exception\RequestException;
use Hyperf\Utils\Arr;
use Psr\Container\ContainerInterface;
class ServiceClient extends AbstractServiceClient
{
/**
* @var MethodDefinitionCollectorInterface
*/
protected $methodDefinitionCollector;
/**
* @var NormalizerInterface
*/
private $normalizer;
public function __construct(ContainerInterface $container, string $serviceName, string $protocol = 'jsonrpc-http', array $options = [])
{
$this->serviceName = $serviceName;
$this->protocol = $protocol;
$this->setOptions($options);
parent::__construct($container);
$this->normalizer = $container->get(NormalizerInterface::class);
$this->methodDefinitionCollector = $container->get(MethodDefinitionCollectorInterface::class);
}
protected function __request(string $method, array $params, ?string $id = null)
{
if ($this->idGenerator instanceof IdGeneratorInterface && ! $id) {
$id = $this->idGenerator->generate();
}
$response = $this->client->send($this->__generateData($method, $params, $id));
if (! is_array($response)) {
throw new RequestException('Invalid response.');
}
if (isset($response['result'])) {
$type = $this->methodDefinitionCollector->getReturnType($this->serviceName, $method);
return $this->normalizer->denormalize($response['result'], $type->getName());
}
if ($code = $response['error']['code'] ?? null) {
$error = $response['error'];
// Denormalize exception.
$class = Arr::get($error, 'data.class');
$attributes = Arr::get($error, 'data.attributes', []);
if (isset($class) && class_exists($class) && $e = $this->normalizer->denormalize($attributes, $class)) {
if ($e instanceof \Exception) {
throw $e;
}
}
// Throw RequestException when denormalize exception failed.
throw new RequestException($error['message'] ?? '', $error['code']);
}
throw new RequestException('Invalid response.');
}
public function __call(string $method, array $params)
{
return $this->__request($method, $params);
}
protected function setOptions(array $options): void
{
if (isset($options['load_balancer'])) {
$this->loadBalancer = $options['load_balancer'];
}
}
}

View File

@ -62,7 +62,7 @@ class Protocol
if (! $this->container->has($transporter)) { if (! $this->container->has($transporter)) {
throw new \InvalidArgumentException("Transporter {$transporter} for {$this->name} does not exist"); throw new \InvalidArgumentException("Transporter {$transporter} for {$this->name} does not exist");
} }
return $this->container->get($transporter); return make($transporter);
} }
public function getPathGenerator(): PathGeneratorInterface public function getPathGenerator(): PathGeneratorInterface

View File

@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://doc.hyperf.io
* @contact group@hyperf.io
* @license https://github.com/hyperf-cloud/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Utils\Traits;
use Hyperf\Utils\Traits\Container;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class ContainerTest extends TestCase
{
public function testGet()
{
Foo::set('foo', 1);
$this->assertNull(Bar::get('foo'));
Bar::set('foo', 2);
$this->assertEquals(1, Foo::get('foo'));
$this->assertEquals(2, Bar::get('foo'));
}
}
class Foo
{
use Container;
}
class Bar
{
use Container;
}