Created
November 10, 2017 13:27
-
-
Save shengjie/73591575fc0bc45b3c77efaa4cb9892a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace AppBundle\Command; | |
use JMS\Serializer\Metadata\PropertyMetadata; | |
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; | |
use Symfony\Component\Console\Input\InputArgument; | |
use Symfony\Component\Console\Input\InputInterface; | |
use Symfony\Component\Console\Input\InputOption; | |
use Symfony\Component\Console\Output\OutputInterface; | |
class PhpModelToTsInterfaceCommand extends ContainerAwareCommand | |
{ | |
/** | |
* Configures the current command. | |
*/ | |
protected function configure() | |
{ | |
$this->setName('php-model-to-ts-interface') | |
->addArgument('models', InputArgument::REQUIRED | InputArgument::IS_ARRAY, 'indicate model class, multiple allowed separater by space') | |
->addOption('groups', 'g', InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'filter by serializer group'); | |
} | |
/** | |
* Executes the current command. | |
* | |
* This method is not abstract because you can use this class | |
* as a concrete class. In this case, instead of defining the | |
* execute() method, you set the code to execute by passing | |
* a Closure to the setCode() method. | |
* | |
* @param InputInterface $input An InputInterface instance | |
* @param OutputInterface $output An OutputInterface instance | |
* | |
* @return null|int null or 0 if everything went fine, or an error code | |
* | |
* @throws \LogicException When this abstract method is not implemented | |
* | |
* @see setCode() | |
*/ | |
protected function execute(InputInterface $input, OutputInterface $output) | |
{ | |
$this->groups = $input->getOption('groups'); | |
foreach ($input->getArgument('models') as $model) { | |
$model = \str_replace('/', '\\', $model); | |
$this->convertClass($model); | |
} | |
foreach ($this->convertedClass as $class) { | |
$output->writeln('/**'); | |
$output->writeln(' * class auto-generated'); | |
$output->writeln(' * @see ' . $class['full_name']); | |
$output->writeln(' */'); | |
$output->writeln('export interface ' . $class['name'] . ' {'); | |
foreach ($class['fields'] as $field) { | |
$output->writeln(' ' . $field['field'] . ': ' . $field['type']); | |
} | |
$output->writeln('}'); | |
$output->writeln(''); | |
} | |
} | |
/** | |
* @var array | |
*/ | |
private $convertedClass = []; | |
/** | |
* @var string[] | |
*/ | |
private $groups; | |
protected function convertClass($model) | |
{ | |
if (isset($this->convertedClass[$model])) { | |
return $this->convertedClass[$model]['name']; | |
} | |
$driver = $this->getContainer()->get('jms_serializer.metadata_driver'); | |
$ref = new \ReflectionClass($model); | |
$metadata = $driver->loadMetadataForClass($ref); | |
$this->convertedClass[$model] = [ | |
'name' => $ref->getShortName(), | |
'full_name' => $ref->getName(), | |
'fields' => [], | |
]; | |
/** | |
* @var string $name | |
* @var PropertyMetadata $propertyMetadatum | |
*/ | |
foreach ($metadata->propertyMetadata as $name => $propertyMetadatum) { | |
if (!empty($this->groups) && empty(\array_intersect($propertyMetadatum->groups, $this->groups))) { | |
// not in same group | |
continue; | |
} | |
$tsType = $this->convertPhpTypeToTsType($propertyMetadatum->type); | |
$field = $name; | |
if ($propertyMetadatum->readOnly) { | |
$field .= '?'; | |
} | |
$this->convertedClass[$model]['fields'][] = [ | |
'field' => $field, | |
'type' => $tsType, | |
]; | |
} | |
return $ref->getShortName(); | |
} | |
/** | |
* @param $type | |
* | |
* @return string | |
*/ | |
protected function convertPhpTypeToTsType(array $type = null): string | |
{ | |
if (null === $type) { | |
return 'any'; | |
} | |
$params = $type['params']; | |
$countParams = \count($params); | |
$name = $type['name']; | |
switch ($name) { | |
case 'integer': | |
case 'int': | |
case 'float': | |
return 'number'; | |
case 'string': | |
return 'string'; | |
case 'boolean': | |
case 'bool': | |
return 'boolean'; | |
case 'array': | |
if ($countParams == 0) { | |
return 'any[]'; | |
} | |
if ($countParams == 1) { | |
return $this->convertPhpTypeToTsType($params[0]) . '[]'; | |
} | |
if ($countParams == 2) { | |
return \sprintf('{ [key: %s]: %s }', $this->convertPhpTypeToTsType($params[0]), $this->convertPhpTypeToTsType($params[1])); | |
} | |
throw new \LogicException('more params to support!'); | |
case 'DateTime': | |
return 'string'; | |
default: | |
// supports \Symfony\Component\HttpFoundation\File\File ==> File object in js | |
if (\is_a($name, \SplFileInfo::class, true)) { | |
return 'File'; | |
} | |
if (\class_exists($name, true)) { | |
return $this->convertClass($name); | |
} | |
return 'any //' . \json_encode($type); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment