Skip to content

Commit

Permalink
add maker command and update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
fd6130 committed Feb 17, 2022
1 parent 2a1cb89 commit c989ab1
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 40 deletions.
79 changes: 40 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,74 @@
League Fractal Symfony Bundle
Fractal Bundle
=============================

This bundle provides integration of [league/fractal](https://github.com/thephpleague/fractal) for Symfony. In addition it allows you to use [transformers as a services](#using-transformers-as-services).

**This is a fork version of [samjarrett/FractalBundle](https://github.com/samjarrett/FractalBundle).**

## Getting Started

First of all you need to add dependency to composer.json:
Requirements:

* PHP >= 7.4
* Symfony 4, 5 and 6

Install through composer:

```
composer require samj/fractal-bundle
composer require fd6130/fractal-bundle
```

Then register bundle in `app/AppKernel.php`:
If you are using symfony flex, it will automatic register the bundle for you.

## Usage

You can use command `php bin/console make:fractal-transformer` to create a transformer.

Or, just create it by your own and place it in `src/Transformer`.

```php
public function registerBundles()
{
return [
// ...
new SamJ\FractalBundle\SamJFractalBundle(),
];
class UserTransformer extends TransformerAbstract
{
public function transform(User $user): array
{
$data = [
'id' => $user->id(),
'name' => $user->name(),
];

return $data;
}
}
```

Now we can write and use fractal transformers:
$resource = new Collection($users, UserTransformer::class);

$response = $manager->createData($resource)->toArray();
```

## Using Transformers as Services
### Inject services to the transformers

There are several cases when you need to pass some dependencies into transformer. The common one is [role/scope based results](https://github.com/thephpleague/fractal/issues/327) in transformers. For example you need to show `email` field only for administrators or owner of user profile:
You can inject services to your transformer through constructor:

```php
class UserTransformer extends TransformerAbstract
{
private $authorizationCheker;
private $entityManager;

public function __construct(AuthorizationChecker $authorizationCheker)
public function __construct(EntityManagerInterface $entityManager)
{
$this->authorizationCheker = $authorizationCheker;
$this->entityManager = $entityManager;
}

public function transform(User $user)
public function transform(User $user): array
{
$data = [
'id' => $user->id(),
'name' => $user->name(),
];

if ($this->authorizationChecker->isGranted(UserVoter::SEE_EMAIL, $user)) {
$data['email'] = $user->email();
}

// $this->entityManager->getRepository(...)

return $data;
}
}
```

Then you could just register this class as service, and pass service ID as transformer. This bundle then will try to get it from container:

```php
$resource = new Collection($users, 'app.transformer.user');
```

This works in includes as well:

```php
public function includeFriends(User $user)
{
return $this->collection($user->friends(), 'app.transformer.user');
}
```

You could see example of how to use transformers in [sample application](tests/Fixtures) which is used in test suites.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
"php": ">=7.4",
"league/fractal": "~0.17",
"symfony/framework-bundle": "^4.0|^5.0|^6.0"
},
"require-dev": {
"symfony/maker-bundle": "^1.0"
}
}
21 changes: 21 additions & 0 deletions src/DependencyInjection/Compiler/TransformerPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Fd\FractalBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;

class TransformerPass implements CompilerPassInterface
{
const TRANSFORMER_TAG = 'fd_fractal.transformer';
public function process(ContainerBuilder $container)
{
$taggedServices = $container->findTaggedServiceIds(self::TRANSFORMER_TAG);

foreach ($taggedServices as $id => $tags) {
$definition = $container->findDefinition($id);
$definition->setPublic(true);
}
}
}
6 changes: 6 additions & 0 deletions src/DependencyInjection/FdFractalExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

namespace Fd\FractalBundle\DependencyInjection;

use Fd\FractalBundle\DependencyInjection\Compiler\TransformerPass;
use League\Fractal\TransformerAbstract;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
Expand All @@ -16,5 +18,9 @@ public function load(array $configs, ContainerBuilder $container)
{
$loader = new Loader\PhpFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.php');

$container->registerForAutoconfiguration(TransformerAbstract::class)
->addTag(TransformerPass::TRANSFORMER_TAG)
;
}
}
8 changes: 8 additions & 0 deletions src/FdFractalBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@

namespace Fd\FractalBundle;

use Fd\FractalBundle\DependencyInjection\Compiler\TransformerPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

class FdFractalBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);

$container->addCompilerPass(new TransformerPass);
}
}
124 changes: 124 additions & 0 deletions src/Maker/MakeFractalTransformer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace Fd\FractalBundle\Maker;

use League\Fractal\TransformerAbstract;
use Symfony\Bundle\MakerBundle\ConsoleStyle;
use Symfony\Bundle\MakerBundle\DependencyBuilder;
use Symfony\Bundle\MakerBundle\Doctrine\DoctrineHelper;
use Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException;
use Symfony\Bundle\MakerBundle\Generator;
use Symfony\Bundle\MakerBundle\InputConfiguration;
use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
use Symfony\Bundle\MakerBundle\Str;
use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Question\Question;

final class MakeFractalTransformer extends AbstractMaker
{
use MakerTrait;

private $doctrineHelper;

public function __construct(DoctrineHelper $doctrineHelper)
{
$this->doctrineHelper = $doctrineHelper;
}

public static function getCommandName(): string
{
return 'make:fractal-transformer';
}

public static function getCommandDescription(): string
{
return 'Create a transformer class for transforming data.';
}

public function configureCommand(Command $command, InputConfiguration $inputConf)
{
$command
->setDescription('Creates a new transformer class')
->addArgument('entity-class', InputArgument::OPTIONAL, sprintf('The class name of the entity to create Transformer (e.g. <fg=yellow>%s</>)', Str::asClassName(Str::getRandomTerm())))
->addArgument('class-name', InputArgument::OPTIONAL, sprintf('The class name of new Transformer (e.g. <fg=yellow>%s</>)', Str::asClassName(Str::getRandomTerm())))
->addOption('no-entity', null, InputOption::VALUE_NONE, 'Use this option to generate a plank transformer')
->setHelp(file_get_contents(__DIR__ . '/../Resources/help/MakeFractalTransformer.txt'));

$inputConf->setArgumentAsNonInteractive('entity-class');
$inputConf->setArgumentAsNonInteractive('class-name');
}

public function interact(InputInterface $input, ConsoleStyle $io, Command $command)
{
if ($input->getOption('no-entity')) {
$io->block([
'Note: You have choose to generate a blank Transformer.',
], null, 'fg=yellow');
$classname = $io->ask(sprintf('The class name of new Transformer (e.g. <fg=yellow>%s</>)', Str::asClassName(Str::getRandomTerm())), null, [Validator::class, 'notBlank']);

$input->setArgument('class-name', $classname);
}
else
{
$argument = $command->getDefinition()->getArgument('entity-class');
$question = $this->createEntityClassQuestion($argument->getDescription());
$value = $io->askQuestion($question);

$input->setArgument('entity-class', $value);
}
}

public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
{
$noEntity = $input->getOption('no-entity');
$classname = !$noEntity ? null : Str::asClassName($input->getArgument('class-name'));

$entityClassDetails = !$noEntity ? $generator->createClassNameDetails(
Validator::entityExists($input->getArgument('entity-class'), $this->doctrineHelper->getEntitiesForAutocomplete()),
'Entity\\'
) : null;

$transformerClassDetails = !$noEntity ? $generator->createClassNameDetails(
$entityClassDetails->getShortName(),
'Transformer\\',
'Transformer'
) : $generator->createClassNameDetails($classname, 'Transformer\\', 'Transformer');

$generator->generateClass(
$transformerClassDetails->getFullName(),
__DIR__ . '/../Resources/skeleton/Transformer.tpl.php',
[
'no_entity' => $noEntity,
'entity_class_name' => $entityClassDetails ? $entityClassDetails->getShortName() : null,
'entity_variable_name' => $entityClassDetails ? Str::asLowerCamelCase($entityClassDetails->getShortName()) : null,
'entity_full_class_name' => $entityClassDetails ? $entityClassDetails->getFullName(): null,
]
);

$generator->writeChanges();

$this->writeSuccessMessage($io);

$io->text([
'Next: Open your new transformer class and start customizing it.',
]);
}

public function configureDependencies(DependencyBuilder $dependencies)
{
$dependencies->addClassDependency(TransformerAbstract::class, 'fd6130/fractal-bundle');
}

private function createEntityClassQuestion(string $questionText): Question
{
$question = new Question($questionText);
$question->setValidator([Validator::class, 'notBlank']);
$question->setAutocompleterValues($this->doctrineHelper->getEntitiesForAutocomplete());

return $question;
}
}
18 changes: 18 additions & 0 deletions src/Maker/MakerTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Fd\FractalBundle\Maker;

use Symfony\Bundle\MakerBundle\Validator;
use Symfony\Component\Console\Question\Question;

trait MakerTrait
{
private function createEntityClassQuestion(string $questionText): Question
{
$question = new Question($questionText);
$question->setValidator([Validator::class, 'notBlank']);
$question->setAutocompleterValues($this->doctrineHelper->getEntitiesForAutocomplete());

return $question;
}
}
10 changes: 9 additions & 1 deletion src/Resources/config/services.php
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
<?php

use Fd\FractalBundle\ContainerAwareManager;
use Fd\FractalBundle\Maker\MakeFractalTransformer;
use League\Fractal\Manager;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

use function Symfony\Component\DependencyInjection\Loader\Configurator\service;

return static function (ContainerConfigurator $container) {
$container->services()
$services = $container->services();

$services
->set(ContainerAwareManager::class, ContainerAwareManager::class)
->public()
->call('setContainer', [service('service_container')])
->alias('fd_fractal.manager', ContainerAwareManager::class)
->public()
->alias(Manager::class, ContainerAwareManager::class)
->public()

;
$services->set("maker.maker.make_fractal_transformer", MakeFractalTransformer::class)
->args([service("maker.doctrine_helper")])
->tag("maker.command")
;
};
7 changes: 7 additions & 0 deletions src/Resources/help/MakeFractalTransformer.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
The <info>%command.name%</info> command generates a new transformer class.

<info>php %command.full_name%</info>

<info>php %command.full_name% --no-entity</info>

Arguments are optional.
30 changes: 30 additions & 0 deletions src/Resources/skeleton/Transformer.tpl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?= "<?php\n" ?>

namespace <?= $namespace; ?>;

use League\Fractal\TransformerAbstract;
<?php if(!$no_entity): ?>use <?= $entity_full_class_name. ";\n"?><?php endif ?>
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\Item;

class <?= $class_name ?> extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = [];

/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = [];

public function transform(<?php if(!$no_entity):?><?= $entity_class_name ?> $entity<?php else:?>$entity<?php endif ?>): array
{
return [];
}
}

0 comments on commit c989ab1

Please sign in to comment.