Skip to content

Instantly share code, notes, and snippets.

@shengjie
Created November 10, 2017 13:27
Show Gist options
  • Save shengjie/73591575fc0bc45b3c77efaa4cb9892a to your computer and use it in GitHub Desktop.
Save shengjie/73591575fc0bc45b3c77efaa4cb9892a to your computer and use it in GitHub Desktop.
<?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