Artful.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <?php
  2. declare(strict_types=1);
  3. namespace Yansongda\Artful;
  4. use Closure;
  5. use Illuminate\Container\Container as LaravelContainer;
  6. use Psr\Container\ContainerInterface;
  7. use Psr\Container\NotFoundExceptionInterface;
  8. use Psr\Http\Message\MessageInterface;
  9. use Throwable;
  10. use Yansongda\Artful\Contract\ConfigInterface;
  11. use Yansongda\Artful\Contract\DirectionInterface;
  12. use Yansongda\Artful\Contract\HttpClientFactoryInterface;
  13. use Yansongda\Artful\Contract\PackerInterface;
  14. use Yansongda\Artful\Contract\PluginInterface;
  15. use Yansongda\Artful\Contract\ServiceProviderInterface;
  16. use Yansongda\Artful\Contract\ShortcutInterface;
  17. use Yansongda\Artful\Direction\CollectionDirection;
  18. use Yansongda\Artful\Exception\ContainerException;
  19. use Yansongda\Artful\Exception\ContainerNotFoundException;
  20. use Yansongda\Artful\Exception\Exception;
  21. use Yansongda\Artful\Exception\InvalidParamsException;
  22. use Yansongda\Artful\Exception\InvalidResponseException;
  23. use Yansongda\Artful\Exception\ServiceNotFoundException;
  24. use Yansongda\Artful\Packer\JsonPacker;
  25. use Yansongda\Artful\Service\ConfigServiceProvider;
  26. use Yansongda\Artful\Service\ContainerServiceProvider;
  27. use Yansongda\Artful\Service\EventServiceProvider;
  28. use Yansongda\Artful\Service\HttpServiceProvider;
  29. use Yansongda\Artful\Service\LoggerServiceProvider;
  30. use Yansongda\Supports\Collection;
  31. use Yansongda\Supports\Config;
  32. use Yansongda\Supports\Pipeline;
  33. class Artful
  34. {
  35. /**
  36. * @var string[]
  37. */
  38. private array $coreService = [
  39. ContainerServiceProvider::class,
  40. ConfigServiceProvider::class,
  41. LoggerServiceProvider::class,
  42. EventServiceProvider::class,
  43. HttpServiceProvider::class,
  44. ];
  45. private static null|Closure|ContainerInterface $container = null;
  46. /**
  47. * @throws ContainerException
  48. */
  49. private function __construct(array $config, null|Closure|ContainerInterface $container = null)
  50. {
  51. $this->registerServices($config, $container);
  52. Artful::set(DirectionInterface::class, CollectionDirection::class);
  53. Artful::set(PackerInterface::class, JsonPacker::class);
  54. }
  55. /**
  56. * @return mixed
  57. *
  58. * @throws ContainerException
  59. * @throws ServiceNotFoundException
  60. */
  61. public static function __callStatic(string $service, array $config)
  62. {
  63. if (!empty($config)) {
  64. self::config(...$config);
  65. }
  66. return self::get($service);
  67. }
  68. /**
  69. * @throws ContainerException
  70. */
  71. public static function config(array $config = [], null|Closure|ContainerInterface $container = null): bool
  72. {
  73. if (self::hasContainer() && !($config['_force'] ?? false)) {
  74. return false;
  75. }
  76. new self($config, $container);
  77. return true;
  78. }
  79. /**
  80. * @codeCoverageIgnore
  81. *
  82. * @throws ContainerException
  83. */
  84. public static function set(string $name, mixed $value): void
  85. {
  86. try {
  87. $container = Artful::getContainer();
  88. if ($container instanceof LaravelContainer) {
  89. $container->singleton($name, $value instanceof Closure ? $value : static fn () => $value);
  90. return;
  91. }
  92. if (method_exists($container, 'set')) {
  93. $container->set(...func_get_args());
  94. return;
  95. }
  96. } catch (ContainerNotFoundException $e) {
  97. throw $e;
  98. } catch (Throwable $e) {
  99. throw new ContainerException('容器异常: '.$e->getMessage());
  100. }
  101. throw new ContainerException('容器异常: 当前容器类型不支持 `set` 方法');
  102. }
  103. /**
  104. * @codeCoverageIgnore
  105. *
  106. * @throws ContainerException
  107. */
  108. public static function make(string $service, array $parameters = []): mixed
  109. {
  110. try {
  111. $container = Artful::getContainer();
  112. if (method_exists($container, 'make')) {
  113. return $container->make(...func_get_args());
  114. }
  115. } catch (ContainerNotFoundException $e) {
  116. throw $e;
  117. } catch (Throwable $e) {
  118. throw new ContainerException('容器异常: '.$e->getMessage());
  119. }
  120. $parameters = array_values($parameters);
  121. return new $service(...$parameters);
  122. }
  123. /**
  124. * @throws ServiceNotFoundException
  125. * @throws ContainerException
  126. */
  127. public static function get(string $service): mixed
  128. {
  129. try {
  130. return Artful::getContainer()->get($service);
  131. } catch (NotFoundExceptionInterface $e) {
  132. throw new ServiceNotFoundException('服务未找到: '.$e->getMessage());
  133. } catch (ContainerNotFoundException $e) {
  134. throw $e;
  135. } catch (Throwable $e) {
  136. throw new ContainerException('容器异常: '.$e->getMessage());
  137. }
  138. }
  139. /**
  140. * @throws ContainerNotFoundException
  141. */
  142. public static function has(string $service): bool
  143. {
  144. return Artful::getContainer()->has($service);
  145. }
  146. public static function setContainer(null|Closure|ContainerInterface $container): void
  147. {
  148. self::$container = $container;
  149. }
  150. /**
  151. * @throws ContainerNotFoundException
  152. */
  153. public static function getContainer(): ContainerInterface
  154. {
  155. if (self::$container instanceof ContainerInterface) {
  156. return self::$container;
  157. }
  158. if (self::$container instanceof Closure) {
  159. return (self::$container)();
  160. }
  161. throw new ContainerNotFoundException('容器未找到: `getContainer()` 方法调用失败! 或许你应该先 `setContainer()`');
  162. }
  163. public static function hasContainer(): bool
  164. {
  165. return self::$container instanceof ContainerInterface || self::$container instanceof Closure;
  166. }
  167. public static function clear(): void
  168. {
  169. self::$container = null;
  170. }
  171. /**
  172. * @throws ContainerException
  173. */
  174. public static function load(string $service, mixed $data = null): void
  175. {
  176. self::registerService($service, $data);
  177. }
  178. /**
  179. * @throws ContainerException
  180. */
  181. public static function registerService(string $service, mixed $data = null): void
  182. {
  183. $var = new $service();
  184. if ($var instanceof ServiceProviderInterface) {
  185. $var->register($data);
  186. }
  187. }
  188. /**
  189. * @throws ContainerException
  190. * @throws InvalidParamsException
  191. * @throws ServiceNotFoundException
  192. */
  193. public static function shortcut(string $shortcut, array $params = []): null|Collection|MessageInterface|Rocket
  194. {
  195. if (!class_exists($shortcut) || !in_array(ShortcutInterface::class, class_implements($shortcut))) {
  196. throw new InvalidParamsException(Exception::PARAMS_SHORTCUT_INVALID, "参数异常: [{$shortcut}] 未实现 `ShortcutInterface`");
  197. }
  198. /* @var ShortcutInterface $shortcutInstance */
  199. $shortcutInstance = self::get($shortcut);
  200. return self::artful($shortcutInstance->getPlugins($params), $params);
  201. }
  202. /**
  203. * @throws ContainerException
  204. * @throws InvalidParamsException
  205. */
  206. public static function artful(array $plugins, array $params): null|Collection|MessageInterface|Rocket
  207. {
  208. Event::dispatch(new Event\ArtfulStart($plugins, $params));
  209. self::verifyPlugin($plugins);
  210. /* @var Pipeline $pipeline */
  211. $pipeline = self::make(Pipeline::class);
  212. /* @var Rocket $rocket */
  213. $rocket = $pipeline
  214. ->send((new Rocket())->setParams($params)->setPayload(new Collection()))
  215. ->through($plugins)
  216. ->via('assembly')
  217. ->then(static fn ($rocket) => self::ignite($rocket));
  218. Event::dispatch(new Event\ArtfulEnd($rocket));
  219. if (!empty($params['_return_rocket'])) {
  220. return $rocket;
  221. }
  222. return $rocket->getDestination();
  223. }
  224. /**
  225. * @throws ContainerException
  226. * @throws InvalidParamsException
  227. * @throws InvalidResponseException
  228. * @throws ServiceNotFoundException
  229. */
  230. public static function ignite(Rocket $rocket): Rocket
  231. {
  232. if (!should_do_http_request($rocket->getDirection())) {
  233. return $rocket;
  234. }
  235. /* @var HttpClientFactoryInterface $httpFactory */
  236. $httpFactory = self::get(HttpClientFactoryInterface::class);
  237. /* @var Config $config */
  238. $config = self::get(ConfigInterface::class);
  239. if (!$httpFactory instanceof HttpClientFactoryInterface) {
  240. throw new InvalidParamsException(Exception::PARAMS_HTTP_CLIENT_FACTORY_INVALID, '参数异常: 配置的 `HttpClientFactoryInterface` 不符合规范');
  241. }
  242. Logger::info('[Artful] 准备请求第三方 API', $rocket->toArray());
  243. $http = $httpFactory->create(array_merge($config->get('http', []), $rocket->getPayload()?->get('_http') ?? []));
  244. Event::dispatch(new Event\HttpStart($rocket));
  245. try {
  246. $response = $http->sendRequest($rocket->getRadar());
  247. $rocket->setDestination(clone $response)
  248. ->setDestinationOrigin(clone $response);
  249. } catch (Throwable $e) {
  250. Logger::error('[Artful] 请求第三方 API 出错', ['message' => $e->getMessage(), 'rocket' => $rocket->toArray(), 'trace' => $e->getTrace()]);
  251. throw new InvalidResponseException(Exception::REQUEST_RESPONSE_ERROR, '响应异常: 请求第三方 API 出错 - '.$e->getMessage(), [], $e);
  252. }
  253. Logger::info('[Artful] 请求第三方 API 成功', ['rocket' => $rocket->toArray()]);
  254. Event::dispatch(new Event\HttpEnd($rocket));
  255. return $rocket;
  256. }
  257. /**
  258. * @throws InvalidParamsException
  259. */
  260. protected static function verifyPlugin(array $plugins): void
  261. {
  262. foreach ($plugins as $plugin) {
  263. if (is_callable($plugin)) {
  264. continue;
  265. }
  266. if ((is_object($plugin)
  267. || (is_string($plugin) && class_exists($plugin)))
  268. && in_array(PluginInterface::class, class_implements($plugin))) {
  269. continue;
  270. }
  271. throw new InvalidParamsException(Exception::PARAMS_PLUGIN_INCOMPATIBLE, "参数异常: [{$plugin}] 插件未实现 `PluginInterface`");
  272. }
  273. }
  274. /**
  275. * @throws ContainerException
  276. */
  277. private function registerServices(array $config, null|Closure|ContainerInterface $container = null): void
  278. {
  279. foreach ($this->coreService as $service) {
  280. self::registerService($service, ContainerServiceProvider::class == $service ? $container : $config);
  281. }
  282. }
  283. }