1: | <?php declare(strict_types = 1); |
2: | |
3: | namespace ApiGen; |
4: | |
5: | use Composer\InstalledVersions; |
6: | use ErrorException; |
7: | use Nette\DI\Compiler; |
8: | use Nette\DI\Config\Loader; |
9: | use Nette\DI\Container; |
10: | use Nette\DI\ContainerLoader; |
11: | use Nette\DI\Extensions\ExtensionsExtension; |
12: | use Nette\DI\Helpers as DIHelpers; |
13: | use Nette\Schema\Expect; |
14: | use Nette\Schema\Helpers as SchemaHelpers; |
15: | use Nette\Schema\Processor; |
16: | use Nette\Utils\FileSystem; |
17: | use Symfony\Component\Console\Style\OutputStyle; |
18: | |
19: | use function array_keys; |
20: | use function array_map; |
21: | use function assert; |
22: | use function count; |
23: | use function dirname; |
24: | use function error_reporting; |
25: | use function getcwd; |
26: | use function ini_set; |
27: | use function is_array; |
28: | use function is_file; |
29: | use function is_int; |
30: | use function method_exists; |
31: | use function set_error_handler; |
32: | use function str_starts_with; |
33: | use function sys_get_temp_dir; |
34: | |
35: | use const E_ALL; |
36: | use const E_DEPRECATED; |
37: | use const E_USER_DEPRECATED; |
38: | use const PHP_RELEASE_VERSION; |
39: | use const PHP_VERSION_ID; |
40: | |
41: | |
42: | class Bootstrap |
43: | { |
44: | public static function configureErrorHandling(): void |
45: | { |
46: | error_reporting(E_ALL); |
47: | ini_set('display_errors', 'stderr'); |
48: | ini_set('log_errors', '0'); |
49: | |
50: | set_error_handler(function (int $severity, string $message, string $file, int $line): bool { |
51: | if (error_reporting() & $severity && $severity !== E_DEPRECATED && $severity !== E_USER_DEPRECATED) { |
52: | throw new ErrorException($message, 0, $severity, $file, $line); |
53: | |
54: | } else { |
55: | return false; |
56: | } |
57: | }); |
58: | } |
59: | |
60: | |
61: | |
62: | |
63: | |
64: | |
65: | public static function createApiGen(OutputStyle $output, array $parameters, array $configPaths): ApiGen |
66: | { |
67: | $workingDir = getcwd(); |
68: | $tempDir = sys_get_temp_dir() . '/apigen'; |
69: | $version = InstalledVersions::getPrettyVersion('apigen/apigen'); |
70: | |
71: | if ($workingDir === false) { |
72: | throw new \RuntimeException('Unable to get current working directory.'); |
73: | } |
74: | |
75: | $autoDiscoveryPath = "$workingDir/apigen.neon"; |
76: | if (count($configPaths) === 0 && is_file($autoDiscoveryPath)) { |
77: | $output->text("Using configuration file $autoDiscoveryPath.\n"); |
78: | $configPaths[] = $autoDiscoveryPath; |
79: | } |
80: | |
81: | $config = self::mergeConfigs( |
82: | ['parameters' => ['workingDir' => $workingDir, 'tempDir' => $tempDir, 'version' => $version]], |
83: | self::loadConfig(__DIR__ . '/../apigen.neon'), |
84: | ...array_map(self::loadConfig(...), $configPaths), |
85: | ...[['parameters' => self::resolvePaths($parameters, $workingDir)]], |
86: | ); |
87: | |
88: | $parameters = $config['parameters']; |
89: | unset($config['parameters']); |
90: | |
91: | self::validateParameters($parameters); |
92: | $parameters = DIHelpers::expand($parameters, $parameters); |
93: | $containerLoader = new ContainerLoader($parameters['tempDir'], autoRebuild: true); |
94: | |
95: | $containerGenerator = function (Compiler $compiler) use ($config, $parameters): void { |
96: | $compiler->addExtension('extensions', new ExtensionsExtension); |
97: | $compiler->addConfig($config); |
98: | $compiler->setDynamicParameterNames(array_keys($parameters)); |
99: | }; |
100: | |
101: | $containerKey = [ |
102: | $config, |
103: | PHP_VERSION_ID - PHP_RELEASE_VERSION, |
104: | ]; |
105: | |
106: | |
107: | $containerClassName = $containerLoader->load($containerGenerator, $containerKey); |
108: | |
109: | $container = new $containerClassName($parameters); |
110: | $container->addService('symfonyConsole.output', $output); |
111: | $container->initialize(); |
112: | ini_set('memory_limit', $container->getParameter('memoryLimit')); |
113: | |
114: | return $container->getByType(ApiGen::class); |
115: | } |
116: | |
117: | |
118: | |
119: | |
120: | |
121: | protected static function validateParameters(array $parameters): void |
122: | { |
123: | $schema = Expect::structure([ |
124: | |
125: | 'paths' => Expect::listOf('string')->min(1), |
126: | 'include' => Expect::listOf('string'), |
127: | 'exclude' => Expect::listOf('string'), |
128: | |
129: | |
130: | 'excludeProtected' => Expect::bool(), |
131: | 'excludePrivate' => Expect::bool(), |
132: | 'excludeTagged' => Expect::listOf('string'), |
133: | |
134: | |
135: | 'outputDir' => Expect::string(), |
136: | 'themeDir' => Expect::string()->nullable(), |
137: | 'title' => Expect::string(), |
138: | 'version' => Expect::string(), |
139: | 'baseUrl' => Expect::string(), |
140: | |
141: | |
142: | 'workingDir' => Expect::string(), |
143: | 'tempDir' => Expect::string(), |
144: | 'workerCount' => Expect::int()->min(1), |
145: | 'memoryLimit' => Expect::string(), |
146: | ]); |
147: | |
148: | (new Processor)->process($schema, $parameters); |
149: | } |
150: | |
151: | |
152: | |
153: | |
154: | |
155: | |
156: | protected static function mergeConfigs(array...$configs): array |
157: | { |
158: | $mergedConfig = []; |
159: | |
160: | foreach ($configs as $config) { |
161: | foreach ($config['parameters'] ?? [] as $key => $value) { |
162: | if (is_array($value)) { |
163: | $config['parameters'][$key][SchemaHelpers::PreventMerging] = true; |
164: | } |
165: | } |
166: | |
167: | $mergedConfig = SchemaHelpers::merge($config, $mergedConfig); |
168: | assert(is_array($mergedConfig)); |
169: | } |
170: | |
171: | return $mergedConfig; |
172: | } |
173: | |
174: | |
175: | |
176: | |
177: | |
178: | protected static function loadConfig(string $path): array |
179: | { |
180: | $data = (new Loader)->load($path); |
181: | $data['parameters'] = self::resolvePaths($data['parameters'] ?? [], Helpers::realPath(dirname($path))); |
182: | |
183: | return $data; |
184: | } |
185: | |
186: | |
187: | |
188: | |
189: | |
190: | |
191: | protected static function resolvePaths(array $parameters, string $base): array |
192: | { |
193: | foreach (['tempDir', 'workingDir', 'outputDir', 'themeDir'] as $parameterKey) { |
194: | if (isset($parameters[$parameterKey])) { |
195: | $parameters[$parameterKey] = self::resolvePath($parameters[$parameterKey], $base); |
196: | } |
197: | } |
198: | |
199: | foreach ($parameters['paths'] ?? [] as $i => $path) { |
200: | if (is_int($i)) { |
201: | $parameters['paths'][$i] = self::resolvePath($parameters['paths'][$i], $base); |
202: | } |
203: | } |
204: | |
205: | return $parameters; |
206: | } |
207: | |
208: | |
209: | protected static function resolvePath(string $path, string $base): string |
210: | { |
211: | return (FileSystem::isAbsolute($path) || str_starts_with($path, '%')) |
212: | ? $path |
213: | : FileSystem::joinPaths($base, $path); |
214: | } |
215: | } |
216: | |