DependencyFactory.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <?php
  2. declare(strict_types=1);
  3. namespace Doctrine\Migrations;
  4. use Doctrine\DBAL\Connection;
  5. use Doctrine\Migrations\Configuration\Configuration;
  6. use Doctrine\Migrations\Configuration\Connection\ConnectionLoader;
  7. use Doctrine\Migrations\Configuration\EntityManager\EntityManagerLoader;
  8. use Doctrine\Migrations\Configuration\Migration\ConfigurationLoader;
  9. use Doctrine\Migrations\Exception\FrozenDependencies;
  10. use Doctrine\Migrations\Exception\MissingDependency;
  11. use Doctrine\Migrations\Finder\GlobFinder;
  12. use Doctrine\Migrations\Finder\MigrationFinder;
  13. use Doctrine\Migrations\Finder\RecursiveRegexFinder;
  14. use Doctrine\Migrations\Generator\ClassNameGenerator;
  15. use Doctrine\Migrations\Generator\ConcatenationFileBuilder;
  16. use Doctrine\Migrations\Generator\DiffGenerator;
  17. use Doctrine\Migrations\Generator\FileBuilder;
  18. use Doctrine\Migrations\Generator\Generator;
  19. use Doctrine\Migrations\Generator\SqlGenerator;
  20. use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
  21. use Doctrine\Migrations\Metadata\Storage\TableMetadataStorage;
  22. use Doctrine\Migrations\Metadata\Storage\TableMetadataStorageConfiguration;
  23. use Doctrine\Migrations\Provider\DBALSchemaDiffProvider;
  24. use Doctrine\Migrations\Provider\EmptySchemaProvider;
  25. use Doctrine\Migrations\Provider\LazySchemaDiffProvider;
  26. use Doctrine\Migrations\Provider\OrmSchemaProvider;
  27. use Doctrine\Migrations\Provider\SchemaDiffProvider;
  28. use Doctrine\Migrations\Provider\SchemaProvider;
  29. use Doctrine\Migrations\Tools\Console\ConsoleInputMigratorConfigurationFactory;
  30. use Doctrine\Migrations\Tools\Console\Helper\MigrationStatusInfosHelper;
  31. use Doctrine\Migrations\Tools\Console\MigratorConfigurationFactory;
  32. use Doctrine\Migrations\Version\AliasResolver;
  33. use Doctrine\Migrations\Version\AlphabeticalComparator;
  34. use Doctrine\Migrations\Version\Comparator;
  35. use Doctrine\Migrations\Version\CurrentMigrationStatusCalculator;
  36. use Doctrine\Migrations\Version\DbalExecutor;
  37. use Doctrine\Migrations\Version\DbalMigrationFactory;
  38. use Doctrine\Migrations\Version\DefaultAliasResolver;
  39. use Doctrine\Migrations\Version\Executor;
  40. use Doctrine\Migrations\Version\MigrationFactory;
  41. use Doctrine\Migrations\Version\MigrationPlanCalculator;
  42. use Doctrine\Migrations\Version\MigrationStatusCalculator;
  43. use Doctrine\Migrations\Version\SortedMigrationPlanCalculator;
  44. use Doctrine\ORM\EntityManagerInterface;
  45. use Psr\Log\LoggerInterface;
  46. use Psr\Log\NullLogger;
  47. use Symfony\Component\Stopwatch\Stopwatch;
  48. use function array_key_exists;
  49. use function call_user_func;
  50. use function preg_quote;
  51. use function sprintf;
  52. /**
  53. * The DependencyFactory is responsible for wiring up and managing internal class dependencies.
  54. */
  55. class DependencyFactory
  56. {
  57. /** @psalm-var array<string, bool> */
  58. private $inResolution = [];
  59. /** @var Configuration */
  60. private $configuration;
  61. /** @var object[]|callable[] */
  62. private $dependencies = [];
  63. /** @var Connection */
  64. private $connection;
  65. /** @var EntityManagerInterface|null */
  66. private $em;
  67. /** @var bool */
  68. private $frozen = false;
  69. /** @var ConfigurationLoader */
  70. private $configurationLoader;
  71. /** @var ConnectionLoader */
  72. private $connectionLoader;
  73. /** @var EntityManagerLoader|null */
  74. private $emLoader;
  75. /** @var callable[] */
  76. private $factories = [];
  77. public static function fromConnection(
  78. ConfigurationLoader $configurationLoader,
  79. ConnectionLoader $connectionLoader,
  80. ?LoggerInterface $logger = null
  81. ): self {
  82. $dependencyFactory = new self($logger);
  83. $dependencyFactory->configurationLoader = $configurationLoader;
  84. $dependencyFactory->connectionLoader = $connectionLoader;
  85. return $dependencyFactory;
  86. }
  87. public static function fromEntityManager(
  88. ConfigurationLoader $configurationLoader,
  89. EntityManagerLoader $emLoader,
  90. ?LoggerInterface $logger = null
  91. ): self {
  92. $dependencyFactory = new self($logger);
  93. $dependencyFactory->configurationLoader = $configurationLoader;
  94. $dependencyFactory->emLoader = $emLoader;
  95. return $dependencyFactory;
  96. }
  97. private function __construct(?LoggerInterface $logger)
  98. {
  99. if ($logger === null) {
  100. return;
  101. }
  102. $this->setDefinition(LoggerInterface::class, static function () use ($logger): LoggerInterface {
  103. return $logger;
  104. });
  105. }
  106. public function isFrozen(): bool
  107. {
  108. return $this->frozen;
  109. }
  110. public function freeze(): void
  111. {
  112. $this->frozen = true;
  113. }
  114. private function assertNotFrozen(): void
  115. {
  116. if ($this->frozen) {
  117. throw FrozenDependencies::new();
  118. }
  119. }
  120. public function hasEntityManager(): bool
  121. {
  122. return $this->emLoader !== null;
  123. }
  124. public function setConfigurationLoader(ConfigurationLoader $configurationLoader): void
  125. {
  126. $this->assertNotFrozen();
  127. $this->configurationLoader = $configurationLoader;
  128. }
  129. public function getConfiguration(): Configuration
  130. {
  131. if ($this->configuration === null) {
  132. $this->configuration = $this->configurationLoader->getConfiguration();
  133. $this->freeze();
  134. }
  135. return $this->configuration;
  136. }
  137. public function getConnection(): Connection
  138. {
  139. if ($this->connection === null) {
  140. $this->connection = $this->hasEntityManager()
  141. ? $this->getEntityManager()->getConnection()
  142. : $this->connectionLoader->getConnection($this->getConfiguration()->getConnectionName());
  143. $this->freeze();
  144. }
  145. return $this->connection;
  146. }
  147. public function getEntityManager(): EntityManagerInterface
  148. {
  149. if ($this->em === null) {
  150. if ($this->emLoader === null) {
  151. throw MissingDependency::noEntityManager();
  152. }
  153. $this->em = $this->emLoader->getEntityManager($this->getConfiguration()->getEntityManagerName());
  154. $this->freeze();
  155. }
  156. return $this->em;
  157. }
  158. public function getVersionComparator(): Comparator
  159. {
  160. return $this->getDependency(Comparator::class, static function (): AlphabeticalComparator {
  161. return new AlphabeticalComparator();
  162. });
  163. }
  164. public function getLogger(): LoggerInterface
  165. {
  166. return $this->getDependency(LoggerInterface::class, static function (): LoggerInterface {
  167. return new NullLogger();
  168. });
  169. }
  170. public function getEventDispatcher(): EventDispatcher
  171. {
  172. return $this->getDependency(EventDispatcher::class, function (): EventDispatcher {
  173. return new EventDispatcher(
  174. $this->getConnection(),
  175. $this->getConnection()->getEventManager()
  176. );
  177. });
  178. }
  179. public function getClassNameGenerator(): ClassNameGenerator
  180. {
  181. return $this->getDependency(ClassNameGenerator::class, static function (): ClassNameGenerator {
  182. return new ClassNameGenerator();
  183. });
  184. }
  185. public function getSchemaDumper(): SchemaDumper
  186. {
  187. return $this->getDependency(SchemaDumper::class, function (): SchemaDumper {
  188. $excludedTables = [];
  189. $metadataConfig = $this->getConfiguration()->getMetadataStorageConfiguration();
  190. if ($metadataConfig instanceof TableMetadataStorageConfiguration) {
  191. $excludedTables[] = sprintf('/^%s$/', preg_quote($metadataConfig->getTableName(), '/'));
  192. }
  193. return new SchemaDumper(
  194. $this->getConnection()->getDatabasePlatform(),
  195. $this->getConnection()->getSchemaManager(),
  196. $this->getMigrationGenerator(),
  197. $this->getMigrationSqlGenerator(),
  198. $excludedTables
  199. );
  200. });
  201. }
  202. private function getEmptySchemaProvider(): SchemaProvider
  203. {
  204. return $this->getDependency(EmptySchemaProvider::class, function (): SchemaProvider {
  205. return new EmptySchemaProvider(
  206. $this->getConnection()->getSchemaManager()
  207. );
  208. });
  209. }
  210. public function hasSchemaProvider(): bool
  211. {
  212. try {
  213. $this->getSchemaProvider();
  214. } catch (MissingDependency $exception) {
  215. return false;
  216. }
  217. return true;
  218. }
  219. public function getSchemaProvider(): SchemaProvider
  220. {
  221. return $this->getDependency(SchemaProvider::class, function (): SchemaProvider {
  222. if ($this->hasEntityManager()) {
  223. return new OrmSchemaProvider($this->getEntityManager());
  224. }
  225. throw MissingDependency::noSchemaProvider();
  226. });
  227. }
  228. public function getDiffGenerator(): DiffGenerator
  229. {
  230. return $this->getDependency(DiffGenerator::class, function (): DiffGenerator {
  231. return new DiffGenerator(
  232. $this->getConnection()->getConfiguration(),
  233. $this->getConnection()->getSchemaManager(),
  234. $this->getSchemaProvider(),
  235. $this->getConnection()->getDatabasePlatform(),
  236. $this->getMigrationGenerator(),
  237. $this->getMigrationSqlGenerator(),
  238. $this->getEmptySchemaProvider()
  239. );
  240. });
  241. }
  242. public function getSchemaDiffProvider(): SchemaDiffProvider
  243. {
  244. return $this->getDependency(SchemaDiffProvider::class, function (): LazySchemaDiffProvider {
  245. return LazySchemaDiffProvider::fromDefaultProxyFactoryConfiguration(
  246. new DBALSchemaDiffProvider(
  247. $this->getConnection()->getSchemaManager(),
  248. $this->getConnection()->getDatabasePlatform()
  249. )
  250. );
  251. });
  252. }
  253. private function getFileBuilder(): FileBuilder
  254. {
  255. return $this->getDependency(FileBuilder::class, static function (): FileBuilder {
  256. return new ConcatenationFileBuilder();
  257. });
  258. }
  259. private function getParameterFormatter(): ParameterFormatter
  260. {
  261. return $this->getDependency(ParameterFormatter::class, function (): ParameterFormatter {
  262. return new InlineParameterFormatter($this->getConnection());
  263. });
  264. }
  265. public function getMigrationsFinder(): MigrationFinder
  266. {
  267. return $this->getDependency(MigrationFinder::class, function (): MigrationFinder {
  268. $configs = $this->getConfiguration();
  269. $needsRecursiveFinder = $configs->areMigrationsOrganizedByYear() || $configs->areMigrationsOrganizedByYearAndMonth();
  270. return $needsRecursiveFinder ? new RecursiveRegexFinder() : new GlobFinder();
  271. });
  272. }
  273. public function getMigrationRepository(): MigrationsRepository
  274. {
  275. return $this->getDependency(MigrationsRepository::class, function (): MigrationsRepository {
  276. return new FilesystemMigrationsRepository(
  277. $this->getConfiguration()->getMigrationClasses(),
  278. $this->getConfiguration()->getMigrationDirectories(),
  279. $this->getMigrationsFinder(),
  280. $this->getMigrationFactory()
  281. );
  282. });
  283. }
  284. public function getMigrationFactory(): MigrationFactory
  285. {
  286. return $this->getDependency(MigrationFactory::class, function (): MigrationFactory {
  287. return new DbalMigrationFactory($this->getConnection(), $this->getLogger());
  288. });
  289. }
  290. /**
  291. * @param object|callable $service
  292. */
  293. public function setService(string $id, $service): void
  294. {
  295. $this->assertNotFrozen();
  296. $this->dependencies[$id] = $service;
  297. }
  298. public function getMetadataStorage(): MetadataStorage
  299. {
  300. return $this->getDependency(MetadataStorage::class, function (): MetadataStorage {
  301. return new TableMetadataStorage(
  302. $this->getConnection(),
  303. $this->getVersionComparator(),
  304. $this->getConfiguration()->getMetadataStorageConfiguration(),
  305. $this->getMigrationRepository()
  306. );
  307. });
  308. }
  309. private function getVersionExecutor(): Executor
  310. {
  311. return $this->getDependency(Executor::class, function (): Executor {
  312. return new DbalExecutor(
  313. $this->getMetadataStorage(),
  314. $this->getEventDispatcher(),
  315. $this->getConnection(),
  316. $this->getSchemaDiffProvider(),
  317. $this->getLogger(),
  318. $this->getParameterFormatter(),
  319. $this->getStopwatch()
  320. );
  321. });
  322. }
  323. public function getQueryWriter(): QueryWriter
  324. {
  325. return $this->getDependency(QueryWriter::class, function (): QueryWriter {
  326. return new FileQueryWriter(
  327. $this->getFileBuilder(),
  328. $this->getLogger()
  329. );
  330. });
  331. }
  332. public function getVersionAliasResolver(): AliasResolver
  333. {
  334. return $this->getDependency(AliasResolver::class, function (): AliasResolver {
  335. return new DefaultAliasResolver(
  336. $this->getMigrationPlanCalculator(),
  337. $this->getMetadataStorage(),
  338. $this->getMigrationStatusCalculator()
  339. );
  340. });
  341. }
  342. public function getMigrationStatusCalculator(): MigrationStatusCalculator
  343. {
  344. return $this->getDependency(MigrationStatusCalculator::class, function (): MigrationStatusCalculator {
  345. return new CurrentMigrationStatusCalculator(
  346. $this->getMigrationPlanCalculator(),
  347. $this->getMetadataStorage()
  348. );
  349. });
  350. }
  351. public function getMigrationPlanCalculator(): MigrationPlanCalculator
  352. {
  353. return $this->getDependency(MigrationPlanCalculator::class, function (): MigrationPlanCalculator {
  354. return new SortedMigrationPlanCalculator(
  355. $this->getMigrationRepository(),
  356. $this->getMetadataStorage(),
  357. $this->getVersionComparator()
  358. );
  359. });
  360. }
  361. public function getMigrationGenerator(): Generator
  362. {
  363. return $this->getDependency(Generator::class, function (): Generator {
  364. return new Generator($this->getConfiguration());
  365. });
  366. }
  367. public function getMigrationSqlGenerator(): SqlGenerator
  368. {
  369. return $this->getDependency(SqlGenerator::class, function (): SqlGenerator {
  370. return new SqlGenerator(
  371. $this->getConfiguration(),
  372. $this->getConnection()->getDatabasePlatform()
  373. );
  374. });
  375. }
  376. public function getConsoleInputMigratorConfigurationFactory(): MigratorConfigurationFactory
  377. {
  378. return $this->getDependency(MigratorConfigurationFactory::class, function (): MigratorConfigurationFactory {
  379. return new ConsoleInputMigratorConfigurationFactory(
  380. $this->getConfiguration()
  381. );
  382. });
  383. }
  384. public function getMigrationStatusInfosHelper(): MigrationStatusInfosHelper
  385. {
  386. return $this->getDependency(MigrationStatusInfosHelper::class, function (): MigrationStatusInfosHelper {
  387. return new MigrationStatusInfosHelper(
  388. $this->getConfiguration(),
  389. $this->getConnection(),
  390. $this->getVersionAliasResolver(),
  391. $this->getMigrationPlanCalculator(),
  392. $this->getMigrationStatusCalculator(),
  393. $this->getMetadataStorage()
  394. );
  395. });
  396. }
  397. public function getMigrator(): Migrator
  398. {
  399. return $this->getDependency(Migrator::class, function (): Migrator {
  400. return new DbalMigrator(
  401. $this->getConnection(),
  402. $this->getEventDispatcher(),
  403. $this->getVersionExecutor(),
  404. $this->getLogger(),
  405. $this->getStopwatch()
  406. );
  407. });
  408. }
  409. public function getStopwatch(): Stopwatch
  410. {
  411. return $this->getDependency(Stopwatch::class, static function (): Stopwatch {
  412. return new Stopwatch(true);
  413. });
  414. }
  415. public function getRollup(): Rollup
  416. {
  417. return $this->getDependency(Rollup::class, function (): Rollup {
  418. return new Rollup(
  419. $this->getMetadataStorage(),
  420. $this->getMigrationRepository()
  421. );
  422. });
  423. }
  424. /**
  425. * @return mixed
  426. */
  427. private function getDependency(string $id, callable $callback)
  428. {
  429. if (! isset($this->inResolution[$id]) && array_key_exists($id, $this->factories) && ! array_key_exists($id, $this->dependencies)) {
  430. $this->inResolution[$id] = true;
  431. $this->dependencies[$id] = call_user_func($this->factories[$id], $this);
  432. unset($this->inResolution);
  433. }
  434. if (! array_key_exists($id, $this->dependencies)) {
  435. $this->dependencies[$id] = $callback();
  436. }
  437. return $this->dependencies[$id];
  438. }
  439. public function setDefinition(string $id, callable $service): void
  440. {
  441. $this->assertNotFrozen();
  442. $this->factories[$id] = $service;
  443. }
  444. }