123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342 |
- <?php
- declare(strict_types=1);
- namespace Yansongda\Artful;
- use Closure;
- use Illuminate\Container\Container as LaravelContainer;
- use Psr\Container\ContainerInterface;
- use Psr\Container\NotFoundExceptionInterface;
- use Psr\Http\Message\MessageInterface;
- use Throwable;
- use Yansongda\Artful\Contract\ConfigInterface;
- use Yansongda\Artful\Contract\DirectionInterface;
- use Yansongda\Artful\Contract\HttpClientFactoryInterface;
- use Yansongda\Artful\Contract\PackerInterface;
- use Yansongda\Artful\Contract\PluginInterface;
- use Yansongda\Artful\Contract\ServiceProviderInterface;
- use Yansongda\Artful\Contract\ShortcutInterface;
- use Yansongda\Artful\Direction\CollectionDirection;
- use Yansongda\Artful\Exception\ContainerException;
- use Yansongda\Artful\Exception\ContainerNotFoundException;
- use Yansongda\Artful\Exception\Exception;
- use Yansongda\Artful\Exception\InvalidParamsException;
- use Yansongda\Artful\Exception\InvalidResponseException;
- use Yansongda\Artful\Exception\ServiceNotFoundException;
- use Yansongda\Artful\Packer\JsonPacker;
- use Yansongda\Artful\Service\ConfigServiceProvider;
- use Yansongda\Artful\Service\ContainerServiceProvider;
- use Yansongda\Artful\Service\EventServiceProvider;
- use Yansongda\Artful\Service\HttpServiceProvider;
- use Yansongda\Artful\Service\LoggerServiceProvider;
- use Yansongda\Supports\Collection;
- use Yansongda\Supports\Config;
- use Yansongda\Supports\Pipeline;
- class Artful
- {
- /**
- * @var string[]
- */
- private array $coreService = [
- ContainerServiceProvider::class,
- ConfigServiceProvider::class,
- LoggerServiceProvider::class,
- EventServiceProvider::class,
- HttpServiceProvider::class,
- ];
- private static null|Closure|ContainerInterface $container = null;
- /**
- * @throws ContainerException
- */
- private function __construct(array $config, null|Closure|ContainerInterface $container = null)
- {
- $this->registerServices($config, $container);
- Artful::set(DirectionInterface::class, CollectionDirection::class);
- Artful::set(PackerInterface::class, JsonPacker::class);
- }
- /**
- * @return mixed
- *
- * @throws ContainerException
- * @throws ServiceNotFoundException
- */
- public static function __callStatic(string $service, array $config)
- {
- if (!empty($config)) {
- self::config(...$config);
- }
- return self::get($service);
- }
- /**
- * @throws ContainerException
- */
- public static function config(array $config = [], null|Closure|ContainerInterface $container = null): bool
- {
- if (self::hasContainer() && !($config['_force'] ?? false)) {
- return false;
- }
- new self($config, $container);
- return true;
- }
- /**
- * @codeCoverageIgnore
- *
- * @throws ContainerException
- */
- public static function set(string $name, mixed $value): void
- {
- try {
- $container = Artful::getContainer();
- if ($container instanceof LaravelContainer) {
- $container->singleton($name, $value instanceof Closure ? $value : static fn () => $value);
- return;
- }
- if (method_exists($container, 'set')) {
- $container->set(...func_get_args());
- return;
- }
- } catch (ContainerNotFoundException $e) {
- throw $e;
- } catch (Throwable $e) {
- throw new ContainerException('容器异常: '.$e->getMessage());
- }
- throw new ContainerException('容器异常: 当前容器类型不支持 `set` 方法');
- }
- /**
- * @codeCoverageIgnore
- *
- * @throws ContainerException
- */
- public static function make(string $service, array $parameters = []): mixed
- {
- try {
- $container = Artful::getContainer();
- if (method_exists($container, 'make')) {
- return $container->make(...func_get_args());
- }
- } catch (ContainerNotFoundException $e) {
- throw $e;
- } catch (Throwable $e) {
- throw new ContainerException('容器异常: '.$e->getMessage());
- }
- $parameters = array_values($parameters);
- return new $service(...$parameters);
- }
- /**
- * @throws ServiceNotFoundException
- * @throws ContainerException
- */
- public static function get(string $service): mixed
- {
- try {
- return Artful::getContainer()->get($service);
- } catch (NotFoundExceptionInterface $e) {
- throw new ServiceNotFoundException('服务未找到: '.$e->getMessage());
- } catch (ContainerNotFoundException $e) {
- throw $e;
- } catch (Throwable $e) {
- throw new ContainerException('容器异常: '.$e->getMessage());
- }
- }
- /**
- * @throws ContainerNotFoundException
- */
- public static function has(string $service): bool
- {
- return Artful::getContainer()->has($service);
- }
- public static function setContainer(null|Closure|ContainerInterface $container): void
- {
- self::$container = $container;
- }
- /**
- * @throws ContainerNotFoundException
- */
- public static function getContainer(): ContainerInterface
- {
- if (self::$container instanceof ContainerInterface) {
- return self::$container;
- }
- if (self::$container instanceof Closure) {
- return (self::$container)();
- }
- throw new ContainerNotFoundException('容器未找到: `getContainer()` 方法调用失败! 或许你应该先 `setContainer()`');
- }
- public static function hasContainer(): bool
- {
- return self::$container instanceof ContainerInterface || self::$container instanceof Closure;
- }
- public static function clear(): void
- {
- self::$container = null;
- }
- /**
- * @throws ContainerException
- */
- public static function load(string $service, mixed $data = null): void
- {
- self::registerService($service, $data);
- }
- /**
- * @throws ContainerException
- */
- public static function registerService(string $service, mixed $data = null): void
- {
- $var = new $service();
- if ($var instanceof ServiceProviderInterface) {
- $var->register($data);
- }
- }
- /**
- * @throws ContainerException
- * @throws InvalidParamsException
- * @throws ServiceNotFoundException
- */
- public static function shortcut(string $shortcut, array $params = []): null|Collection|MessageInterface|Rocket
- {
- if (!class_exists($shortcut) || !in_array(ShortcutInterface::class, class_implements($shortcut))) {
- throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_INVALID, "参数异常: [{$shortcut}] 未实现 `ShortcutInterface`");
- }
- /* @var ShortcutInterface $shortcutInstance */
- $shortcutInstance = self::get($shortcut);
- return self::artful($shortcutInstance->getPlugins($params), $params);
- }
- /**
- * @throws ContainerException
- * @throws InvalidParamsException
- */
- public static function artful(array $plugins, array $params): null|Collection|MessageInterface|Rocket
- {
- Event::dispatch(new Event\ArtfulStart($plugins, $params));
- self::verifyPlugin($plugins);
- /* @var Pipeline $pipeline */
- $pipeline = self::make(Pipeline::class);
- /* @var Rocket $rocket */
- $rocket = $pipeline
- ->send((new Rocket())->setParams($params)->setPayload(new Collection()))
- ->through($plugins)
- ->via('assembly')
- ->then(static fn ($rocket) => self::ignite($rocket));
- Event::dispatch(new Event\ArtfulEnd($rocket));
- if (!empty($params['_return_rocket'])) {
- return $rocket;
- }
- return $rocket->getDestination();
- }
- /**
- * @throws ContainerException
- * @throws InvalidParamsException
- * @throws InvalidResponseException
- * @throws ServiceNotFoundException
- */
- public static function ignite(Rocket $rocket): Rocket
- {
- if (!should_do_http_request($rocket->getDirection())) {
- return $rocket;
- }
- /* @var HttpClientFactoryInterface $httpFactory */
- $httpFactory = self::get(HttpClientFactoryInterface::class);
- /* @var Config $config */
- $config = self::get(ConfigInterface::class);
- if (!$httpFactory instanceof HttpClientFactoryInterface) {
- throw new InvalidParamsException(Exception::PARAMS_HTTP_CLIENT_FACTORY_INVALID, '参数异常: 配置的 `HttpClientFactoryInterface` 不符合规范');
- }
- Logger::info('[Artful] 准备请求第三方 API', $rocket->toArray());
- $http = $httpFactory->create(array_merge($config->get('http', []), $rocket->getPayload()?->get('_http') ?? []));
- Event::dispatch(new Event\HttpStart($rocket));
- try {
- $response = $http->sendRequest($rocket->getRadar());
- $rocket->setDestination(clone $response)
- ->setDestinationOrigin(clone $response);
- } catch (Throwable $e) {
- Logger::error('[Artful] 请求第三方 API 出错', ['message' => $e->getMessage(), 'rocket' => $rocket->toArray(), 'trace' => $e->getTrace()]);
- throw new InvalidResponseException(Exception::REQUEST_RESPONSE_ERROR, '响应异常: 请求第三方 API 出错 - '.$e->getMessage(), [], $e);
- }
- Logger::info('[Artful] 请求第三方 API 成功', ['rocket' => $rocket->toArray()]);
- Event::dispatch(new Event\HttpEnd($rocket));
- return $rocket;
- }
- /**
- * @throws InvalidParamsException
- */
- protected static function verifyPlugin(array $plugins): void
- {
- foreach ($plugins as $plugin) {
- if (is_callable($plugin)) {
- continue;
- }
- if ((is_object($plugin)
- || (is_string($plugin) && class_exists($plugin)))
- && in_array(PluginInterface::class, class_implements($plugin))) {
- continue;
- }
- throw new InvalidParamsException(Exception::PARAMS_PLUGIN_INCOMPATIBLE, "参数异常: [{$plugin}] 插件未实现 `PluginInterface`");
- }
- }
- /**
- * @throws ContainerException
- */
- private function registerServices(array $config, null|Closure|ContainerInterface $container = null): void
- {
- foreach ($this->coreService as $service) {
- self::registerService($service, ContainerServiceProvider::class == $service ? $container : $config);
- }
- }
- }
|