ConfigurationResolver.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. <?php
  2. /*
  3. * This file is part of PHP CS Fixer.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. * Dariusz Rumiński <dariusz.ruminski@gmail.com>
  7. *
  8. * This source file is subject to the MIT license that is bundled
  9. * with this source code in the file LICENSE.
  10. */
  11. namespace PhpCsFixer\Console;
  12. use PhpCsFixer\Cache\CacheManagerInterface;
  13. use PhpCsFixer\Cache\Directory;
  14. use PhpCsFixer\Cache\DirectoryInterface;
  15. use PhpCsFixer\Cache\FileCacheManager;
  16. use PhpCsFixer\Cache\FileHandler;
  17. use PhpCsFixer\Cache\NullCacheManager;
  18. use PhpCsFixer\Cache\Signature;
  19. use PhpCsFixer\ConfigInterface;
  20. use PhpCsFixer\ConfigurationException\InvalidConfigurationException;
  21. use PhpCsFixer\Differ\DifferInterface;
  22. use PhpCsFixer\Differ\NullDiffer;
  23. use PhpCsFixer\Differ\UnifiedDiffer;
  24. use PhpCsFixer\Finder;
  25. use PhpCsFixer\Fixer\DeprecatedFixerInterface;
  26. use PhpCsFixer\Fixer\FixerInterface;
  27. use PhpCsFixer\FixerFactory;
  28. use PhpCsFixer\Linter\Linter;
  29. use PhpCsFixer\Linter\LinterInterface;
  30. use PhpCsFixer\Report\ReporterFactory;
  31. use PhpCsFixer\Report\ReporterInterface;
  32. use PhpCsFixer\RuleSet\RuleSet;
  33. use PhpCsFixer\StdinFileInfo;
  34. use PhpCsFixer\ToolInfoInterface;
  35. use PhpCsFixer\Utils;
  36. use PhpCsFixer\WhitespacesFixerConfig;
  37. use PhpCsFixer\WordMatcher;
  38. use Symfony\Component\Console\Output\OutputInterface;
  39. use Symfony\Component\Filesystem\Filesystem;
  40. use Symfony\Component\Finder\Finder as SymfonyFinder;
  41. /**
  42. * The resolver that resolves configuration to use by command line options and config.
  43. *
  44. * @author Fabien Potencier <fabien@symfony.com>
  45. * @author Katsuhiro Ogawa <ko.fivestar@gmail.com>
  46. * @author Dariusz Rumiński <dariusz.ruminski@gmail.com>
  47. *
  48. * @internal
  49. */
  50. final class ConfigurationResolver
  51. {
  52. const PATH_MODE_OVERRIDE = 'override';
  53. const PATH_MODE_INTERSECTION = 'intersection';
  54. /**
  55. * @var null|bool
  56. */
  57. private $allowRisky;
  58. /**
  59. * @var null|ConfigInterface
  60. */
  61. private $config;
  62. /**
  63. * @var null|string
  64. */
  65. private $configFile;
  66. /**
  67. * @var string
  68. */
  69. private $cwd;
  70. /**
  71. * @var ConfigInterface
  72. */
  73. private $defaultConfig;
  74. /**
  75. * @var null|ReporterInterface
  76. */
  77. private $reporter;
  78. /**
  79. * @var null|bool
  80. */
  81. private $isStdIn;
  82. /**
  83. * @var null|bool
  84. */
  85. private $isDryRun;
  86. /**
  87. * @var null|FixerInterface[]
  88. */
  89. private $fixers;
  90. /**
  91. * @var null|bool
  92. */
  93. private $configFinderIsOverridden;
  94. /**
  95. * @var ToolInfoInterface
  96. */
  97. private $toolInfo;
  98. /**
  99. * @var array
  100. */
  101. private $options = [
  102. 'allow-risky' => null,
  103. 'cache-file' => null,
  104. 'config' => null,
  105. 'diff' => null,
  106. 'dry-run' => null,
  107. 'format' => null,
  108. 'path' => [],
  109. 'path-mode' => self::PATH_MODE_OVERRIDE,
  110. 'rules' => null,
  111. 'show-progress' => null,
  112. 'stop-on-violation' => null,
  113. 'using-cache' => null,
  114. 'verbosity' => null,
  115. ];
  116. private $cacheFile;
  117. private $cacheManager;
  118. private $differ;
  119. private $directory;
  120. private $finder;
  121. private $format;
  122. private $linter;
  123. private $path;
  124. private $progress;
  125. private $ruleSet;
  126. private $usingCache;
  127. /**
  128. * @var FixerFactory
  129. */
  130. private $fixerFactory;
  131. /**
  132. * @param string $cwd
  133. */
  134. public function __construct(
  135. ConfigInterface $config,
  136. array $options,
  137. $cwd,
  138. ToolInfoInterface $toolInfo
  139. ) {
  140. $this->cwd = $cwd;
  141. $this->defaultConfig = $config;
  142. $this->toolInfo = $toolInfo;
  143. foreach ($options as $name => $value) {
  144. $this->setOption($name, $value);
  145. }
  146. }
  147. /**
  148. * @return null|string
  149. */
  150. public function getCacheFile()
  151. {
  152. if (!$this->getUsingCache()) {
  153. return null;
  154. }
  155. if (null === $this->cacheFile) {
  156. if (null === $this->options['cache-file']) {
  157. $this->cacheFile = $this->getConfig()->getCacheFile();
  158. } else {
  159. $this->cacheFile = $this->options['cache-file'];
  160. }
  161. }
  162. return $this->cacheFile;
  163. }
  164. /**
  165. * @return CacheManagerInterface
  166. */
  167. public function getCacheManager()
  168. {
  169. if (null === $this->cacheManager) {
  170. $cacheFile = $this->getCacheFile();
  171. if (null === $cacheFile) {
  172. $this->cacheManager = new NullCacheManager();
  173. } else {
  174. $this->cacheManager = new FileCacheManager(
  175. new FileHandler($cacheFile),
  176. new Signature(
  177. PHP_VERSION,
  178. $this->toolInfo->getVersion(),
  179. $this->getConfig()->getIndent(),
  180. $this->getConfig()->getLineEnding(),
  181. $this->getRules()
  182. ),
  183. $this->isDryRun(),
  184. $this->getDirectory()
  185. );
  186. }
  187. }
  188. return $this->cacheManager;
  189. }
  190. /**
  191. * @return ConfigInterface
  192. */
  193. public function getConfig()
  194. {
  195. if (null === $this->config) {
  196. foreach ($this->computeConfigFiles() as $configFile) {
  197. if (!file_exists($configFile)) {
  198. continue;
  199. }
  200. $config = self::separatedContextLessInclude($configFile);
  201. // verify that the config has an instance of Config
  202. if (!$config instanceof ConfigInterface) {
  203. throw new InvalidConfigurationException(sprintf('The config file: "%s" does not return a "PhpCsFixer\ConfigInterface" instance. Got: "%s".', $configFile, \is_object($config) ? \get_class($config) : \gettype($config)));
  204. }
  205. $this->config = $config;
  206. $this->configFile = $configFile;
  207. break;
  208. }
  209. if (null === $this->config) {
  210. $this->config = $this->defaultConfig;
  211. }
  212. }
  213. return $this->config;
  214. }
  215. /**
  216. * @return null|string
  217. */
  218. public function getConfigFile()
  219. {
  220. if (null === $this->configFile) {
  221. $this->getConfig();
  222. }
  223. return $this->configFile;
  224. }
  225. /**
  226. * @return DifferInterface
  227. */
  228. public function getDiffer()
  229. {
  230. if (null === $this->differ) {
  231. if ($this->options['diff']) {
  232. $this->differ = new UnifiedDiffer();
  233. } else {
  234. $this->differ = new NullDiffer();
  235. }
  236. }
  237. return $this->differ;
  238. }
  239. /**
  240. * @return DirectoryInterface
  241. */
  242. public function getDirectory()
  243. {
  244. if (null === $this->directory) {
  245. $path = $this->getCacheFile();
  246. if (null === $path) {
  247. $absolutePath = $this->cwd;
  248. } else {
  249. $filesystem = new Filesystem();
  250. $absolutePath = $filesystem->isAbsolutePath($path)
  251. ? $path
  252. : $this->cwd.\DIRECTORY_SEPARATOR.$path;
  253. }
  254. $this->directory = new Directory(\dirname($absolutePath));
  255. }
  256. return $this->directory;
  257. }
  258. /**
  259. * @return FixerInterface[] An array of FixerInterface
  260. */
  261. public function getFixers()
  262. {
  263. if (null === $this->fixers) {
  264. $this->fixers = $this->createFixerFactory()
  265. ->useRuleSet($this->getRuleSet())
  266. ->setWhitespacesConfig(new WhitespacesFixerConfig($this->config->getIndent(), $this->config->getLineEnding()))
  267. ->getFixers()
  268. ;
  269. if (false === $this->getRiskyAllowed()) {
  270. $riskyFixers = array_map(
  271. static function (FixerInterface $fixer) {
  272. return $fixer->getName();
  273. },
  274. array_filter(
  275. $this->fixers,
  276. static function (FixerInterface $fixer) {
  277. return $fixer->isRisky();
  278. }
  279. )
  280. );
  281. if (\count($riskyFixers)) {
  282. throw new InvalidConfigurationException(sprintf('The rules contain risky fixers (%s), but they are not allowed to run. Perhaps you forget to use --allow-risky=yes option?', implode('", "', $riskyFixers)));
  283. }
  284. }
  285. }
  286. return $this->fixers;
  287. }
  288. /**
  289. * @return LinterInterface
  290. */
  291. public function getLinter()
  292. {
  293. if (null === $this->linter) {
  294. $this->linter = new Linter($this->getConfig()->getPhpExecutable());
  295. }
  296. return $this->linter;
  297. }
  298. /**
  299. * Returns path.
  300. *
  301. * @return string[]
  302. */
  303. public function getPath()
  304. {
  305. if (null === $this->path) {
  306. $filesystem = new Filesystem();
  307. $cwd = $this->cwd;
  308. if (1 === \count($this->options['path']) && '-' === $this->options['path'][0]) {
  309. $this->path = $this->options['path'];
  310. } else {
  311. $this->path = array_map(
  312. static function ($rawPath) use ($cwd, $filesystem) {
  313. $path = trim($rawPath);
  314. if ('' === $path) {
  315. throw new InvalidConfigurationException("Invalid path: \"{$rawPath}\".");
  316. }
  317. $absolutePath = $filesystem->isAbsolutePath($path)
  318. ? $path
  319. : $cwd.\DIRECTORY_SEPARATOR.$path;
  320. if (!file_exists($absolutePath)) {
  321. throw new InvalidConfigurationException(sprintf(
  322. 'The path "%s" is not readable.',
  323. $path
  324. ));
  325. }
  326. return $absolutePath;
  327. },
  328. $this->options['path']
  329. );
  330. }
  331. }
  332. return $this->path;
  333. }
  334. /**
  335. * @throws InvalidConfigurationException
  336. *
  337. * @return string
  338. */
  339. public function getProgress()
  340. {
  341. if (null === $this->progress) {
  342. if (OutputInterface::VERBOSITY_VERBOSE <= $this->options['verbosity'] && 'txt' === $this->getFormat()) {
  343. $progressType = $this->options['show-progress'];
  344. $progressTypes = ['none', 'dots'];
  345. if (null === $progressType) {
  346. $progressType = $this->getConfig()->getHideProgress() ? 'none' : 'dots';
  347. } elseif (!\in_array($progressType, $progressTypes, true)) {
  348. throw new InvalidConfigurationException(sprintf(
  349. 'The progress type "%s" is not defined, supported are "%s".',
  350. $progressType,
  351. implode('", "', $progressTypes)
  352. ));
  353. }
  354. $this->progress = $progressType;
  355. } else {
  356. $this->progress = 'none';
  357. }
  358. }
  359. return $this->progress;
  360. }
  361. /**
  362. * @return ReporterInterface
  363. */
  364. public function getReporter()
  365. {
  366. if (null === $this->reporter) {
  367. $reporterFactory = ReporterFactory::create();
  368. $reporterFactory->registerBuiltInReporters();
  369. $format = $this->getFormat();
  370. try {
  371. $this->reporter = $reporterFactory->getReporter($format);
  372. } catch (\UnexpectedValueException $e) {
  373. $formats = $reporterFactory->getFormats();
  374. sort($formats);
  375. throw new InvalidConfigurationException(sprintf('The format "%s" is not defined, supported are "%s".', $format, implode('", "', $formats)));
  376. }
  377. }
  378. return $this->reporter;
  379. }
  380. /**
  381. * @return bool
  382. */
  383. public function getRiskyAllowed()
  384. {
  385. if (null === $this->allowRisky) {
  386. if (null === $this->options['allow-risky']) {
  387. $this->allowRisky = $this->getConfig()->getRiskyAllowed();
  388. } else {
  389. $this->allowRisky = $this->resolveOptionBooleanValue('allow-risky');
  390. }
  391. }
  392. return $this->allowRisky;
  393. }
  394. /**
  395. * Returns rules.
  396. *
  397. * @return array
  398. */
  399. public function getRules()
  400. {
  401. return $this->getRuleSet()->getRules();
  402. }
  403. /**
  404. * @return bool
  405. */
  406. public function getUsingCache()
  407. {
  408. if (null === $this->usingCache) {
  409. if (null === $this->options['using-cache']) {
  410. $this->usingCache = $this->getConfig()->getUsingCache();
  411. } else {
  412. $this->usingCache = $this->resolveOptionBooleanValue('using-cache');
  413. }
  414. }
  415. $this->usingCache = $this->usingCache && ($this->toolInfo->isInstalledAsPhar() || $this->toolInfo->isInstalledByComposer());
  416. return $this->usingCache;
  417. }
  418. public function getFinder()
  419. {
  420. if (null === $this->finder) {
  421. $this->finder = $this->resolveFinder();
  422. }
  423. return $this->finder;
  424. }
  425. /**
  426. * Returns dry-run flag.
  427. *
  428. * @return bool
  429. */
  430. public function isDryRun()
  431. {
  432. if (null === $this->isDryRun) {
  433. if ($this->isStdIn()) {
  434. // Can't write to STDIN
  435. $this->isDryRun = true;
  436. } else {
  437. $this->isDryRun = $this->options['dry-run'];
  438. }
  439. }
  440. return $this->isDryRun;
  441. }
  442. public function shouldStopOnViolation()
  443. {
  444. return $this->options['stop-on-violation'];
  445. }
  446. /**
  447. * @return bool
  448. */
  449. public function configFinderIsOverridden()
  450. {
  451. if (null === $this->configFinderIsOverridden) {
  452. $this->resolveFinder();
  453. }
  454. return $this->configFinderIsOverridden;
  455. }
  456. /**
  457. * Compute file candidates for config file.
  458. *
  459. * @return string[]
  460. */
  461. private function computeConfigFiles()
  462. {
  463. $configFile = $this->options['config'];
  464. if (null !== $configFile) {
  465. if (false === file_exists($configFile) || false === is_readable($configFile)) {
  466. throw new InvalidConfigurationException(sprintf('Cannot read config file "%s".', $configFile));
  467. }
  468. return [$configFile];
  469. }
  470. $path = $this->getPath();
  471. if ($this->isStdIn() || 0 === \count($path)) {
  472. $configDir = $this->cwd;
  473. } elseif (1 < \count($path)) {
  474. throw new InvalidConfigurationException('For multiple paths config parameter is required.');
  475. } elseif (!is_file($path[0])) {
  476. $configDir = $path[0];
  477. } else {
  478. $dirName = pathinfo($path[0], PATHINFO_DIRNAME);
  479. $configDir = $dirName ?: $path[0];
  480. }
  481. $candidates = [
  482. $configDir.\DIRECTORY_SEPARATOR.'.php_cs',
  483. $configDir.\DIRECTORY_SEPARATOR.'.php_cs.dist',
  484. ];
  485. if ($configDir !== $this->cwd) {
  486. $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs';
  487. $candidates[] = $this->cwd.\DIRECTORY_SEPARATOR.'.php_cs.dist';
  488. }
  489. return $candidates;
  490. }
  491. /**
  492. * @return FixerFactory
  493. */
  494. private function createFixerFactory()
  495. {
  496. if (null === $this->fixerFactory) {
  497. $fixerFactory = new FixerFactory();
  498. $fixerFactory->registerBuiltInFixers();
  499. $fixerFactory->registerCustomFixers($this->getConfig()->getCustomFixers());
  500. $this->fixerFactory = $fixerFactory;
  501. }
  502. return $this->fixerFactory;
  503. }
  504. /**
  505. * @return string
  506. */
  507. private function getFormat()
  508. {
  509. if (null === $this->format) {
  510. $this->format = null === $this->options['format']
  511. ? $this->getConfig()->getFormat()
  512. : $this->options['format'];
  513. }
  514. return $this->format;
  515. }
  516. private function getRuleSet()
  517. {
  518. if (null === $this->ruleSet) {
  519. $rules = $this->parseRules();
  520. $this->validateRules($rules);
  521. $this->ruleSet = new RuleSet($rules);
  522. }
  523. return $this->ruleSet;
  524. }
  525. /**
  526. * @return bool
  527. */
  528. private function isStdIn()
  529. {
  530. if (null === $this->isStdIn) {
  531. $this->isStdIn = 1 === \count($this->options['path']) && '-' === $this->options['path'][0];
  532. }
  533. return $this->isStdIn;
  534. }
  535. /**
  536. * @param iterable $iterable
  537. *
  538. * @return \Traversable
  539. */
  540. private function iterableToTraversable($iterable)
  541. {
  542. return \is_array($iterable) ? new \ArrayIterator($iterable) : $iterable;
  543. }
  544. /**
  545. * Compute rules.
  546. *
  547. * @return array
  548. */
  549. private function parseRules()
  550. {
  551. if (null === $this->options['rules']) {
  552. return $this->getConfig()->getRules();
  553. }
  554. $rules = trim($this->options['rules']);
  555. if ('' === $rules) {
  556. throw new InvalidConfigurationException('Empty rules value is not allowed.');
  557. }
  558. if ('{' === $rules[0]) {
  559. $rules = json_decode($rules, true);
  560. if (JSON_ERROR_NONE !== json_last_error()) {
  561. throw new InvalidConfigurationException(sprintf('Invalid JSON rules input: "%s".', json_last_error_msg()));
  562. }
  563. return $rules;
  564. }
  565. $rules = [];
  566. foreach (explode(',', $this->options['rules']) as $rule) {
  567. $rule = trim($rule);
  568. if ('' === $rule) {
  569. throw new InvalidConfigurationException('Empty rule name is not allowed.');
  570. }
  571. if ('-' === $rule[0]) {
  572. $rules[substr($rule, 1)] = false;
  573. } else {
  574. $rules[$rule] = true;
  575. }
  576. }
  577. return $rules;
  578. }
  579. /**
  580. * @throws InvalidConfigurationException
  581. */
  582. private function validateRules(array $rules)
  583. {
  584. /**
  585. * Create a ruleset that contains all configured rules, even when they originally have been disabled.
  586. *
  587. * @see RuleSet::resolveSet()
  588. */
  589. $ruleSet = [];
  590. foreach ($rules as $key => $value) {
  591. if (\is_int($key)) {
  592. throw new InvalidConfigurationException(sprintf('Missing value for "%s" rule/set.', $value));
  593. }
  594. $ruleSet[$key] = true;
  595. }
  596. $ruleSet = new RuleSet($ruleSet);
  597. /** @var string[] $configuredFixers */
  598. $configuredFixers = array_keys($ruleSet->getRules());
  599. $fixers = $this->createFixerFactory()->getFixers();
  600. /** @var string[] $availableFixers */
  601. $availableFixers = array_map(static function (FixerInterface $fixer) {
  602. return $fixer->getName();
  603. }, $fixers);
  604. $unknownFixers = array_diff(
  605. $configuredFixers,
  606. $availableFixers
  607. );
  608. if (\count($unknownFixers)) {
  609. $matcher = new WordMatcher($availableFixers);
  610. $message = 'The rules contain unknown fixers: ';
  611. foreach ($unknownFixers as $unknownFixer) {
  612. $alternative = $matcher->match($unknownFixer);
  613. $message .= sprintf(
  614. '"%s"%s, ',
  615. $unknownFixer,
  616. null === $alternative ? '' : ' (did you mean "'.$alternative.'"?)'
  617. );
  618. }
  619. throw new InvalidConfigurationException(substr($message, 0, -2).'.');
  620. }
  621. foreach ($fixers as $fixer) {
  622. $fixerName = $fixer->getName();
  623. if (isset($rules[$fixerName]) && $fixer instanceof DeprecatedFixerInterface) {
  624. $successors = $fixer->getSuccessorsNames();
  625. $messageEnd = [] === $successors
  626. ? sprintf(' and will be removed in version %d.0.', Application::getMajorVersion() + 1)
  627. : sprintf('. Use %s instead.', str_replace('`', '"', Utils::naturalLanguageJoinWithBackticks($successors)));
  628. $message = "Rule \"{$fixerName}\" is deprecated{$messageEnd}";
  629. if (getenv('PHP_CS_FIXER_FUTURE_MODE')) {
  630. throw new \RuntimeException("{$message} This check was performed as `PHP_CS_FIXER_FUTURE_MODE` env var is set.");
  631. }
  632. @trigger_error($message, E_USER_DEPRECATED);
  633. }
  634. }
  635. }
  636. /**
  637. * Apply path on config instance.
  638. */
  639. private function resolveFinder()
  640. {
  641. $this->configFinderIsOverridden = false;
  642. if ($this->isStdIn()) {
  643. return new \ArrayIterator([new StdinFileInfo()]);
  644. }
  645. $modes = [self::PATH_MODE_OVERRIDE, self::PATH_MODE_INTERSECTION];
  646. if (!\in_array(
  647. $this->options['path-mode'],
  648. $modes,
  649. true
  650. )) {
  651. throw new InvalidConfigurationException(sprintf(
  652. 'The path-mode "%s" is not defined, supported are "%s".',
  653. $this->options['path-mode'],
  654. implode('", "', $modes)
  655. ));
  656. }
  657. $isIntersectionPathMode = self::PATH_MODE_INTERSECTION === $this->options['path-mode'];
  658. $paths = array_filter(array_map(
  659. static function ($path) {
  660. return realpath($path);
  661. },
  662. $this->getPath()
  663. ));
  664. if (!\count($paths)) {
  665. if ($isIntersectionPathMode) {
  666. return new \ArrayIterator([]);
  667. }
  668. return $this->iterableToTraversable($this->getConfig()->getFinder());
  669. }
  670. $pathsByType = [
  671. 'file' => [],
  672. 'dir' => [],
  673. ];
  674. foreach ($paths as $path) {
  675. if (is_file($path)) {
  676. $pathsByType['file'][] = $path;
  677. } else {
  678. $pathsByType['dir'][] = $path.\DIRECTORY_SEPARATOR;
  679. }
  680. }
  681. $nestedFinder = null;
  682. $currentFinder = $this->iterableToTraversable($this->getConfig()->getFinder());
  683. try {
  684. $nestedFinder = $currentFinder instanceof \IteratorAggregate ? $currentFinder->getIterator() : $currentFinder;
  685. } catch (\Exception $e) {
  686. }
  687. if ($isIntersectionPathMode) {
  688. if (null === $nestedFinder) {
  689. throw new InvalidConfigurationException(
  690. 'Cannot create intersection with not-fully defined Finder in configuration file.'
  691. );
  692. }
  693. return new \CallbackFilterIterator(
  694. new \IteratorIterator($nestedFinder),
  695. static function (\SplFileInfo $current) use ($pathsByType) {
  696. $currentRealPath = $current->getRealPath();
  697. if (\in_array($currentRealPath, $pathsByType['file'], true)) {
  698. return true;
  699. }
  700. foreach ($pathsByType['dir'] as $path) {
  701. if (0 === strpos($currentRealPath, $path)) {
  702. return true;
  703. }
  704. }
  705. return false;
  706. }
  707. );
  708. }
  709. if (null !== $this->getConfigFile() && null !== $nestedFinder) {
  710. $this->configFinderIsOverridden = true;
  711. }
  712. if ($currentFinder instanceof SymfonyFinder && null === $nestedFinder) {
  713. // finder from configuration Symfony finder and it is not fully defined, we may fulfill it
  714. return $currentFinder->in($pathsByType['dir'])->append($pathsByType['file']);
  715. }
  716. return Finder::create()->in($pathsByType['dir'])->append($pathsByType['file']);
  717. }
  718. /**
  719. * Set option that will be resolved.
  720. *
  721. * @param string $name
  722. * @param mixed $value
  723. */
  724. private function setOption($name, $value)
  725. {
  726. if (!\array_key_exists($name, $this->options)) {
  727. throw new InvalidConfigurationException(sprintf('Unknown option name: "%s".', $name));
  728. }
  729. $this->options[$name] = $value;
  730. }
  731. /**
  732. * @param string $optionName
  733. *
  734. * @return bool
  735. */
  736. private function resolveOptionBooleanValue($optionName)
  737. {
  738. $value = $this->options[$optionName];
  739. if (!\is_string($value)) {
  740. throw new InvalidConfigurationException(sprintf('Expected boolean or string value for option "%s".', $optionName));
  741. }
  742. if ('yes' === $value) {
  743. return true;
  744. }
  745. if ('no' === $value) {
  746. return false;
  747. }
  748. throw new InvalidConfigurationException(sprintf('Expected "yes" or "no" for option "%s", got "%s".', $optionName, $value));
  749. }
  750. private static function separatedContextLessInclude($path)
  751. {
  752. return include $path;
  753. }
  754. }