ServiceProvider.php 16 KB

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