ServiceProvider.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. <?php
  2. namespace Illuminate\Support;
  3. use Closure;
  4. use Illuminate\Console\Application as Artisan;
  5. use Illuminate\Contracts\Foundation\CachesConfiguration;
  6. use Illuminate\Contracts\Foundation\CachesRoutes;
  7. use Illuminate\Contracts\Support\DeferrableProvider;
  8. use Illuminate\Database\Eloquent\Factory as ModelFactory;
  9. use Illuminate\View\Compilers\BladeCompiler;
  10. /**
  11. * @property array<string, string> $bindings All of the container bindings that should be registered.
  12. * @property array<array-key, string> $singletons All of the singletons that should be registered.
  13. */
  14. abstract class ServiceProvider
  15. {
  16. /**
  17. * The application instance.
  18. *
  19. * @var \Illuminate\Contracts\Foundation\Application
  20. */
  21. protected $app;
  22. /**
  23. * All of the registered booting callbacks.
  24. *
  25. * @var array
  26. */
  27. protected $bootingCallbacks = [];
  28. /**
  29. * All of the registered booted callbacks.
  30. *
  31. * @var array
  32. */
  33. protected $bootedCallbacks = [];
  34. /**
  35. * The paths that should be published.
  36. *
  37. * @var array
  38. */
  39. public static $publishes = [];
  40. /**
  41. * The paths that should be published by group.
  42. *
  43. * @var array
  44. */
  45. public static $publishGroups = [];
  46. /**
  47. * The migration paths available for publishing.
  48. *
  49. * @var array
  50. */
  51. protected static $publishableMigrationPaths = [];
  52. /**
  53. * Commands that should be run during the "optimize" command.
  54. *
  55. * @var array<string, string>
  56. */
  57. public static array $optimizeCommands = [];
  58. /**
  59. * Commands that should be run during the "optimize:clear" command.
  60. *
  61. * @var array<string, string>
  62. */
  63. public static array $optimizeClearCommands = [];
  64. /**
  65. * Commands that should be run during the "reload" command.
  66. *
  67. * @var array<string, string>
  68. */
  69. public static array $reloadCommands = [];
  70. /**
  71. * Create a new service provider instance.
  72. *
  73. * @param \Illuminate\Contracts\Foundation\Application $app
  74. */
  75. public function __construct($app)
  76. {
  77. $this->app = $app;
  78. }
  79. /**
  80. * Register any application services.
  81. *
  82. * @return void
  83. */
  84. public function register()
  85. {
  86. //
  87. }
  88. /**
  89. * Register a booting callback to be run before the "boot" method is called.
  90. *
  91. * @param \Closure $callback
  92. * @return void
  93. */
  94. public function booting(Closure $callback)
  95. {
  96. $this->bootingCallbacks[] = $callback;
  97. }
  98. /**
  99. * Register a booted callback to be run after the "boot" method is called.
  100. *
  101. * @param \Closure $callback
  102. * @return void
  103. */
  104. public function booted(Closure $callback)
  105. {
  106. $this->bootedCallbacks[] = $callback;
  107. }
  108. /**
  109. * Call the registered booting callbacks.
  110. *
  111. * @return void
  112. */
  113. public function callBootingCallbacks()
  114. {
  115. $index = 0;
  116. while ($index < count($this->bootingCallbacks)) {
  117. $this->app->call($this->bootingCallbacks[$index]);
  118. $index++;
  119. }
  120. }
  121. /**
  122. * Call the registered booted callbacks.
  123. *
  124. * @return void
  125. */
  126. public function callBootedCallbacks()
  127. {
  128. $index = 0;
  129. while ($index < count($this->bootedCallbacks)) {
  130. $this->app->call($this->bootedCallbacks[$index]);
  131. $index++;
  132. }
  133. }
  134. /**
  135. * Merge the given configuration with the existing configuration.
  136. *
  137. * @param string $path
  138. * @param string $key
  139. * @return void
  140. */
  141. protected function mergeConfigFrom($path, $key)
  142. {
  143. if (! ($this->app instanceof CachesConfiguration && $this->app->configurationIsCached())) {
  144. $config = $this->app->make('config');
  145. $config->set($key, array_merge(
  146. require $path, $config->get($key, [])
  147. ));
  148. }
  149. }
  150. /**
  151. * Replace the given configuration with the existing configuration recursively.
  152. *
  153. * @param string $path
  154. * @param string $key
  155. * @return void
  156. */
  157. protected function replaceConfigRecursivelyFrom($path, $key)
  158. {
  159. if (! ($this->app instanceof CachesConfiguration && $this->app->configurationIsCached())) {
  160. $config = $this->app->make('config');
  161. $config->set($key, array_replace_recursive(
  162. require $path, $config->get($key, [])
  163. ));
  164. }
  165. }
  166. /**
  167. * Load the given routes file if routes are not already cached.
  168. *
  169. * @param string $path
  170. * @return void
  171. */
  172. protected function loadRoutesFrom($path)
  173. {
  174. if (! ($this->app instanceof CachesRoutes && $this->app->routesAreCached())) {
  175. require $path;
  176. }
  177. }
  178. /**
  179. * Register a view file namespace.
  180. *
  181. * @param string|array $path
  182. * @param string $namespace
  183. * @return void
  184. */
  185. protected function loadViewsFrom($path, $namespace)
  186. {
  187. $this->callAfterResolving('view', function ($view) use ($path, $namespace) {
  188. if (isset($this->app->config['view']['paths']) &&
  189. is_array($this->app->config['view']['paths'])) {
  190. foreach ($this->app->config['view']['paths'] as $viewPath) {
  191. if (is_dir($appPath = $viewPath.'/vendor/'.$namespace)) {
  192. $view->addNamespace($namespace, $appPath);
  193. }
  194. }
  195. }
  196. $view->addNamespace($namespace, $path);
  197. });
  198. }
  199. /**
  200. * Register the given view components with a custom prefix.
  201. *
  202. * @param string $prefix
  203. * @param array $components
  204. * @return void
  205. */
  206. protected function loadViewComponentsAs($prefix, array $components)
  207. {
  208. $this->callAfterResolving(BladeCompiler::class, function ($blade) use ($prefix, $components) {
  209. foreach ($components as $alias => $component) {
  210. $blade->component($component, is_string($alias) ? $alias : null, $prefix);
  211. }
  212. });
  213. }
  214. /**
  215. * Register a translation file namespace or path.
  216. *
  217. * @param string $path
  218. * @param string|null $namespace
  219. * @return void
  220. */
  221. protected function loadTranslationsFrom($path, $namespace = null)
  222. {
  223. $this->callAfterResolving('translator', fn ($translator) => is_null($namespace)
  224. ? $translator->addPath($path)
  225. : $translator->addNamespace($namespace, $path));
  226. }
  227. /**
  228. * Register a JSON translation file path.
  229. *
  230. * @param string $path
  231. * @return void
  232. */
  233. protected function loadJsonTranslationsFrom($path)
  234. {
  235. $this->callAfterResolving('translator', function ($translator) use ($path) {
  236. $translator->addJsonPath($path);
  237. });
  238. }
  239. /**
  240. * Register database migration paths.
  241. *
  242. * @param array|string $paths
  243. * @return void
  244. */
  245. protected function loadMigrationsFrom($paths)
  246. {
  247. $this->callAfterResolving('migrator', function ($migrator) use ($paths) {
  248. foreach ((array) $paths as $path) {
  249. $migrator->path($path);
  250. }
  251. });
  252. }
  253. /**
  254. * Register Eloquent model factory paths.
  255. *
  256. * @deprecated Will be removed in a future Laravel version.
  257. *
  258. * @param array|string $paths
  259. * @return void
  260. */
  261. protected function loadFactoriesFrom($paths)
  262. {
  263. $this->callAfterResolving(ModelFactory::class, function ($factory) use ($paths) {
  264. foreach ((array) $paths as $path) {
  265. $factory->load($path);
  266. }
  267. });
  268. }
  269. /**
  270. * Setup an after resolving listener, or fire immediately if already resolved.
  271. *
  272. * @param string $name
  273. * @param callable $callback
  274. * @return void
  275. */
  276. protected function callAfterResolving($name, $callback)
  277. {
  278. $this->app->afterResolving($name, $callback);
  279. if ($this->app->resolved($name)) {
  280. $callback($this->app->make($name), $this->app);
  281. }
  282. }
  283. /**
  284. * Register migration paths to be published by the publish command.
  285. *
  286. * @param array $paths
  287. * @param mixed $groups
  288. * @return void
  289. */
  290. protected function publishesMigrations(array $paths, $groups = null)
  291. {
  292. $this->publishes($paths, $groups);
  293. if ($this->app->config->get('database.migrations.update_date_on_publish', false)) {
  294. static::$publishableMigrationPaths = array_unique(array_merge(static::$publishableMigrationPaths, array_keys($paths)));
  295. }
  296. }
  297. /**
  298. * Register paths to be published by the publish command.
  299. *
  300. * @param array $paths
  301. * @param mixed $groups
  302. * @return void
  303. */
  304. protected function publishes(array $paths, $groups = null)
  305. {
  306. $this->ensurePublishArrayInitialized($class = static::class);
  307. static::$publishes[$class] = array_merge(static::$publishes[$class], $paths);
  308. foreach ((array) $groups as $group) {
  309. $this->addPublishGroup($group, $paths);
  310. }
  311. }
  312. /**
  313. * Ensure the publish array for the service provider is initialized.
  314. *
  315. * @param string $class
  316. * @return void
  317. */
  318. protected function ensurePublishArrayInitialized($class)
  319. {
  320. if (! array_key_exists($class, static::$publishes)) {
  321. static::$publishes[$class] = [];
  322. }
  323. }
  324. /**
  325. * Add a publish group / tag to the service provider.
  326. *
  327. * @param string $group
  328. * @param array $paths
  329. * @return void
  330. */
  331. protected function addPublishGroup($group, $paths)
  332. {
  333. if (! array_key_exists($group, static::$publishGroups)) {
  334. static::$publishGroups[$group] = [];
  335. }
  336. static::$publishGroups[$group] = array_merge(
  337. static::$publishGroups[$group], $paths
  338. );
  339. }
  340. /**
  341. * Get the paths to publish.
  342. *
  343. * @param string|null $provider
  344. * @param string|null $group
  345. * @return array
  346. */
  347. public static function pathsToPublish($provider = null, $group = null)
  348. {
  349. if (! is_null($paths = static::pathsForProviderOrGroup($provider, $group))) {
  350. return $paths;
  351. }
  352. return (new Collection(static::$publishes))->reduce(function ($paths, $p) {
  353. return array_merge($paths, $p);
  354. }, []);
  355. }
  356. /**
  357. * Get the paths for the provider or group (or both).
  358. *
  359. * @param string|null $provider
  360. * @param string|null $group
  361. * @return array
  362. */
  363. protected static function pathsForProviderOrGroup($provider, $group)
  364. {
  365. if ($provider && $group) {
  366. return static::pathsForProviderAndGroup($provider, $group);
  367. } elseif ($group && array_key_exists($group, static::$publishGroups)) {
  368. return static::$publishGroups[$group];
  369. } elseif ($provider && array_key_exists($provider, static::$publishes)) {
  370. return static::$publishes[$provider];
  371. } elseif ($group || $provider) {
  372. return [];
  373. }
  374. }
  375. /**
  376. * Get the paths for the provider and group.
  377. *
  378. * @param string $provider
  379. * @param string $group
  380. * @return array
  381. */
  382. protected static function pathsForProviderAndGroup($provider, $group)
  383. {
  384. if (! empty(static::$publishes[$provider]) && ! empty(static::$publishGroups[$group])) {
  385. return array_intersect_key(static::$publishes[$provider], static::$publishGroups[$group]);
  386. }
  387. return [];
  388. }
  389. /**
  390. * Get the service providers available for publishing.
  391. *
  392. * @return array
  393. */
  394. public static function publishableProviders()
  395. {
  396. return array_keys(static::$publishes);
  397. }
  398. /**
  399. * Get the migration paths available for publishing.
  400. *
  401. * @return array
  402. */
  403. public static function publishableMigrationPaths()
  404. {
  405. return static::$publishableMigrationPaths;
  406. }
  407. /**
  408. * Get the groups available for publishing.
  409. *
  410. * @return array
  411. */
  412. public static function publishableGroups()
  413. {
  414. return array_keys(static::$publishGroups);
  415. }
  416. /**
  417. * Register the package's custom Artisan commands.
  418. *
  419. * @param mixed $commands
  420. * @return void
  421. */
  422. public function commands($commands)
  423. {
  424. $commands = is_array($commands) ? $commands : func_get_args();
  425. Artisan::starting(function ($artisan) use ($commands) {
  426. $artisan->resolveCommands($commands);
  427. });
  428. }
  429. /**
  430. * Register commands that should run on "optimize" or "optimize:clear".
  431. *
  432. * @param string|null $optimize
  433. * @param string|null $clear
  434. * @param string|null $key
  435. * @return void
  436. */
  437. protected function optimizes(?string $optimize = null, ?string $clear = null, ?string $key = null)
  438. {
  439. $key = $this->getProviderKey($key);
  440. if ($optimize) {
  441. static::$optimizeCommands[$key] = $optimize;
  442. }
  443. if ($clear) {
  444. static::$optimizeClearCommands[$key] = $clear;
  445. }
  446. }
  447. /**
  448. * Register commands that should run on "reload".
  449. *
  450. * @param string|null $reload
  451. * @param string|null $key
  452. * @return void
  453. */
  454. protected function reloads(string $reload, ?string $key = null)
  455. {
  456. $key = $this->getProviderKey($key);
  457. static::$reloadCommands[$key] = $reload;
  458. }
  459. /**
  460. * Get a short descriptive key for the current service provider.
  461. *
  462. * @param string|null $key
  463. * @return string
  464. */
  465. protected function getProviderKey(?string $key = null): string
  466. {
  467. $key ??= (string) Str::of(get_class($this))
  468. ->classBasename()
  469. ->before('ServiceProvider')
  470. ->kebab()
  471. ->lower()
  472. ->trim();
  473. if (empty($key)) {
  474. $key = class_basename(get_class($this));
  475. }
  476. return $key;
  477. }
  478. /**
  479. * Get the services provided by the provider.
  480. *
  481. * @return array
  482. */
  483. public function provides()
  484. {
  485. return [];
  486. }
  487. /**
  488. * Get the events that trigger this service provider to register.
  489. *
  490. * @return array
  491. */
  492. public function when()
  493. {
  494. return [];
  495. }
  496. /**
  497. * Determine if the provider is deferred.
  498. *
  499. * @return bool
  500. */
  501. public function isDeferred()
  502. {
  503. return $this instanceof DeferrableProvider;
  504. }
  505. /**
  506. * Get the default providers for a Laravel application.
  507. *
  508. * @return \Illuminate\Support\DefaultProviders
  509. */
  510. public static function defaultProviders()
  511. {
  512. return new DefaultProviders;
  513. }
  514. /**
  515. * Add the given provider to the application's provider bootstrap file.
  516. *
  517. * @param string $provider
  518. * @param string|null $path
  519. * @return bool
  520. */
  521. public static function addProviderToBootstrapFile(string $provider, ?string $path = null)
  522. {
  523. $path ??= app()->getBootstrapProvidersPath();
  524. if (! file_exists($path)) {
  525. return false;
  526. }
  527. if (function_exists('opcache_invalidate')) {
  528. opcache_invalidate($path, true);
  529. }
  530. $providers = (new Collection(require $path))
  531. ->merge([$provider])
  532. ->unique()
  533. ->sort()
  534. ->values()
  535. ->map(fn ($p) => ' '.$p.'::class,')
  536. ->implode(PHP_EOL);
  537. $content = '<?php
  538. return [
  539. '.$providers.'
  540. ];';
  541. file_put_contents($path, $content.PHP_EOL);
  542. return true;
  543. }
  544. /**
  545. * Remove a provider from the application's provider bootstrap file.
  546. *
  547. * @param string|array $providersToRemove
  548. * @param string|null $path
  549. * @param bool $strict
  550. * @return bool
  551. */
  552. public static function removeProviderFromBootstrapFile(string|array $providersToRemove, ?string $path = null, bool $strict = false)
  553. {
  554. $path ??= app()->getBootstrapProvidersPath();
  555. if (! file_exists($path)) {
  556. return false;
  557. }
  558. if (function_exists('opcache_invalidate')) {
  559. opcache_invalidate($path, true);
  560. }
  561. $providersToRemove = Arr::wrap($providersToRemove);
  562. $providers = (new Collection(require $path))
  563. ->unique()
  564. ->sort()
  565. ->values()
  566. ->when(
  567. $strict,
  568. static fn (Collection $providerCollection) => $providerCollection->reject(fn (string $p) => in_array($p, $providersToRemove, true)),
  569. static fn (Collection $providerCollection) => $providerCollection->reject(fn (string $p) => Str::contains($p, $providersToRemove))
  570. )
  571. ->map(fn ($p) => ' '.$p.'::class,')
  572. ->implode(PHP_EOL);
  573. $content = '<?php
  574. return [
  575. '.$providers.'
  576. ];';
  577. file_put_contents($path, $content.PHP_EOL);
  578. return true;
  579. }
  580. }