14use Bitrix\Main\ORM\Annotations\AnnotationTrait;
25use Symfony\Component\Console\Command\Command;
26use Symfony\Component\Console\Input\InputArgument;
27use Symfony\Component\Console\Input\InputDefinition;
28use Symfony\Component\Console\Input\InputInterface;
29use Symfony\Component\Console\Input\InputOption;
30use Symfony\Component\Console\Output\OutputInterface;
31use Symfony\Component\Console\Style\SymfonyStyle;
43 protected $modulesScanned = [];
45 protected $filesIncluded = 0;
48 protected $entitiesFound = [];
50 protected $excludedFiles = [
51 'main/lib/text/string.php',
52 'main/lib/composite/compatibility/aliases.php',
53 'sale/lib/delivery/extra_services/string.php',
56 protected function configure()
62 ->setName(
'orm:annotate')
65 ->setDescription(
'Scans project for ORM Entities.')
69 ->setHelp(
'This system command optimizes Entity Relation Map building.')
72 new InputDefinition(
array(
74 'output', InputArgument::OPTIONAL,
'File for annotations to be saved to',
76 ?
'modules/orm_annotations.php'
80 'modules',
'm', InputOption::VALUE_OPTIONAL,
81 'Modules to be scanned, separated by comma.',
'main'
84 'clean',
'c', InputOption::VALUE_NONE,
85 'Clean current entity map.'
95 protected function execute(InputInterface $input, OutputInterface
$output): int
103 $time = microtime(
true);
104 $memoryBefore = memory_get_usage();
114 $this->getDeclaredClassesDiff();
118 $inputModulesRaw = $input->getOption(
'modules');
120 if (!empty($inputModulesRaw) && $inputModulesRaw !=
'all')
122 $inputModules = explode(
',', $inputModulesRaw);
125 $dirs = $this->getDirsToScan($inputModules, $input,
$output);
127 foreach ($dirs as
$dir)
133 $this->scanBitrixEntities($inputModules, $input,
$output);
138 $this->handleVirtualClasses($inputModules, $input,
$output);
140 catch (\Exception $e)
146 $filePath = $input->getArgument(
'output');
147 $filePath = ($filePath[0] ==
'/')
149 : getcwd().
'/'.$filePath;
155 if (!$input->getOption(
'clean') && file_exists($filePath) && is_readable($filePath))
157 $rawAnnotations = explode(
'/* '.static::ANNOTATION_MARKER, file_get_contents($filePath));
159 foreach ($rawAnnotations as $rawAnnotation)
161 if ($rawAnnotation[0] ===
':')
163 $endPos = mb_strpos($rawAnnotation,
' */');
164 $entityClass = mb_substr($rawAnnotation, 1, $endPos - 1);
167 $annotations[$entityClass] =
'/* '.static::ANNOTATION_MARKER.rtrim($rawAnnotation);
173 foreach ($this->entitiesFound as $entityMeta)
177 $entityClass = $entityMeta[
'class'];
178 $annotateUfOnly = $entityMeta[
'ufOnly'];
180 $entity = Entity::getInstance($entityClass);
181 $entityAnnotation = static::annotateEntity(
$entity, $annotateUfOnly);
183 if (!empty($entityAnnotation))
185 $annotations[$entityClass] =
"/* ".static::ANNOTATION_MARKER.
":{$entityClass} */".PHP_EOL;
186 $annotations[$entityClass] .= $entityAnnotation;
189 catch (\Exception $e)
196 $fileContent =
'<?php'.PHP_EOL.PHP_EOL.join(PHP_EOL, $annotations);
199 $output->writeln(
'Map has been saved to: '.$filePath);
203 $memoryAfter = memory_get_usage();
204 $memoryDiff = $memoryAfter - $memoryBefore;
206 $output->writeln(
'Scanned modules: '.join(
', ', $this->modulesScanned));
207 $output->writeln(
'Scanned files: '.$this->filesIncluded);
208 $output->writeln(
'Found entities: '.
count($this->entitiesFound));
210 $output->writeln(
'Memory usage: '.(round($memoryAfter/1024/1024, 1)).
'M (+'.(round($memoryDiff/1024/1024, 1)).
'M)');
211 $output->writeln(
'Memory peak usage: '.(round(memory_get_peak_usage()/1024/1024, 1)).
'M');
213 if (!empty($exceptions))
217 foreach ($exceptions as $e)
219 $io->warning(
'Exception: '.$e->getMessage().PHP_EOL.$e->getTraceAsString());
226 protected function getDirsToScan($inputModules, InputInterface $input, OutputInterface
$output)
230 Application::getDocumentRoot().
'/local/modules/'
244 foreach (
new \DirectoryIterator(
$basePath) as $item)
246 if($item->isDir() && !$item->isDot())
248 $moduleList[] = $item->getFilename();
253 if (!empty($inputModules))
255 $moduleList = array_intersect($moduleList, $inputModules);
258 foreach ($moduleList as $moduleName)
261 if (!Loader::includeModule($moduleName))
267 if (is_dir($libDir) && is_readable($libDir))
272 $libDir =
$basePath.$moduleName.
'/dev/lib';
273 if (is_dir($libDir) && is_readable($libDir))
278 $this->modulesScanned[] = $moduleName;
285 protected function scanBitrixEntities($inputModules, InputInterface $input, OutputInterface
$output)
287 $basePath = Application::getDocumentRoot().Application::getPersonalRoot().
'/modules/';
292 foreach (
new \DirectoryIterator(
$basePath) as $item)
294 if($item->isDir() && !$item->isDot())
296 $moduleList[] = $item->getFilename();
301 if (!empty($inputModules))
303 $moduleList = array_intersect($moduleList, $inputModules);
307 foreach ($moduleList as $moduleName)
309 $ufPath =
$basePath.$moduleName.
'/meta/'.static::ANNOTATION_UF_FILENAME;
311 if (file_exists($ufPath))
313 $classes = include $ufPath;
315 foreach ($classes as $class)
317 if (class_exists($class))
319 $this->entitiesFound[] = [
329 $this->getDeclaredClassesDiff();
332 protected function registerFallbackAutoload()
334 spl_autoload_register(
function($className) {
335 list($vendor, $module) = explode(
'\\', $className);
339 Loader::includeModule($module);
342 Loader::autoLoad($className);
346 protected function scanDir(
$dir, InputInterface $input, OutputInterface
$output)
350 $this->registerFallbackAutoload();
353 $iterator =
new \RecursiveIteratorIterator(
354 new \RecursiveDirectoryIterator(
$dir, \RecursiveDirectoryIterator::SKIP_DOTS | \RecursiveDirectoryIterator::FOLLOW_SYMLINKS),
355 \RecursiveIteratorIterator::SELF_FIRST) as $item
359 foreach ($this->excludedFiles as $excludedFile)
361 $currentPath = str_replace(
'\\',
'/', $item->getPathname());
370 if ($item->isFile() && $item->isReadable() && str_ends_with($item->getFilename(),
'.php'))
372 $this->
debug(
$output,
'handle file: '.$item->getPathname());
377 include_once $item->getPathname();
378 $this->filesIncluded++;
380 $classes = $this->getDeclaredClassesDiff();
383 $this->handleClasses($classes, $input,
$output);
385 catch (\Throwable $e)
389 catch (\Exception $e)
397 protected function handleClasses($classes, InputInterface $input, OutputInterface
$output)
399 foreach ($classes as $class)
403 if (is_subclass_of($class, DataManager::class) && str_ends_with($class,
'Table'))
405 if ((
new \ReflectionClass($class))->isAbstract())
410 $debugMsg .=
' found!';
411 $this->entitiesFound[] = [
421 protected function getDeclaredClassesDiff()
423 static $lastDeclaredClasses = [];
425 $currentDeclaredClasses = get_declared_classes();
426 $diff = array_diff($currentDeclaredClasses, $lastDeclaredClasses);
427 $lastDeclaredClasses = $currentDeclaredClasses;
439 protected function handleVirtualClasses($inputModules, InputInterface $input, OutputInterface
$output)
442 $event = new \Bitrix\Main\Event(
"main",
"onVirtualClassBuildList", [], $inputModules);
446 $classes = $this->getDeclaredClassesDiff();
448 $this->handleClasses($classes, $input,
$output);
458 public static function scalarFieldToTypeHint($field)
460 if (is_string($field))
462 $fieldClass = $field;
466 $fieldClass = get_class($field);
471 case DateField::class:
472 return '\\'.Date::class;
473 case DatetimeField::class:
474 return '\\'.DateTime::class;
475 case IntegerField::class:
477 case BooleanField::class:
479 case FloatField::class:
481 case ArrayField::class:
static setRequireThrowException($requireThrowException)
</td ></tr ></table ></td ></tr >< tr >< td class="bx-popup-label bx-width30"><?=GetMessage("PAGE_NEW_TAGS")?> array( $site)
</p ></td >< td valign=top style='border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0cm 2.0pt 0cm 2.0pt;height:9.0pt'>< p class=Normal align=center style='margin:0cm;margin-bottom:.0001pt;text-align:center;line-height:normal'>< a name=ТекстовоеПоле54 ></a ><?=($taxRate > count( $arTaxList) > 0) ? $taxRate."%"