initial commit

This commit is contained in:
boris
2025-09-30 09:24:25 +01:00
committed by boris
parent a783a12c97
commit c7770ea03b
4695 changed files with 525784 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
class <?= $class_name."\n" ?>
{
}

View File

@@ -0,0 +1,39 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
class <?= $class_name ?> extends AbstractAuthenticator
{
public function supports(Request $request): ?bool
{
// TODO: Implement supports() method.
}
public function authenticate(Request $request): Passport
{
// TODO: Implement authenticate() method.
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// TODO: Implement onAuthenticationSuccess() method.
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
// TODO: Implement onAuthenticationFailure() method.
}
// public function start(Request $request, ?AuthenticationException $authException = null): Response
// {
// /*
// * If you would like this class to control what happens when an anonymous user accesses a
// * protected page (e.g. redirect to /login), uncomment this method and make this class
// * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface.
// *
// * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point
// */
// }
}

View File

@@ -0,0 +1,9 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
class <?= $class_name; ?> extends AbstractController
{
}

View File

@@ -0,0 +1,48 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
class <?= $class_name; ?> extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'app_login';
public function __construct(private UrlGeneratorInterface $urlGenerator)
{
}
public function authenticate(Request $request): Passport
{
$<?= $username_field_var ?> = $request->getPayload()->getString('<?= $username_field ?>');
$request->getSession()->set(SecurityRequestAttributes::LAST_USERNAME, $<?= $username_field_var ?>);
return new Passport(
new UserBadge($<?= $username_field_var ?>),
new PasswordCredentials($request->getPayload()->getString('password')),
[
new CsrfTokenBadge('authenticate', $request->getPayload()->getString('_csrf_token')),<?= $remember_me_badge ? "
new RememberMeBadge(),\n" : "" ?>
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
// For example:
// return new RedirectResponse($this->urlGenerator->generate('some_route'));
throw new \Exception('TODO: provide a valid redirect inside '.__FILE__);
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}

View File

@@ -0,0 +1,38 @@
{% extends 'base.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<?php if ($logout_setup): ?>
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
</div>
{% endif %}
<?php endif; ?>
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="input<?= ucfirst($username_field); ?>"><?= $username_label; ?></label>
<input type="<?= $username_is_email ? 'email' : 'text'; ?>" value="{{ last_username }}" name="<?= $username_field; ?>" id="input<?= ucfirst($username_field); ?>" class="form-control" autocomplete="<?= $username_is_email ? 'email' : 'username'; ?>" required autofocus>
<label for="inputPassword">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" required>
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
<?php if($support_remember_me && !$always_remember_me): ?>
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="_remember_me"> Remember me
</label>
</div>
<?php endif; ?>
<button class="btn btn-lg btn-primary" type="submit">
Sign in
</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,44 @@
<?= "<?php\n"; ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
#[AsCommand(
name: '<?= $command_name; ?>',
description: 'Add a short description for your command',
)]
class <?= $class_name; ?> extends Command
{
public function __construct()
{
parent::__construct();
}
protected function configure(): void
{
$this
<?= $set_description ? " ->setDescription(self::\$defaultDescription)\n" : '' ?>
->addArgument('arg1', InputArgument::OPTIONAL, 'Argument description')
->addOption('option1', null, InputOption::VALUE_NONE, 'Option description')
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$arg1 = $input->getArgument('arg1');
if ($arg1) {
$io->note(sprintf('You passed an argument: %s', $arg1));
}
if ($input->getOption('option1')) {
// ...
}
$io->success('You have a new command! Now make it your own! Pass --help to see your options.');
return Command::SUCCESS;
}
}

View File

@@ -0,0 +1,25 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace(); ?>;
<?= $class_data->getUseStatements(); ?>
<?= $class_data->getClassDeclaration(); ?>
{
<?= $generator->generateRouteForControllerMethod($route_path, $route_name); ?>
public function <?= $method_name ?>(): <?php if ($with_template) { ?>Response<?php } else { ?>JsonResponse<?php } ?>
{
<?php if ($with_template) { ?>
return $this->render('<?= $template_name ?>', [
'controller_name' => '<?= $class_data->getClassName() ?>',
]);
<?php } else { ?>
return $this->json([
'message' => 'Welcome to your new controller!',
'path' => '<?= $relative_path; ?>',
]);
<?php } ?>
}
}

View File

@@ -0,0 +1,17 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace(); ?>;
<?= $class_data->getUseStatements(); ?>
<?= $class_data->getClassDeclaration(); ?>
{
public function testIndex(): void
{
$client = static::createClient();
$client->request('GET', '<?= $route_path; ?>');
self::assertResponseIsSuccessful();
}
}

View File

@@ -0,0 +1,18 @@
<?= $helper->getHeadPrintCode("Hello $class_name!"); ?>
{% block body %}
<style>
.example-wrapper { margin: 1em auto; max-width: 800px; width: 95%; font: 18px/1.5 sans-serif; }
.example-wrapper code { background: #F5F5F5; padding: 2px 6px; }
</style>
<div class="example-wrapper">
<h1>Hello {{ controller_name }}! ✅</h1>
This friendly message is coming from:
<ul>
<li>Your controller at <code><?= $root_directory ?>/<?= $controller_path ?></code></li>
<li>Your template at <code><?= $root_directory ?>/<?= $relative_path ?></code></li>
</ul>
</div>
{% endblock %}

View File

@@ -0,0 +1,88 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace() ?>;
<?= $class_data->getUseStatements(); ?>
#[Route('<?= $route_path ?>')]
<?= $class_data->getClassDeclaration() ?>
{
<?= $generator->generateRouteForControllerMethod('', sprintf('%s_index', $route_name), ['GET']) ?>
<?php if (isset($repository_full_class_name)): ?>
public function index(<?= $repository_class_name ?> $<?= $repository_var ?>): Response
{
return $this->render('<?= $templates_path ?>/index.html.twig', [
'<?= $entity_twig_var_plural ?>' => $<?= $repository_var ?>->findAll(),
]);
}
<?php else: ?>
public function index(EntityManagerInterface $entityManager): Response
{
$<?= $entity_var_plural ?> = $entityManager
->getRepository(<?= $entity_class_name ?>::class)
->findAll();
return $this->render('<?= $templates_path ?>/index.html.twig', [
'<?= $entity_twig_var_plural ?>' => $<?= $entity_var_plural ?>,
]);
}
<?php endif ?>
<?= $generator->generateRouteForControllerMethod('/new', sprintf('%s_new', $route_name), ['GET', 'POST']) ?>
public function new(Request $request, EntityManagerInterface $entityManager): Response
{
$<?= $entity_var_singular ?> = new <?= $entity_class_name ?>();
$form = $this->createForm(<?= $form_class_name ?>::class, $<?= $entity_var_singular ?>);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($<?= $entity_var_singular ?>);
$entityManager->flush();
return $this->redirectToRoute('<?= $route_name ?>_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('<?= $templates_path ?>/new.html.twig', [
'<?= $entity_twig_var_singular ?>' => $<?= $entity_var_singular ?>,
'form' => $form,
]);
}
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_show', $route_name), ['GET']) ?>
public function show(<?= $entity_class_name ?> $<?= $entity_var_singular ?>): Response
{
return $this->render('<?= $templates_path ?>/show.html.twig', [
'<?= $entity_twig_var_singular ?>' => $<?= $entity_var_singular ?>,
]);
}
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}/edit', $entity_identifier), sprintf('%s_edit', $route_name), ['GET', 'POST']) ?>
public function edit(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(<?= $form_class_name ?>::class, $<?= $entity_var_singular ?>);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('<?= $route_name ?>_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('<?= $templates_path ?>/edit.html.twig', [
'<?= $entity_twig_var_singular ?>' => $<?= $entity_var_singular ?>,
'form' => $form,
]);
}
<?= $generator->generateRouteForControllerMethod(sprintf('/{%s}', $entity_identifier), sprintf('%s_delete', $route_name), ['POST']) ?>
public function delete(Request $request, <?= $entity_class_name ?> $<?= $entity_var_singular ?>, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$<?= $entity_var_singular ?>->get<?= ucfirst($entity_identifier) ?>(), $request->getPayload()->getString('_token'))) {
$entityManager->remove($<?= $entity_var_singular ?>);
$entityManager->flush();
}
return $this->redirectToRoute('<?= $route_name ?>_index', [], Response::HTTP_SEE_OTHER);
}
}

View File

@@ -0,0 +1,4 @@
<form method="post" action="{{ path('<?= $route_name ?>_delete', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>) }}">
<button class="btn">Delete</button>
</form>

View File

@@ -0,0 +1,4 @@
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}

View File

@@ -0,0 +1,11 @@
<?= $helper->getHeadPrintCode('Edit '.$entity_class_name) ?>
{% block body %}
<h1>Edit <?= $entity_class_name ?></h1>
{{ include('<?= $templates_path ?>/_form.html.twig', {'button_label': 'Update'}) }}
<a href="{{ path('<?= $route_name ?>_index') }}">back to list</a>
{{ include('<?= $templates_path ?>/_delete_form.html.twig') }}
{% endblock %}

View File

@@ -0,0 +1,35 @@
<?= $helper->getHeadPrintCode($entity_class_name.' index'); ?>
{% block body %}
<h1><?= $entity_class_name ?> index</h1>
<table class="table">
<thead>
<tr>
<?php foreach ($entity_fields as $field): ?>
<th><?= ucfirst($field['fieldName']) ?></th>
<?php endforeach; ?>
<th>actions</th>
</tr>
</thead>
<tbody>
{% for <?= $entity_twig_var_singular ?> in <?= $entity_twig_var_plural ?> %}
<tr>
<?php foreach ($entity_fields as $field): ?>
<td>{{ <?= $helper->getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}</td>
<?php endforeach; ?>
<td>
<a href="{{ path('<?= $route_name ?>_show', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}">show</a>
<a href="{{ path('<?= $route_name ?>_edit', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}">edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="<?= (count($entity_fields) + 1) ?>">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ path('<?= $route_name ?>_new') }}">Create new</a>
{% endblock %}

View File

@@ -0,0 +1,9 @@
<?= $helper->getHeadPrintCode('New '.$entity_class_name) ?>
{% block body %}
<h1>Create new <?= $entity_class_name ?></h1>
{{ include('<?= $templates_path ?>/_form.html.twig') }}
<a href="{{ path('<?= $route_name ?>_index') }}">back to list</a>
{% endblock %}

View File

@@ -0,0 +1,22 @@
<?= $helper->getHeadPrintCode($entity_class_name) ?>
{% block body %}
<h1><?= $entity_class_name ?></h1>
<table class="table">
<tbody>
<?php foreach ($entity_fields as $field): ?>
<tr>
<th><?= ucfirst($field['fieldName']) ?></th>
<td>{{ <?= $helper->getEntityFieldPrintCode($entity_twig_var_singular, $field) ?> }}</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<a href="{{ path('<?= $route_name ?>_index') }}">back to list</a>
<a href="{{ path('<?= $route_name ?>_edit', {'<?= $entity_identifier ?>': <?= $entity_twig_var_singular ?>.<?= $entity_identifier ?>}) }}">edit</a>
{{ include('<?= $templates_path ?>/_delete_form.html.twig') }}
{% endblock %}

View File

@@ -0,0 +1,123 @@
<?= "<?php\n" ?>
<?php use Symfony\Bundle\MakerBundle\Str; ?>
namespace <?= $namespace ?>;
<?= $class_data->getUseStatements(); ?>
<?= $class_data->getClassDeclaration() ?>
{
private KernelBrowser $client;
private EntityManagerInterface $manager;
private EntityRepository $<?= lcfirst($entity_var_singular); ?>Repository;
private string $path = '<?= $route_path; ?>/';
protected function setUp(): void
{
$this->client = static::createClient();
$this->manager = static::getContainer()->get('doctrine')->getManager();
$this-><?= lcfirst($entity_var_singular); ?>Repository = $this->manager->getRepository(<?= $entity_class_name; ?>::class);
foreach ($this-><?= lcfirst($entity_var_singular); ?>Repository->findAll() as $object) {
$this->manager->remove($object);
}
$this->manager->flush();
}
public function testIndex(): void
{
$this->client->followRedirects();
$crawler = $this->client->request('GET', $this->path);
self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('<?= ucfirst($entity_var_singular); ?> index');
// Use the $crawler to perform additional assertions e.g.
// self::assertSame('Some text on the page', $crawler->filter('.p')->first()->text());
}
public function testNew(): void
{
$this->markTestIncomplete();
$this->client->request('GET', sprintf('%snew', $this->path));
self::assertResponseStatusCodeSame(200);
$this->client->submitForm('Save', [
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
'<?= $form_field_prefix; ?>[<?= $form_field; ?>]' => 'Testing',
<?php endforeach; ?>
]);
self::assertResponseRedirects($this->path);
self::assertSame(1, $this-><?= lcfirst($entity_var_singular); ?>Repository->count([]));
}
public function testShow(): void
{
$this->markTestIncomplete();
$fixture = new <?= $entity_class_name; ?>();
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
$fixture->set<?= ucfirst($form_field); ?>('My Title');
<?php endforeach; ?>
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('<?= ucfirst($entity_var_singular); ?>');
// Use assertions to check that the properties are properly displayed.
}
public function testEdit(): void
{
$this->markTestIncomplete();
$fixture = new <?= $entity_class_name; ?>();
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
$fixture->set<?= ucfirst($form_field); ?>('Value');
<?php endforeach; ?>
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId()));
$this->client->submitForm('Update', [
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
'<?= $form_field_prefix; ?>[<?= $form_field; ?>]' => 'Something New',
<?php endforeach; ?>
]);
self::assertResponseRedirects('<?= $route_path; ?>/');
$fixture = $this-><?= lcfirst($entity_var_singular); ?>Repository->findAll();
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
self::assertSame('Something New', $fixture[0]->get<?= ucfirst($form_field); ?>());
<?php endforeach; ?>
}
public function testRemove(): void
{
$this->markTestIncomplete();
$fixture = new <?= $entity_class_name; ?>();
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
$fixture->set<?= ucfirst($form_field); ?>('Value');
<?php endforeach; ?>
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
$this->client->submitForm('Delete');
self::assertResponseRedirects('<?= $route_path; ?>/');
self::assertSame(0, $this-><?= lcfirst($entity_var_singular); ?>Repository->count([]));
}
}

View File

@@ -0,0 +1,56 @@
<?php
use Symfony\Bundle\MakerBundle\Maker\Common\EntityIdTypeEnum;
?>
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
#[ORM\Entity(repositoryClass: <?= $repository_class_name ?>::class)]
<?php if ($should_escape_table_name): ?>#[ORM\Table(name: '`<?= $table_name ?>`')]
<?php endif ?>
<?php if ($api_resource): ?>
#[ApiResource]
<?php endif ?>
<?php if ($broadcast): ?>
#[Broadcast]
<?php endif ?>
class <?= $class_name."\n" ?>
{
<?php if (EntityIdTypeEnum::UUID === $id_type): ?>
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;
public function getId(): ?Uuid
{
return $this->id;
}
<?php elseif (EntityIdTypeEnum::ULID === $id_type): ?>
#[ORM\Id]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.ulid_generator')]
private ?Ulid $id = null;
public function getId(): ?Ulid
{
return $this->id;
}
<?php else: ?>
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
<?php endif ?>
}

View File

@@ -0,0 +1,16 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> extends Fixture
{
public function load(ObjectManager $manager): void
{
// $product = new Product();
// $manager->persist($product);
$manager->flush();
}
}

View File

@@ -0,0 +1,61 @@
<?= "<?php\n"; ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
/**
* @extends ServiceEntityRepository<<?= $entity_class_name; ?>>
*/
class <?= $class_name; ?> extends ServiceEntityRepository<?= $with_password_upgrade ? " implements PasswordUpgraderInterface\n" : "\n" ?>
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, <?= $entity_class_name; ?>::class);
}
<?php if ($include_example_comments): // When adding a new method without existing default comments, the blank line is automatically added.?>
<?php endif; ?>
<?php if ($with_password_upgrade): ?>
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(<?= sprintf('%s ', $password_upgrade_user_interface->getShortName()); ?>$user, string $newHashedPassword): void
{
if (!$user instanceof <?= $entity_class_name ?>) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
}
$user->setPassword($newHashedPassword);
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
<?php endif ?>
<?php if ($include_example_comments): ?>
// /**
// * @return <?= $entity_class_name ?>[] Returns an array of <?= $entity_class_name ?> objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('<?= $entity_alias; ?>')
// ->andWhere('<?= $entity_alias; ?>.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('<?= $entity_alias; ?>.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?<?= $entity_class_name."\n" ?>
// {
// return $this->createQueryBuilder('<?= $entity_alias ?>')
// ->andWhere('<?= $entity_alias ?>.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
<?php endif; ?>
}

View File

@@ -0,0 +1,22 @@
{# Learn how to use Turbo Streams: https://github.com/symfony/ux-turbo#broadcast-doctrine-entities-update #}
{% block create %}
<turbo-stream action="append" target="<?= $class_name_plural ?>">
<template>
<div id="{{ '<?= $class_name ?>_' ~ id }}">
#{{ id }} created
</div>
</template>
</turbo-stream>
{% endblock %}
{% block update %}
<turbo-stream action="update" target="<?= $class_name ?>_{{ id }}">
<template>
#{{ id }} updated
</template>
</turbo-stream>
{% endblock %}
{% block remove %}
<turbo-stream action="remove" target="<?= $class_name ?>_{{ id }}"></turbo-stream>
{% endblock %}

View File

@@ -0,0 +1,14 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
final class <?= $class_name."\n" ?>
{
#[AsEventListener<?php if (!$class_event): ?>(event: <?= $event ?>)<?php endif ?>]
public function <?= $method_name ?>(<?= $event_arg ?>): void
{
// ...
}
}

View File

@@ -0,0 +1,20 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> implements EventSubscriberInterface
{
public function <?= $method_name ?>(<?= $event_arg ?>): void
{
// ...
}
public static function getSubscribedEvents(): array
{
return [
<?= $event ?> => '<?= $method_name ?>',
];
}
}

View File

@@ -0,0 +1,36 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
class <?= $class_name ?> extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
<?php foreach ($form_fields as $form_field => $typeOptions): ?>
<?php if (null === $typeOptions['type'] && !$typeOptions['options_code']): ?>
->add('<?= $form_field ?>')
<?php elseif (null !== $typeOptions['type'] && !$typeOptions['options_code']): ?>
->add('<?= $form_field ?>', <?= $typeOptions['type'] ?>::class)
<?php else: ?>
->add('<?= $form_field ?>', <?= $typeOptions['type'] ? ($typeOptions['type'].'::class') : 'null' ?>, [
<?= $typeOptions['options_code']."\n" ?>
])
<?php endif; ?>
<?php endforeach; ?>
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
<?php if ($bounded_class_name): ?>
'data_class' => <?= $bounded_class_name ?>::class,
<?php else: ?>
// Configure your form options here
<?php endif ?>
]);
}
}

View File

@@ -0,0 +1,19 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
<?php if ($transport): ?>#[AsMessage('<?= $transport ?>')]<?= "\n" ?><?php endif ?>
final class <?= $class_name."\n" ?>
{
/*
* Add whatever properties and methods you need
* to hold the data for this message class.
*/
// public function __construct(
// public readonly string $name,
// ) {
// }
}

View File

@@ -0,0 +1,14 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
#[AsMessageHandler]
final class <?= $class_name ?>
{
public function __invoke(<?= $message_class_name ?> $message): void
{
// do something with your message
}
}

View File

@@ -0,0 +1,14 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
final class <?= $class_name; ?> implements MiddlewareInterface
{
public function handle(Envelope $envelope, StackInterface $stack): Envelope
{
// ...
return $stack->next()->handle($envelope, $stack);
}
}

View File

@@ -0,0 +1,102 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name; ?> extends AbstractController
{
<?php if ($will_verify_email): ?>
public function __construct(private <?= $generator->getPropertyType($email_verifier_class_details) ?>$emailVerifier)
{
}
<?php endif; ?>
<?= $generator->generateRouteForControllerMethod($route_path, $route_name) ?>
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher<?= $login_after_registration ? ', Security $security': '' ?>, EntityManagerInterface $entityManager): Response
{
$user = new <?= $user_class_name ?>();
$form = $this->createForm(<?= $form_class_name ?>::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var string $plainPassword */
$plainPassword = $form->get('plainPassword')->getData();
// encode the plain password
$user->set<?= ucfirst($password_field) ?>($userPasswordHasher->hashPassword($user, $plainPassword));
$entityManager->persist($user);
$entityManager->flush();
<?php if ($will_verify_email): ?>
// generate a signed url and email it to the user
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
(new TemplatedEmail())
->from(new Address('<?= $from_email ?>', '<?= $from_email_name ?>'))
->to((string) $user-><?= $email_getter ?>())
->subject('Please Confirm your Email')
->htmlTemplate('registration/confirmation_email.html.twig')
);
<?php endif; ?>
// do anything else you need here, like send an email
<?php if ($login_after_registration): ?>
return $security->login($user, <?= $authenticator ?>, '<?= $firewall ?>');
<?php else: ?>
return $this->redirectToRoute('<?= $redirect_route_name ?>');
<?php endif; ?>
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form,
]);
}
<?php if ($will_verify_email): ?>
<?= $generator->generateRouteForControllerMethod('/verify/email', 'app_verify_email') ?>
public function verifyUserEmail(Request $request<?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?><?= $verify_email_anonymously ? sprintf(', %s %s', $repository_class_name, $repository_var) : null ?>): Response
{
<?php if (!$verify_email_anonymously): ?>
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
<?php else: ?>
$id = $request->query->get('id');
if (null === $id) {
return $this->redirectToRoute('app_register');
}
<?php if ('$manager' === $repository_var): ?>
$repository = $manager->getRepository(<?= $user_class_name ?>::class);
$user = $repository->find($id);
<?php else: ?>
$user = <?= $repository_var; ?>->find($id);
<?php endif; ?>
if (null === $user) {
return $this->redirectToRoute('app_register');
}
<?php endif; ?>
// validate email confirmation link, sets User::isVerified=true and persists
try {
<?php if (!$verify_email_anonymously): ?>
/** @var <?= $user_class_name ?> $user */
$user = $this->getUser();
<?php endif; ?>
$this->emailVerifier->handleEmailConfirmation($request, $user);
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash('verify_email_error', <?php if ($translator_available): ?>$translator->trans($exception->getReason(), [], 'VerifyEmailBundle')<?php else: ?>$exception->getReason()<?php endif ?>);
return $this->redirectToRoute('<?= $route_name ?>');
}
// @TODO Change the redirect on success and handle or remove the flash message in your templates
$this->addFlash('success', 'Your email address has been verified.');
return $this->redirectToRoute('app_register');
}
<?php endif; ?>
}

View File

@@ -0,0 +1,75 @@
<?= "<?php\n" ?>
namespace App\Tests;
<?= $use_statements ?>
class RegistrationControllerTest extends WebTestCase
{
private KernelBrowser $client;
private <?= $repository_class_name ?> $userRepository;
protected function setUp(): void
{
$this->client = static::createClient();
// Ensure we have a clean database
$container = static::getContainer();
/** @var EntityManager $em */
$em = $container->get('doctrine')->getManager();
$this->userRepository = $container->get(<?= $repository_class_name ?>::class);
foreach ($this->userRepository->findAll() as $user) {
$em->remove($user);
}
$em->flush();
}
public function testRegister(): void
{
// Register a new user
$this->client->request('GET', '/register');
self::assertResponseIsSuccessful();
self::assertPageTitleContains('Register');
$this->client->submitForm('Register', [
'registration_form[email]' => 'me@example.com',
'registration_form[plainPassword]' => 'password',
'registration_form[agreeTerms]' => true,
]);
// Ensure the response redirects after submitting the form, the user exists, and is not verified
// self::assertResponseRedirects('/'); @TODO: set the appropriate path that the user is redirected to.
self::assertCount(1, $this->userRepository->findAll());
self::assertFalse(($user = $this->userRepository->findAll()[0])->isVerified());
// Ensure the verification email was sent
// Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup
// self::assertQueuedEmailCount(1);
self::assertEmailCount(1);
self::assertCount(1, $messages = $this->getMailerMessages());
self::assertEmailAddressContains($messages[0], 'from', '<?= $from_email ?>');
self::assertEmailAddressContains($messages[0], 'to', 'me@example.com');
self::assertEmailTextBodyContains($messages[0], 'This link will expire in 1 hour.');
// Login the new user
$this->client->followRedirect();
$this->client->loginUser($user);
// Get the verification link from the email
/** @var TemplatedEmail $templatedEmail */
$templatedEmail = $messages[0];
$messageBody = $templatedEmail->getHtmlBody();
self::assertIsString($messageBody);
preg_match('#(http://localhost/verify/email.+)">#', $messageBody, $resetLink);
// "Click" the link and see if the user is verified
$this->client->request('GET', $resetLink[1]);
$this->client->followRedirect();
self::assertTrue(static::getContainer()->get(<?= $repository_class_name ?>::class)->findAll()[0]->isVerified());
}
}

View File

@@ -0,0 +1,46 @@
<?= "<?php\n" ?>
namespace App\Tests;
<?= $use_statements ?>
class RegistrationControllerTest extends WebTestCase
{
private KernelBrowser $client;
private <?= $repository_class_name ?> $userRepository;
protected function setUp(): void
{
$this->client = static::createClient();
// Ensure we have a clean database
$container = static::getContainer();
/** @var EntityManager $em */
$em = $container->get('doctrine')->getManager();
$this->userRepository = $container->get(<?= $repository_class_name ?>::class);
foreach ($this->userRepository->findAll() as $user) {
$em->remove($user);
}
$em->flush();
}
public function testRegister(): void
{
// Register a new user
$this->client->request('GET', '/register');
self::assertResponseIsSuccessful();
self::assertPageTitleContains('Register');
$this->client->submitForm('Register', [
'registration_form[email]' => 'me@example.com',
'registration_form[plainPassword]' => 'password',
'registration_form[agreeTerms]' => true,
]);
// Ensure the response redirects after submitting the form, the user exists, and is not verified
// self::assertResponseRedirects('/'); @TODO: set the appropriate path that the user is redirected to.
self::assertCount(1, $this->userRepository->findAll());
}
}

View File

@@ -0,0 +1,11 @@
<h1>Hi! Please confirm your email!</h1>
<p>
Please confirm your email address by clicking the following link: <br><br>
<a href="{{ signedUrl|raw }}">Confirm my Email</a>.
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
</p>
<p>
Cheers!
</p>

View File

@@ -0,0 +1,23 @@
<?= $helper->getHeadPrintCode('Register'); ?>
{% block body %}
<?php if ($will_verify_email): ?>
{% for flash_error in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<?php endif; ?>
<h1>Register</h1>
{{ form_errors(registrationForm) }}
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.<?= $username_field ?>) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="btn">Register</button>
{{ form_end(registrationForm) }}
{% endblock %}

View File

@@ -0,0 +1,50 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements ?>
class <?= $class_name ?> extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('plainPassword', RepeatedType::class, [
'type' => PasswordType::class,
'options' => [
'attr' => [
'autocomplete' => 'new-password',
],
],
'first_options' => [
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 12,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
new PasswordStrength(),
new NotCompromisedPassword(),
],
'label' => 'New password',
],
'second_options' => [
'label' => 'Repeat Password',
],
'invalid_message' => 'The password fields must match.',
// Instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([]);
}
}

View File

@@ -0,0 +1,158 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements; ?>
#[Route('/reset-password')]
class <?= $class_name ?> extends AbstractController
{
use ResetPasswordControllerTrait;
public function __construct(
private ResetPasswordHelperInterface $resetPasswordHelper,
private EntityManagerInterface $entityManager
) {
}
/**
* Display & process form to request a password reset.
*/
#[Route('', name: 'app_forgot_password_request')]
public function request(Request $request, MailerInterface $mailer<?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?>): Response
{
$form = $this->createForm(<?= $request_form_type_class_name ?>::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/** @var string $email */
$email = $form->get('<?= $email_field ?>')->getData();
return $this->processSendingPasswordResetEmail($email, $mailer<?php if ($translator_available): ?>, $translator<?php endif ?><?= "\n" ?>);
}
return $this->render('reset_password/request.html.twig', [
'requestForm' => $form,
]);
}
/**
* Confirmation page after a user has requested a password reset.
*/
#[Route('/check-email', name: 'app_check_email')]
public function checkEmail(): Response
{
// Generate a fake token if the user does not exist or someone hit this page directly.
// This prevents exposing whether or not a user was found with the given email address or not
if (null === ($resetToken = $this->getTokenObjectFromSession())) {
$resetToken = $this->resetPasswordHelper->generateFakeResetToken();
}
return $this->render('reset_password/check_email.html.twig', [
'resetToken' => $resetToken,
]);
}
/**
* Validates and process the reset URL that the user clicked in their email.
*/
#[Route('/reset/{token}', name: 'app_reset_password')]
public function reset(Request $request, UserPasswordHasherInterface $passwordHasher<?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?>, string $token = null): Response
{
if ($token) {
// We store the token in session and remove it from the URL, to avoid the URL being
// loaded in a browser and potentially leaking the token to 3rd party JavaScript.
$this->storeTokenInSession($token);
return $this->redirectToRoute('app_reset_password');
}
$token = $this->getTokenFromSession();
if (null === $token) {
throw $this->createNotFoundException('No reset password token found in the URL or in the session.');
}
try {
/** @var <?= $user_class_name ?> $user */
$user = $this->resetPasswordHelper->validateTokenAndFetchUser($token);
} catch (ResetPasswordExceptionInterface $e) {
$this->addFlash('reset_password_error', sprintf(
'%s - %s',
<?php if ($translator_available): ?>$translator->trans(<?= $problem_validate_message_or_constant ?>, [], 'ResetPasswordBundle')<?php else: ?><?= $problem_validate_message_or_constant ?><?php endif ?>,
<?php if ($translator_available): ?>$translator->trans($e->getReason(), [], 'ResetPasswordBundle')<?php else: ?>$e->getReason()<?php endif ?><?= "\n" ?>
));
return $this->redirectToRoute('app_forgot_password_request');
}
// The token is valid; allow the user to change their password.
$form = $this->createForm(<?= $reset_form_type_class_name ?>::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// A password reset token should be used only once, remove it.
$this->resetPasswordHelper->removeResetRequest($token);
/** @var string $plainPassword */
$plainPassword = $form->get('plainPassword')->getData();
// Encode(hash) the plain password, and set it.
$user-><?= $password_setter ?>($passwordHasher->hashPassword($user, $plainPassword));
$this->entityManager->flush();
// The session is cleaned up after the password has been changed.
$this->cleanSessionAfterReset();
return $this->redirectToRoute('<?= $success_redirect_route ?>');
}
return $this->render('reset_password/reset.html.twig', [
'resetForm' => $form,
]);
}
private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer<?php if ($translator_available): ?>, TranslatorInterface $translator<?php endif ?>): RedirectResponse
{
$user = $this->entityManager->getRepository(<?= $user_class_name ?>::class)->findOneBy([
'<?= $email_field ?>' => $emailFormData,
]);
// Do not reveal whether a user account was found or not.
if (!$user) {
return $this->redirectToRoute('app_check_email');
}
try {
$resetToken = $this->resetPasswordHelper->generateResetToken($user);
} catch (ResetPasswordExceptionInterface $e) {
// If you want to tell the user why a reset email was not sent, uncomment
// the lines below and change the redirect to 'app_forgot_password_request'.
// Caution: This may reveal if a user is registered or not.
//
// $this->addFlash('reset_password_error', sprintf(
// '%s - %s',
// <?php if ($translator_available): ?>$translator->trans(<?= $problem_handle_message_or_constant ?>, [], 'ResetPasswordBundle')<?php else: ?><?= $problem_handle_message_or_constant ?><?php endif ?>,
// <?php if ($translator_available): ?>$translator->trans($e->getReason(), [], 'ResetPasswordBundle')<?php else: ?>$e->getReason()<?php endif ?><?= "\n" ?>
// ));
return $this->redirectToRoute('app_check_email');
}
$email = (new TemplatedEmail())
->from(new Address('<?= $from_email ?>', '<?= $from_email_name ?>'))
->to((string) $user-><?= $email_getter ?>())
->subject('Your password reset request')
->htmlTemplate('reset_password/email.html.twig')
->context([
'resetToken' => $resetToken,
])
;
$mailer->send($email);
// Store the token object in session for retrieval in check-email route.
$this->setTokenObjectInSession($resetToken);
return $this->redirectToRoute('app_check_email');
}
}

View File

@@ -0,0 +1,27 @@
<?= "<?php\n" ?>
namespace <?= $namespace ?>;
<?= $use_statements ?>
class <?= $class_name ?> extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('<?= $email_field ?>', EmailType::class, [
'attr' => ['autocomplete' => 'email'],
'constraints' => [
new NotBlank([
'message' => 'Please enter your email',
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([]);
}
}

View File

@@ -0,0 +1,98 @@
<?= "<?php\n" ?>
namespace App\Tests;
<?= $use_statements ?>
class ResetPasswordControllerTest extends WebTestCase
{
private KernelBrowser $client;
private EntityManagerInterface $em;
private <?= $user_repo_short_name ?> $userRepository;
protected function setUp(): void
{
$this->client = static::createClient();
// Ensure we have a clean database
$container = static::getContainer();
/** @var EntityManagerInterface $em */
$em = $container->get('doctrine')->getManager();
$this->em = $em;
$this->userRepository = $container->get(<?= $user_repo_short_name ?>::class);
foreach ($this->userRepository->findAll() as $user) {
$this->em->remove($user);
}
$this->em->flush();
}
public function testResetPasswordController(): void
{
// Create a test user
$user = (new <?= $user_short_name ?>())
->setEmail('me@example.com')
->setPassword('a-test-password-that-will-be-changed-later')
;
$this->em->persist($user);
$this->em->flush();
// Test Request reset password page
$this->client->request('GET', '/reset-password');
self::assertResponseIsSuccessful();
self::assertPageTitleContains('Reset your password');
// Submit the reset password form and test email message is queued / sent
$this->client->submitForm('Send password reset email', [
'reset_password_request_form[email]' => 'me@example.com',
]);
// Ensure the reset password email was sent
// Use either assertQueuedEmailCount() || assertEmailCount() depending on your mailer setup
// self::assertQueuedEmailCount(1);
self::assertEmailCount(1);
self::assertCount(1, $messages = $this->getMailerMessages());
self::assertEmailAddressContains($messages[0], 'from', '<?= $from_email ?>');
self::assertEmailAddressContains($messages[0], 'to', 'me@example.com');
self::assertEmailTextBodyContains($messages[0], 'This link will expire in 1 hour.');
self::assertResponseRedirects('/reset-password/check-email');
// Test check email landing page shows correct "expires at" time
$crawler = $this->client->followRedirect();
self::assertPageTitleContains('Password Reset Email Sent');
self::assertStringContainsString('This link will expire in 1 hour', $crawler->html());
// Test the link sent in the email is valid
$email = $messages[0]->toString();
preg_match('#(/reset-password/reset/[a-zA-Z0-9]+)#', $email, $resetLink);
$this->client->request('GET', $resetLink[1]);
self::assertResponseRedirects('/reset-password/reset');
$this->client->followRedirect();
// Test we can set a new password
$this->client->submitForm('Reset password', [
'change_password_form[plainPassword][first]' => 'newStrongPassword',
'change_password_form[plainPassword][second]' => 'newStrongPassword',
]);
self::assertResponseRedirects('<?= $success_route_path ?>');
$user = $this->userRepository->findOneBy(['email' => 'me@example.com']);
self::assertInstanceOf(<?= $user_short_name ?>::class, $user);
/** @var UserPasswordHasherInterface $passwordHasher */
$passwordHasher = static::getContainer()->get(UserPasswordHasherInterface::class);
self::assertTrue($passwordHasher->isPasswordValid($user, 'newStrongPassword'));
}
}

View File

@@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}Password Reset Email Sent{% endblock %}
{% block body %}
<p>
If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password.
This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.
</p>
<p>If you don't receive an email please check your spam folder or <a href="{{ path('app_forgot_password_request') }}">try again</a>.</p>
{% endblock %}

View File

@@ -0,0 +1,9 @@
<h1>Hi!</h1>
<p>To reset your password, please visit the following link</p>
<a href="{{ url('app_reset_password', {token: resetToken.token}) }}">{{ url('app_reset_password', {token: resetToken.token}) }}</a>
<p>This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.</p>
<p>Cheers!</p>

View File

@@ -0,0 +1,22 @@
{% extends 'base.html.twig' %}
{% block title %}Reset your password{% endblock %}
{% block body %}
{% for flash_error in app.flashes('reset_password_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<h1>Reset your password</h1>
{{ form_start(requestForm) }}
{{ form_row(requestForm.<?= $email_field ?>) }}
<div>
<small>
Enter your email address, and we will send you a
link to reset your password.
</small>
</div>
<button class="btn btn-primary">Send password reset email</button>
{{ form_end(requestForm) }}
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends 'base.html.twig' %}
{% block title %}Reset your password{% endblock %}
{% block body %}
<h1>Reset your password</h1>
{{ form_start(resetForm) }}
{{ form_row(resetForm.plainPassword) }}
<button class="btn btn-primary">Reset password</button>
{{ form_end(resetForm) }}
{% endblock %}

View File

@@ -0,0 +1,30 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
#[AsSchedule<?php if ($has_transport_name): ?>('<?= $transport_name; ?>')<?php endif ?>]
final class <?= $class_name; ?> implements ScheduleProviderInterface
{
public function __construct(
private CacheInterface $cache,
) {
}
public function getSchedule(): Schedule
{
return (new Schedule())
->add(
<?php if ($has_custom_message): ?>
// @TODO - Modify the frequency to suite your needs
RecurringMessage::every('1 hour', new <?= $message_class_name; ?>()),
<?php else: ?>
// @TODO - Create a Message to schedule
// RecurringMessage::every('1 hour', new App\Message\Message()),
<?php endif ?>
)
->stateful($this->cache)
;
}
}

View File

@@ -0,0 +1,74 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> implements UserProviderInterface, PasswordUpgraderInterface
{
/**
* Symfony calls this method if you use features like switch_user
* or remember_me.
*
* If you're not using these features, you do not need to implement
* this method.
*
* @throws UserNotFoundException if the user is not found
*/
public function loadUserByIdentifier($identifier): UserInterface
{
// Load a User object from your data source or throw UserNotFoundException.
// The $identifier argument may not actually be a username:
// it is whatever value is being returned by the getUserIdentifier()
// method in your User class.
throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__);
}
/**
* @deprecated since Symfony 5.3, loadUserByIdentifier() is used instead
*/
public function loadUserByUsername($username): UserInterface
{
return $this->loadUserByIdentifier($username);
}
/**
* Refreshes the user after being reloaded from the session.
*
* When a user is logged in, at the beginning of each request, the
* User object is loaded from the session and then this method is
* called. Your job is to make sure the user's data is still fresh by,
* for example, re-querying for fresh User data.
*
* If your firewall is "stateless: true" (for a pure API), this
* method is not called.
*/
public function refreshUser(UserInterface $user): UserInterface
{
if (!$user instanceof <?= $user_short_name ?>) {
throw new UnsupportedUserException(sprintf('Invalid user class "%s".', $user::class));
}
// Return a User object after making sure its data is "fresh".
// Or throw a UsernameNotFoundException if the user no longer exists.
throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
}
/**
* Tells Symfony to use this provider for this User class.
*/
public function supportsClass(string $class): bool
{
return <?= $user_short_name ?>::class === $class || is_subclass_of($class, <?= $user_short_name ?>::class);
}
/**
* Upgrades the hashed password of a user, typically for using a better hash algorithm.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
// TODO: when hashed passwords are in use, this method should:
// 1. persist the new password in the user storage
// 2. update the $user object with $user->setPassword($newHashedPassword);
}
}

View File

@@ -0,0 +1,43 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace(); ?>;
<?= $class_data->getUseStatements(); ?>
<?= $class_data->getClassDeclaration() ?>
{
public const EDIT = 'POST_EDIT';
public const VIEW = 'POST_VIEW';
protected function supports(string $attribute, mixed $subject): bool
{
// replace with your own logic
// https://symfony.com/doc/current/security/voters.html
return in_array($attribute, [self::EDIT, self::VIEW])
&& $subject instanceof \App\Entity\<?= str_replace('Voter', null, $class_data->getClassName()) ?>;
}
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool
{
$user = $token->getUser();
// if the user is anonymous, do not grant access
if (!$user instanceof UserInterface) {
return false;
}
// ... (check conditions and return true to grant permission) ...
switch ($attribute) {
case self::EDIT:
// logic to determine if the user can EDIT
// return true or false
break;
case self::VIEW:
// logic to determine if the user can VIEW
// return true or false
break;
}
return false;
}
}

View File

@@ -0,0 +1,67 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
/**
* @see https://symfony.com/doc/current/security/custom_authenticator.html
*/
class <?= $class_short_name ?> extends AbstractAuthenticator
{
/**
* Called on every request to decide if this authenticator should be
* used for the request. Returning `false` will cause this authenticator
* to be skipped.
*/
public function supports(Request $request): ?bool
{
// return $request->headers->has('X-AUTH-TOKEN');
}
public function authenticate(Request $request): Passport
{
// $apiToken = $request->headers->get('X-AUTH-TOKEN');
// if (null === $apiToken) {
// The token header was empty, authentication fails with HTTP Status
// Code 401 "Unauthorized"
// throw new CustomUserMessageAuthenticationException('No API token provided');
// }
// implement your own logic to get the user identifier from `$apiToken`
// e.g. by looking up a user in the database using its API key
// $userIdentifier = /** ... */;
// return new SelfValidatingPassport(new UserBadge($userIdentifier));
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
// on success, let the request continue
return null;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
// public function start(Request $request, ?AuthenticationException $authException = null): Response
// {
// /*
// * If you would like this class to control what happens when an anonymous user accesses a
// * protected page (e.g. redirect to /login), uncomment this method and make this class
// * implement Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface.
// *
// * For more details, see https://symfony.com/doc/current/security/experimental_authenticators.html#configuring-the-authentication-entry-point
// */
// }
}

View File

@@ -0,0 +1,23 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $controller_name ?> extends AbstractController
{
#[Route(path: '/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('<?= $template_path ?>/login.html.twig', [
'last_username' => $lastUsername,
'error' => $error,
]);
}
}

View File

@@ -0,0 +1,79 @@
<?= "<?php\n" ?>
namespace App\Tests;
<?= $use_statements ?>
class LoginControllerTest extends WebTestCase
{
private KernelBrowser $client;
protected function setUp(): void
{
$this->client = static::createClient();
$container = static::getContainer();
$em = $container->get('doctrine.orm.entity_manager');
$userRepository = $em->getRepository(<?= $user_short_name ?>::class);
// Remove any existing users from the test database
foreach ($userRepository->findAll() as $user) {
$em->remove($user);
}
$em->flush();
// Create a <?= $user_short_name ?> fixture
/** @var UserPasswordHasherInterface $passwordHasher */
$passwordHasher = $container->get('security.user_password_hasher');
$user = (new <?= $user_short_name ?>())->setEmail('email@example.com');
$user->setPassword($passwordHasher->hashPassword($user, 'password'));
$em->persist($user);
$em->flush();
}
public function testLogin(): void
{
// Denied - Can't login with invalid email address.
$this->client->request('GET', '/login');
self::assertResponseIsSuccessful();
$this->client->submitForm('Sign in', [
'_username' => 'doesNotExist@example.com',
'_password' => 'password',
]);
self::assertResponseRedirects('/login');
$this->client->followRedirect();
// Ensure we do not reveal if the user exists or not.
self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.');
// Denied - Can't login with invalid password.
$this->client->request('GET', '/login');
self::assertResponseIsSuccessful();
$this->client->submitForm('Sign in', [
'_username' => 'email@example.com',
'_password' => 'bad-password',
]);
self::assertResponseRedirects('/login');
$this->client->followRedirect();
// Ensure we do not reveal the user exists but the password is wrong.
self::assertSelectorTextContains('.alert-danger', 'Invalid credentials.');
// Success - Login with valid credentials is allowed.
$this->client->submitForm('Sign in', [
'_username' => 'email@example.com',
'_password' => 'password',
]);
self::assertResponseRedirects('/');
$this->client->followRedirect();
self::assertSelectorNotExists('.alert-danger');
self::assertResponseIsSuccessful();
}
}

View File

@@ -0,0 +1,40 @@
{% extends 'base.html.twig' %}
{% block title %}Log in!{% endblock %}
{% block body %}
<form method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
<?php if ($logout_setup): ?>
{% if app.user %}
<div class="mb-3">
You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
</div>
{% endif %}
<?php endif; ?>
<h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
<label for="username"><?= $username_label; ?></label>
<input type="<?= $username_is_email ? 'email' : 'text'; ?>" value="{{ last_username }}" name="_username" id="username" class="form-control" autocomplete="<?= $username_is_email ? 'email' : 'username'; ?>" required autofocus>
<label for="password">Password</label>
<input type="password" name="_password" id="password" class="form-control" autocomplete="current-password" required>
<input type="hidden" name="_csrf_token" data-controller="csrf-protection" value="{{ csrf_token('authenticate') }}">
{#
Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
See https://symfony.com/doc/current/security/remember_me.html
<div class="checkbox mb-3">
<input type="checkbox" name="_remember_me" id="_remember_me">
<label for="_remember_me">Remember me</label>
</div>
#}
<button class="btn btn-lg btn-primary" type="submit">
Sign in
</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,32 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> implements EncoderInterface, DecoderInterface
{
public const FORMAT = '<?= $format ?>';
public function encode(mixed $data, string $format, array $context = []): string
{
// TODO: return your encoded data
return '';
}
public function supportsEncoding(string $format): bool
{
return self::FORMAT === $format;
}
public function decode(string $data, string $format, array $context = [])<?php if ($use_decoder_return_type): ?>: mixed<?php endif; ?>
{
// TODO: return your decoded data
return '';
}
public function supportsDecoding(string $format): bool
{
return self::FORMAT === $format;
}
}

View File

@@ -0,0 +1,41 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> implements NormalizerInterface
{
public function __construct(
#[Autowire(service: 'serializer.normalizer.object')]
private NormalizerInterface $normalizer
) {
}
public function normalize($object, ?string $format = null, array $context = []): array
{
$data = $this->normalizer->normalize($object, $format, $context);
// TODO: add, edit, or delete some data
return $data;
}
public function supportsNormalization($data, ?string $format = null, array $context = []): bool
{
<?php if ($entity_exists): ?>
return $data instanceof <?= $entity_name ?>;
<?php else: ?>
// TODO: return $data instanceof Object
<?php endif ?>
}
public function getSupportedTypes(?string $format): array
{
<?php if ($entity_exists): ?>
return [<?= $entity_name ?>::class => true];
<?php else: ?>
// TODO: return [Object::class => true];
<?php endif ?>
}
}

View File

@@ -0,0 +1,47 @@
import { Controller } from '@hotwired/stimulus';
/*
* The following line makes this controller "lazy": it won't be downloaded until needed
* See https://symfony.com/bundles/StimulusBundle/current/index.html#lazy-stimulus-controllers
*/
/* stimulusFetch: 'lazy' */
export default class extends Controller {
<?= $targets ? " static targets = $targets\n" : "" ?>
<?php if ($values) { ?>
static values = {
<?php foreach ($values as $value): ?>
<?= $value['name'] ?>: <?= $value['type'] ?>,
<?php endforeach; ?>
}
<?php } ?>
<?= $classes ? " static classes = $classes\n" : '' ?>
initialize() {
// Called once when the controller is first instantiated (per element)
// Here you can initialize variables, create scoped callables for event
// listeners, instantiate external libraries, etc.
// this._fooBar = this.fooBar.bind(this)
}
connect() {
// Called every time the controller is connected to the DOM
// (on page load, when it's added to the DOM, moved in the DOM, etc.)
// Here you can add event listeners on the element or target elements,
// add or remove classes, attributes, dispatch custom events, etc.
// this.fooTarget.addEventListener('click', this._fooBar)
}
// Add custom controller actions here
// fooBar() { this.fooTarget.classList.toggle(this.bazClass) }
disconnect() {
// Called anytime its element is disconnected from the DOM
// (on page change, when it's removed from or moved in the DOM, etc.)
// Here you should remove all event listeners added in "connect()"
// this.fooTarget.removeEventListener('click', this._fooBar)
}
}

View File

@@ -0,0 +1,16 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use <?= $api_test_case_fqcn; ?>;
class <?= $class_name ?> extends ApiTestCase
{
public function testSomething(): void
{
$response = static::createClient()->request('GET', '/');
$this->assertResponseIsSuccessful();
$this->assertJsonContains(['@id' => '/']);
}
}

View File

@@ -0,0 +1,31 @@
<?php /* @deprecated remove this method when removing make:unit-test and make:functional-test */ ?>
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements ?>
class <?= $class_name ?> extends <?= $panther_is_available ? 'PantherTestCase' : 'WebTestCase' ?><?= "\n" ?>
{
public function testSomething(): void
{
<?php if ($panther_is_available): ?>
$client = static::createPantherClient();
<?php else: ?>
$client = static::createClient();
<?php endif ?>
$crawler = $client->request('GET', '/');
<?php if ($web_assertions_are_available): ?>
<?php if (!$panther_is_available): ?>
$this->assertResponseIsSuccessful();
<?php endif ?>
$this->assertSelectorTextContains('h1', 'Hello World');
<?php else: ?>
<?php if (!$panther_is_available): ?>
$this->assertSame(200, $client->getResponse()->getStatusCode());
<?php endif ?>
$this->assertStringContainsString('Hello World', $crawler->filter('h1')->text());
<?php endif ?>
}
}

View File

@@ -0,0 +1,17 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class <?= $class_name ?> extends KernelTestCase
{
public function testSomething(): void
{
$kernel = self::bootKernel();
$this->assertSame('test', $kernel->getEnvironment());
// $routerService = static::getContainer()->get('router');
// $myCustomService = static::getContainer()->get(CustomService::class);
}
}

View File

@@ -0,0 +1,20 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use Symfony\Component\Panther\PantherTestCase;
class <?= $class_name ?> extends PantherTestCase
{
public function testSomething(): void
{
$client = static::createPantherClient();
$crawler = $client->request('GET', '/');
<?php if ($web_assertions_are_available): ?>
$this->assertSelectorTextContains('h1', 'Hello World');
<?php else: ?>
$this->assertStringContainsString('Hello World', $crawler->filter('h1')->text());
<?php endif ?>
}
}

View File

@@ -0,0 +1,13 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use PHPUnit\Framework\TestCase;
class <?= $class_name ?> extends TestCase
{
public function testSomething(): void
{
$this->assertTrue(true);
}
}

View File

@@ -0,0 +1,14 @@
<?php /* @deprecated remove this method when removing make:unit-test and make:functional-test */ ?>
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name ?> extends TestCase
{
public function testSomething(): void
{
$this->assertTrue(true);
}
}

View File

@@ -0,0 +1,22 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class <?= $class_name ?> extends WebTestCase
{
public function testSomething(): void
{
$client = static::createClient();
$crawler = $client->request('GET', '/');
<?php if ($web_assertions_are_available): ?>
$this->assertResponseIsSuccessful();
$this->assertSelectorTextContains('h1', 'Hello World');
<?php else: ?>
$this->assertSame(200, $client->getResponse()->getStatusCode());
$this->assertStringContainsString('Hello World', $crawler->filter('h1')->text());
<?php endif ?>
}
}

View File

@@ -0,0 +1,10 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
#[AsTwigComponent]
final class <?= $class_name."\n" ?>
{
}

View File

@@ -0,0 +1,25 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements ?>
class <?= $class_name ?> extends AbstractExtension
{
public function getFilters(): array
{
return [
// If your filter generates SAFE HTML, you should add a third
// parameter: ['is_safe' => ['html']]
// Reference: https://twig.symfony.com/doc/3.x/advanced.html#automatic-escaping
new TwigFilter('filter_name', [<?= $runtime_class_name ?>::class, 'doSomething']),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('function_name', [<?= $runtime_class_name ?>::class, 'doSomething']),
];
}
}

View File

@@ -0,0 +1,12 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use Symfony\UX\LiveComponent\Attribute\AsLiveComponent;
use Symfony\UX\LiveComponent\DefaultActionTrait;
#[AsLiveComponent]
final class <?= $class_name."\n" ?>
{
use DefaultActionTrait;
}

View File

@@ -0,0 +1,18 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements ?>
class <?= $class_name ?> implements RuntimeExtensionInterface
{
public function __construct()
{
// Inject dependencies if needed
}
public function doSomething($value)
{
// ...
}
}

View File

@@ -0,0 +1,3 @@
<div{{ attributes }}>
<!-- component HTML -->
</div>

View File

@@ -0,0 +1,22 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace(); ?>;
<?= $class_data->getUseStatements(); ?>
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
<?= $class_data->getClassDeclaration(); ?>
{
public string $message = 'The string "{{ value }}" contains an illegal character: it can only contain letters or numbers.';
// You can use #[HasNamedArguments] to make some constraint options required.
// All configurable options must be passed to the constructor.
public function __construct(
public string $mode = 'strict',
?array $groups = null,
mixed $payload = null
) {
parent::__construct([], $groups, $payload);
}
}

View File

@@ -0,0 +1,24 @@
<?= "<?php\n" ?>
namespace <?= $class_data->getNamespace(); ?>;
<?= $class_data->getUseStatements(); ?>
<?= $class_data->getClassDeclaration(); ?>
{
public function validate(mixed $value, Constraint $constraint): void
{
/** @var <?= $constraint_class_name ?> $constraint */
if (null === $value || '' === $value) {
return;
}
// TODO: implement the validation here
$this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $value)
->addViolation()
;
}
}

View File

@@ -0,0 +1,51 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
class <?= $class_name; ?><?= "\n" ?>
{
public function __construct(
private VerifyEmailHelperInterface $verifyEmailHelper,
private MailerInterface $mailer,
private EntityManagerInterface $entityManager
) {
}
public function sendEmailConfirmation(string $verifyEmailRouteName, <?= $user_class_name ?> $user, TemplatedEmail $email): void
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
$verifyEmailRouteName,
(string) $user-><?= $id_getter ?>(),
<?php if ($verify_email_anonymously): ?>
(string) $user-><?= $email_getter ?>(),
['id' => $user-><?= $id_getter ?>()]
<?php else: ?>
(string) $user-><?= $email_getter ?>()
<?php endif; ?>
);
$context = $email->getContext();
$context['signedUrl'] = $signatureComponents->getSignedUrl();
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
$email->context($context);
$this->mailer->send($email);
}
/**
* @throws VerifyEmailExceptionInterface
*/
public function handleEmailConfirmation(Request $request, <?= $user_class_name ?> $user): void
{
$this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user-><?= $id_getter ?>(), (string) $user-><?= $email_getter?>());
$user->setIsVerified(true);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}

View File

@@ -0,0 +1,52 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
<?= $use_statements; ?>
final class <?= $class_name ?> extends AbstractRequestParser
{
protected function getRequestMatcher(): RequestMatcherInterface
{
<?php if ($use_chained_requests_matcher) : ?>
return new ChainRequestMatcher([
<?= empty($request_matchers) ? '// Add RequestMatchers to fit your needs' : '' ?>
<?php foreach ($request_matchers as $request_matcher) : ?>
new <?= Symfony\Bundle\MakerBundle\Str::getShortClassName($request_matcher) ?>(<?= $request_matcher_arguments[$request_matcher] ?>),
<?php endforeach; ?>
]);
<?php else : ?>
return new <?= Symfony\Bundle\MakerBundle\Str::getShortClassName($request_matchers[0]) ?>(<?= $request_matcher_arguments[$request_matchers[0]] ?>);
<?php endif; ?>
}
/**
* @throws JsonException
*/
protected function doParse(Request $request, #[\SensitiveParameter] string $secret): ?RemoteEvent
{
// TODO: Adapt or replace the content of this method to fit your need.
// Validate the request against $secret.
$authToken = $request->headers->get('X-Authentication-Token');
if ($authToken !== $secret) {
throw new RejectWebhookException(Response::HTTP_UNAUTHORIZED, 'Invalid authentication token.');
}
// Validate the request payload.
if (!$request->getPayload()->has('name')
|| !$request->getPayload()->has('id')) {
throw new RejectWebhookException(Response::HTTP_BAD_REQUEST, 'Request payload does not contain required fields.');
}
// Parse the request payload and return a RemoteEvent object.
$payload = $request->getPayload();
return new RemoteEvent(
$payload->getString('name'),
$payload->getString('id'),
$payload->all(),
);
}
}

View File

@@ -0,0 +1,20 @@
<?= "<?php\n" ?>
namespace <?= $namespace; ?>;
use Symfony\Component\RemoteEvent\Attribute\AsRemoteEventConsumer;
use Symfony\Component\RemoteEvent\Consumer\ConsumerInterface;
use Symfony\Component\RemoteEvent\RemoteEvent;
#[AsRemoteEventConsumer('<?= $webhook_name ?>')]
final class <?= $class_name ?> implements ConsumerInterface
{
public function __construct()
{
}
public function consume(RemoteEvent $event): void
{
// Implement your own logic here
}
}