Merge remote-tracking branch 'origin/feature_form'
This commit is contained in:
commit
be7e1b7180
6
.env.test
Normal file
6
.env.test
Normal file
@ -0,0 +1,6 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
SYMFONY_DEPRECATIONS_HELPER=999999
|
||||
PANTHER_APP_ENV=panther
|
||||
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
|
15
.gitignore
vendored
15
.gitignore
vendored
@ -15,3 +15,18 @@ TODO.rs
|
||||
###> phpstan/phpstan ###
|
||||
phpstan.neon
|
||||
###< phpstan/phpstan ###
|
||||
|
||||
###> symfony/asset-mapper ###
|
||||
/public/assets/
|
||||
/assets/vendor
|
||||
###< symfony/asset-mapper ###
|
||||
|
||||
###> symfony/phpunit-bridge ###
|
||||
.phpunit.result.cache
|
||||
/phpunit.xml
|
||||
###< symfony/phpunit-bridge ###
|
||||
|
||||
###> phpunit/phpunit ###
|
||||
/phpunit.xml
|
||||
.phpunit.result.cache
|
||||
###< phpunit/phpunit ###
|
||||
|
@ -2,7 +2,7 @@ FROM php:8.3-fpm
|
||||
|
||||
RUN apt-get update && apt-get install -y unzip libzip-dev && rm -rf /var/cache/apt/*
|
||||
RUN curl -sSL https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions -o - | sh -s \
|
||||
opentelemetry-php/ext-opentelemetry@main opcache zip grpc
|
||||
opentelemetry-php/ext-opentelemetry@main opcache zip grpc intl
|
||||
RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer
|
||||
RUN composer self-update
|
||||
RUN usermod -a -G www-data root
|
||||
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
TODO:
|
||||
-----
|
||||
- [ ] Chybí speciální slovník nebo vypnutí slovníku pro testy
|
9
assets/app.js
Normal file
9
assets/app.js
Normal file
@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Welcome to your app's main JavaScript file!
|
||||
*
|
||||
* This file will be included onto the page via the importmap() Twig function,
|
||||
* which should already be in your base.html.twig.
|
||||
*/
|
||||
import './styles/app.css';
|
||||
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
BIN
assets/images/wip.png
Normal file
BIN
assets/images/wip.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
11
assets/styles/app.css
Normal file
11
assets/styles/app.css
Normal file
@ -0,0 +1,11 @@
|
||||
body {
|
||||
background-color: skyblue;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: skyblue;
|
||||
}
|
||||
|
||||
.header-another-code {
|
||||
font-size: 2rem;
|
||||
}
|
23
bin/phpunit
Executable file
23
bin/phpunit
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
if (!ini_get('date.timezone')) {
|
||||
ini_set('date.timezone', 'UTC');
|
||||
}
|
||||
|
||||
if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
|
||||
if (PHP_VERSION_ID >= 80000) {
|
||||
require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
|
||||
} else {
|
||||
define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
|
||||
require PHPUNIT_COMPOSER_INSTALL;
|
||||
PHPUnit\TextUI\Command::main();
|
||||
}
|
||||
} else {
|
||||
if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
|
||||
echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
|
||||
}
|
@ -18,6 +18,8 @@
|
||||
"open-telemetry/sdk": "^1.0",
|
||||
"open-telemetry/transport-grpc": "^1.0",
|
||||
"php-http/httplug": "*",
|
||||
"symfony/asset": "7.0.*",
|
||||
"symfony/asset-mapper": "7.0.*",
|
||||
"symfony/console": "7.0.*",
|
||||
"symfony/dotenv": "7.0.*",
|
||||
"symfony/flex": "^2",
|
||||
@ -26,8 +28,12 @@
|
||||
"symfony/http-client": "*",
|
||||
"symfony/options-resolver": "7.0.*",
|
||||
"symfony/runtime": "7.0.*",
|
||||
"symfony/translation": "7.0.*",
|
||||
"symfony/twig-bundle": "7.0.*",
|
||||
"symfony/yaml": "7.0.*"
|
||||
"symfony/validator": "7.0.*",
|
||||
"symfony/yaml": "7.0.*",
|
||||
"twig/extra-bundle": "^2.12|^3.0",
|
||||
"twig/twig": "^2.12|^3.0"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
@ -60,7 +66,8 @@
|
||||
"scripts": {
|
||||
"auto-scripts": {
|
||||
"cache:clear": "symfony-cmd",
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd"
|
||||
"assets:install %PUBLIC_DIR%": "symfony-cmd",
|
||||
"importmap:install": "symfony-cmd"
|
||||
},
|
||||
"post-install-cmd": [
|
||||
"@auto-scripts"
|
||||
@ -79,6 +86,12 @@
|
||||
}
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^1.10"
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/browser-kit": "7.0.*",
|
||||
"symfony/css-selector": "7.0.*",
|
||||
"symfony/phpunit-bridge": "^7.0",
|
||||
"symfony/stopwatch": "7.0.*",
|
||||
"symfony/web-profiler-bundle": "7.0.*"
|
||||
}
|
||||
}
|
||||
|
2717
composer.lock
generated
2717
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -3,4 +3,6 @@
|
||||
return [
|
||||
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
|
||||
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
|
||||
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
|
||||
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
|
||||
];
|
||||
|
5
config/packages/asset_mapper.yaml
Normal file
5
config/packages/asset_mapper.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
framework:
|
||||
asset_mapper:
|
||||
# The paths to make available to the asset mapper.
|
||||
paths:
|
||||
- assets/
|
13
config/packages/translation.yaml
Normal file
13
config/packages/translation.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
framework:
|
||||
default_locale: 'cs'
|
||||
translator:
|
||||
default_path: '%kernel.project_dir%/translations'
|
||||
# providers:
|
||||
# crowdin:
|
||||
# dsn: '%env(CROWDIN_DSN)%'
|
||||
# loco:
|
||||
# dsn: '%env(LOCO_DSN)%'
|
||||
# lokalise:
|
||||
# dsn: '%env(LOKALISE_DSN)%'
|
||||
# phrase:
|
||||
# dsn: '%env(PHRASE_DSN)%'
|
@ -1,6 +1,6 @@
|
||||
twig:
|
||||
default_path: '%kernel.project_dir%/templates'
|
||||
|
||||
form_themes: ['bootstrap_5_layout.html.twig']
|
||||
when@test:
|
||||
twig:
|
||||
strict_variables: true
|
||||
|
15
config/packages/validator.yaml
Normal file
15
config/packages/validator.yaml
Normal file
@ -0,0 +1,15 @@
|
||||
framework:
|
||||
validation:
|
||||
email_validation_mode: html5
|
||||
enabled: true
|
||||
translation_domain: validation_errors
|
||||
|
||||
# Enables validator auto-mapping support.
|
||||
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
|
||||
#auto_mapping:
|
||||
# App\Entity\: []
|
||||
|
||||
when@test:
|
||||
framework:
|
||||
validation:
|
||||
not_compromised_password: false
|
17
config/packages/web_profiler.yaml
Normal file
17
config/packages/web_profiler.yaml
Normal file
@ -0,0 +1,17 @@
|
||||
when@dev:
|
||||
web_profiler:
|
||||
toolbar: true
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler:
|
||||
only_exceptions: false
|
||||
collect_serializer_data: true
|
||||
|
||||
when@test:
|
||||
web_profiler:
|
||||
toolbar: false
|
||||
intercept_redirects: false
|
||||
|
||||
framework:
|
||||
profiler: { collect: false }
|
8
config/routes/web_profiler.yaml
Normal file
8
config/routes/web_profiler.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
when@dev:
|
||||
web_profiler_wdt:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
|
||||
prefix: /_wdt
|
||||
|
||||
web_profiler_profiler:
|
||||
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
|
||||
prefix: /_profiler
|
@ -4,6 +4,8 @@
|
||||
# Put parameters here that don't need to change on each machine where the app is deployed
|
||||
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
|
||||
parameters:
|
||||
env(SYMFONY_EXAMPLE_APP_CURRENCIES): '["CZK", "EUR", "USD", "GBP"]'
|
||||
app.currencies: '%env(json:SYMFONY_EXAMPLE_APP_CURRENCIES)%'
|
||||
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
@ -21,5 +23,17 @@ services:
|
||||
- '../src/Entity/'
|
||||
- '../src/Kernel.php'
|
||||
|
||||
App\Service\StaticCurrencyLister:
|
||||
arguments:
|
||||
$available_currencies: '%app.currencies%'
|
||||
|
||||
App\Service\StubQRCodeGenerator:
|
||||
arguments:
|
||||
$imagePath: '%kernel.project_dir%/assets/images/wip.png'
|
||||
|
||||
App\Service\CurrencyListerInterface: '@App\Service\StaticCurrencyLister'
|
||||
App\Service\QRCodeQROptionsProviderInterface: '@App\Service\QRCodeQROptionsDefaultProvider'
|
||||
App\Service\QRCodeGeneratorInterface: '@App\Service\StubQRCodeGenerator'
|
||||
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
||||
|
12
config/services_test.yaml
Normal file
12
config/services_test.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
services:
|
||||
# default configuration for services in *this* file
|
||||
_defaults:
|
||||
autowire: true # Automatically injects dependencies in your services.
|
||||
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
|
||||
public: true
|
||||
|
||||
App\Service\QRCodeGeneratorInterface:
|
||||
lazy: true
|
||||
public: true
|
||||
# add more service definitions when explicit configuration is needed
|
||||
# please note that last definitions always *replace* previous ones
|
25
importmap.php
Normal file
25
importmap.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Returns the importmap for this application.
|
||||
*
|
||||
* - "path" is a path inside the asset mapper system. Use the
|
||||
* "debug:asset-map" command to see the full list of paths.
|
||||
*
|
||||
* - "entrypoint" (JavaScript only) set to true for any module that will
|
||||
* be used as an "entrypoint" (and passed to the importmap() Twig function).
|
||||
*
|
||||
* The "importmap:require" command can be used to add new entries to this file.
|
||||
*
|
||||
* This file has been auto-generated by the importmap commands.
|
||||
*/
|
||||
return [
|
||||
'app' => [
|
||||
'path' => './assets/app.js',
|
||||
'entrypoint' => true,
|
||||
],
|
||||
'bootstrap/dist/css/bootstrap.min.css' => [
|
||||
'version' => '5.3.2',
|
||||
'type' => 'css',
|
||||
],
|
||||
];
|
@ -1,5 +1,5 @@
|
||||
parameters:
|
||||
level: 6
|
||||
level: 9
|
||||
paths:
|
||||
- bin/
|
||||
- config/
|
||||
|
38
phpunit.xml.dist
Normal file
38
phpunit.xml.dist
Normal file
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
convertDeprecationsToExceptions="false"
|
||||
>
|
||||
<php>
|
||||
<ini name="display_errors" value="1" />
|
||||
<ini name="error_reporting" value="-1" />
|
||||
<server name="APP_ENV" value="test" force="true" />
|
||||
<server name="SHELL_VERBOSITY" value="-1" />
|
||||
<server name="SYMFONY_PHPUNIT_REMOVE" value="" />
|
||||
<server name="SYMFONY_PHPUNIT_VERSION" value="9.6" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Project Test Suite">
|
||||
<directory>tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<listeners>
|
||||
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
|
||||
</listeners>
|
||||
|
||||
<extensions>
|
||||
</extensions>
|
||||
</phpunit>
|
@ -2,16 +2,51 @@
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\QRCode\QRCode;
|
||||
use App\Form\Type\QRCodeType;
|
||||
use App\Service\QRCodeGeneratorInterface;
|
||||
use App\Service\QRCodeQROptionsProviderInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
class IndexController extends AbstractController {
|
||||
|
||||
public function __construct(private readonly QRCodeQROptionsProviderInterface $qrCodeQROptionsFactory) {}
|
||||
#[Route('/', name: 'homepage')]
|
||||
public function indexAction(): Response
|
||||
public function indexAction(
|
||||
Request $request,
|
||||
QRCodeGeneratorInterface $qrCodeGenerator,
|
||||
): Response
|
||||
{
|
||||
$result = $this->render('index/homepage.html.twig');
|
||||
return $result;
|
||||
$qrCodeImage = null;
|
||||
|
||||
$qrCode = $this->createQrCodeEntity();
|
||||
$form = $this->createForm(QRCodeType::class, $qrCode);
|
||||
|
||||
|
||||
if ($request->isMethod('POST')) {
|
||||
$form->submit($request->request->all($form->getName()));
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/* Puvodne jsem premyslel nad redirectem a ulozenim obrazku
|
||||
do session, nicmene u takovehleho typu aplikace me to prijde naopak kontraproduktivni.
|
||||
a zadani o tom mlci. Navic v pripade ze aplikace bude provozovana ve vice instancich
|
||||
by se musela resit memcache, redis... */
|
||||
$qrCodeImage = $qrCodeGenerator->generateQRCodeFromEntity($qrCode);
|
||||
$form = $this->createForm(QRCodeType::class, $this->createQrCodeEntity());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('index/homepage.html.twig', [
|
||||
'form' => $form,
|
||||
'qrCodeImage' => $qrCodeImage
|
||||
]);
|
||||
}
|
||||
|
||||
private function createQrCodeEntity(): QRCode {
|
||||
return new QRCode(
|
||||
codeQROptions: $this->qrCodeQROptionsFactory->getDefault()
|
||||
);
|
||||
}
|
||||
}
|
115
src/Entity/QRCode/QRCode.php
Normal file
115
src/Entity/QRCode/QRCode.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\QRCode;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* QRCode description
|
||||
*/
|
||||
class QRCode
|
||||
{
|
||||
#[Assert\NotBlank]
|
||||
#[Assert\Iban(
|
||||
message: 'messages.not_a_iban_number',
|
||||
)]
|
||||
private ?string $iban;
|
||||
#[Assert\NotBlank(message: 'messages.fill_value')]
|
||||
#[Assert\GreaterThanOrEqual(
|
||||
'today', message: 'messages.invalid_date'
|
||||
)]
|
||||
private ?\DateTime $dueDate;
|
||||
#[Assert\NotBlank(message: 'messages.fill_value')]
|
||||
private ?string $message;
|
||||
|
||||
#[Assert\Valid]
|
||||
#[Assert\NotBlank]
|
||||
private ?QRCodeMoney $money;
|
||||
#[Assert\Valid]
|
||||
#[Assert\NotBlank]
|
||||
private ?QRCodePaymentIdentification $paymentIdentification;
|
||||
#[Assert\Valid]
|
||||
#[Assert\NotBlank]
|
||||
private readonly QRCodeQROptions $codeQROptions;
|
||||
|
||||
/**
|
||||
* @param QRCodeQROptions $codeQROptions
|
||||
* @param string|null $iban
|
||||
* @param \DateTime|null $dueDate
|
||||
* @param string|null $message
|
||||
* @param QRCodeMoney|null $money
|
||||
*/
|
||||
public function __construct(
|
||||
QRCodeQROptions $codeQROptions,
|
||||
?string $iban = null,
|
||||
?\DateTime $dueDate = null,
|
||||
?string $message = null,
|
||||
?QRCodeMoney $money = null)
|
||||
{
|
||||
$this->iban = $iban;
|
||||
$this->dueDate = $dueDate;
|
||||
$this->message = $message;
|
||||
$this->money = $money;
|
||||
$this->codeQROptions = $codeQROptions;
|
||||
}
|
||||
|
||||
public function getIban(): ?string
|
||||
{
|
||||
return $this->iban;
|
||||
}
|
||||
|
||||
public function setIban(?string $iban): void
|
||||
{
|
||||
$this->iban = $iban;
|
||||
}
|
||||
|
||||
public function getDueDate(): ?\DateTime
|
||||
{
|
||||
return $this->dueDate;
|
||||
}
|
||||
|
||||
public function setDueDate(?\DateTime $dueDate): void
|
||||
{
|
||||
$this->dueDate = $dueDate;
|
||||
}
|
||||
|
||||
public function getMessage(): ?string
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function setMessage(?string $message): void
|
||||
{
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function getMoney(): ?QRCodeMoney
|
||||
{
|
||||
return $this->money;
|
||||
}
|
||||
|
||||
public function setMoney(?QRCodeMoney $money): void
|
||||
{
|
||||
$this->money = $money;
|
||||
}
|
||||
|
||||
public function getCodeQROptions(): ?QRCodeQROptions
|
||||
{
|
||||
return $this->codeQROptions;
|
||||
}
|
||||
|
||||
|
||||
public function getPaymentIdentification(): ?QRCodePaymentIdentification
|
||||
{
|
||||
return $this->paymentIdentification;
|
||||
}
|
||||
|
||||
public function setPaymentIdentification(?QRCodePaymentIdentification $paymentIdentification): void
|
||||
{
|
||||
$this->paymentIdentification = $paymentIdentification;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
59
src/Entity/QRCode/QRCodeMoney.php
Normal file
59
src/Entity/QRCode/QRCodeMoney.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\QRCode;
|
||||
|
||||
use App\Validator\Currency;
|
||||
use App\Validator\Money;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Money type for QR code
|
||||
*/
|
||||
class QRCodeMoney
|
||||
{
|
||||
#[Assert\Positive(
|
||||
message: 'messages.not_valid_amount',
|
||||
)]
|
||||
#[Assert\NotBlank(message: 'messages.fill_value')]
|
||||
#[Money(message: 'messages.not_valid_amount')]
|
||||
private ?string $amount;
|
||||
|
||||
// TODO: getCurrencies validation
|
||||
#[Assert\NotBlank(message: 'messages.fill_value')]
|
||||
#[Currency(message: 'messages.not_currency')]
|
||||
private ?string $currency;
|
||||
|
||||
/**
|
||||
* @param string|null $amount
|
||||
* @param string|null $currency
|
||||
*/
|
||||
public function __construct(?string $amount = null, ?string $currency = null)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
$this->currency = $currency;
|
||||
}
|
||||
|
||||
|
||||
public function getAmount(): ?string
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
public function setAmount(?string $amount): void
|
||||
{
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
public function getCurrency(): ?string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
public function setCurrency(?string $currency): void
|
||||
{
|
||||
$this->currency = $currency;
|
||||
}
|
||||
|
||||
|
||||
}
|
59
src/Entity/QRCode/QRCodePaymentIdentification.php
Normal file
59
src/Entity/QRCode/QRCodePaymentIdentification.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\QRCode;
|
||||
|
||||
use App\Validator\BankPaymentIdentificationNumber;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Payment identification for QR code
|
||||
*/
|
||||
class QRCodePaymentIdentification {
|
||||
#[BankPaymentIdentificationNumber(
|
||||
message: 'messages.not_variable_symbol',
|
||||
)]
|
||||
private ?string $variableSymbol;
|
||||
|
||||
#[BankPaymentIdentificationNumber(
|
||||
message: 'messages.not_specific_symbol',
|
||||
)]
|
||||
private ?string $specificSymbol;
|
||||
|
||||
#[BankPaymentIdentificationNumber(
|
||||
message: 'messages.not_constant_symbol',
|
||||
)] // https://www.hyponamiru.cz/en/glossary/constant-symbol/
|
||||
private ?string $constantSymbol;
|
||||
|
||||
public function getVariableSymbol(): ?string
|
||||
{
|
||||
return $this->variableSymbol;
|
||||
}
|
||||
|
||||
public function setVariableSymbol(?string $variableSymbol): void
|
||||
{
|
||||
$this->variableSymbol = $variableSymbol;
|
||||
}
|
||||
|
||||
public function getSpecificSymbol(): ?string
|
||||
{
|
||||
return $this->specificSymbol;
|
||||
}
|
||||
|
||||
public function setSpecificSymbol(?string $specificSymbol): void
|
||||
{
|
||||
$this->specificSymbol = $specificSymbol;
|
||||
}
|
||||
|
||||
public function getConstantSymbol(): ?string
|
||||
{
|
||||
return $this->constantSymbol;
|
||||
}
|
||||
|
||||
public function setConstantSymbol(?string $constantSymbol): void
|
||||
{
|
||||
$this->constantSymbol = $constantSymbol;
|
||||
}
|
||||
|
||||
|
||||
}
|
43
src/Entity/QRCode/QRCodeQROptions.php
Normal file
43
src/Entity/QRCode/QRCodeQROptions.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Entity\QRCode;
|
||||
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
/**
|
||||
* Options for generating QR code
|
||||
*/
|
||||
readonly class QRCodeQROptions {
|
||||
#[Assert\Positive(
|
||||
message: 'messages.scale_must_be_positive',
|
||||
)]
|
||||
#[Assert\NotBlank(message: 'messages.fill_value')]
|
||||
private int $scale;
|
||||
|
||||
#[Assert\PositiveOrZero(
|
||||
message: 'messages.margin_must_be_positive',
|
||||
)]
|
||||
#[Assert\NotBlank(message: 'messages.fill_value')]
|
||||
private int $margin;
|
||||
|
||||
/**
|
||||
* @param int $scale scaling of png image (2 = 1px is mapped onto 2px, 3 = 1px onto 3px etc.)
|
||||
* @param int $margin white borders of png image in pixels
|
||||
*/
|
||||
public function __construct(int $scale, int $margin)
|
||||
{
|
||||
$this->scale = $scale;
|
||||
$this->margin = $margin;
|
||||
}
|
||||
|
||||
public function getScale(): int
|
||||
{
|
||||
return $this->scale;
|
||||
}
|
||||
|
||||
public function getMargin(): int
|
||||
{
|
||||
return $this->margin;
|
||||
}
|
||||
}
|
41
src/Form/Type/QRCodeMoneyType.php
Normal file
41
src/Form/Type/QRCodeMoneyType.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\QRCode\QRCodeMoney;
|
||||
use App\Service\CurrencyListerInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\NumberType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class QRCodeMoneyType extends AbstractType
|
||||
{
|
||||
private readonly CurrencyListerInterface $currencyLister;
|
||||
|
||||
public function __construct(CurrencyListerInterface $currencyLister) {
|
||||
$this->currencyLister = $currencyLister;
|
||||
}
|
||||
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('amount', NumberType::class)
|
||||
->add('currency', ChoiceType::class, [
|
||||
'choices' => array_combine(
|
||||
iterator_to_array($this->currencyLister->getCurrencies()),
|
||||
iterator_to_array($this->currencyLister->getCurrencies())
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => QRCodeMoney::class,
|
||||
]);
|
||||
}
|
||||
}
|
28
src/Form/Type/QRCodePaymentIdentificationType.php
Normal file
28
src/Form/Type/QRCodePaymentIdentificationType.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
use App\Entity\QRCode\QRCodePaymentIdentification;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class QRCodePaymentIdentificationType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('variableSymbol', IntegerType::class, ['required' => false])
|
||||
->add('specificSymbol', IntegerType::class, ['required' => false])
|
||||
->add('constantSymbol', IntegerType::class, ['required' => false]);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => QRCodePaymentIdentification::class,
|
||||
]);
|
||||
}
|
||||
}
|
34
src/Form/Type/QRCodeType.php
Normal file
34
src/Form/Type/QRCodeType.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
|
||||
use App\Entity\QRCode\QRCode;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\DateType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
class QRCodeType extends AbstractType
|
||||
{
|
||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('iban', TextType::class)
|
||||
->add('dueDate', DateType::class)
|
||||
->add('message', TextType::class)
|
||||
->add('money', QRCodeMoneyType::class)
|
||||
->add('paymentIdentification', QRCodePaymentIdentificationType::class)
|
||||
->add('generate_qr_code', SubmitType::class);
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'data_class' => QRCode::class,
|
||||
]);
|
||||
}
|
||||
}
|
12
src/Service/CurrencyListerInterface.php
Normal file
12
src/Service/CurrencyListerInterface.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
interface CurrencyListerInterface
|
||||
{
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getCurrencies(): iterable;
|
||||
}
|
16
src/Service/QRCodeGeneratorInterface.php
Normal file
16
src/Service/QRCodeGeneratorInterface.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\QRCode\QRCode;
|
||||
|
||||
interface QRCodeGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Generates QR code from entity
|
||||
* @param QRCode $entity
|
||||
* @return string base64 encoded PNG
|
||||
*/
|
||||
public function generateQRCodeFromEntity(QRCode $entity): string;
|
||||
}
|
21
src/Service/QRCodeQROptionsDefaultProvider.php
Normal file
21
src/Service/QRCodeQROptionsDefaultProvider.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\QRCode\QRCodeQROptions;
|
||||
|
||||
readonly final class QRCodeQROptionsDefaultProvider implements QRCodeQROptionsProviderInterface {
|
||||
private QRCodeQROptions $qrCodeDefaultOptions;
|
||||
|
||||
public function __construct() {
|
||||
$this->qrCodeDefaultOptions = new QRCodeQROptions(
|
||||
1,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public function getDefault(): QRCodeQROptions {
|
||||
return $this->qrCodeDefaultOptions;
|
||||
}
|
||||
}
|
10
src/Service/QRCodeQROptionsProviderInterface.php
Normal file
10
src/Service/QRCodeQROptionsProviderInterface.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\QRCode\QRCodeQROptions;
|
||||
|
||||
interface QRCodeQROptionsProviderInterface {
|
||||
public function getDefault(): QRCodeQROptions;
|
||||
}
|
20
src/Service/StaticCurrencyLister.php
Normal file
20
src/Service/StaticCurrencyLister.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
/**
|
||||
* Represents currency lister loaded from configuration
|
||||
*/
|
||||
readonly final class StaticCurrencyLister implements CurrencyListerInterface {
|
||||
/**
|
||||
* @param String[] $available_currencies
|
||||
*/
|
||||
public function __construct(private array $available_currencies) {
|
||||
}
|
||||
|
||||
public function getCurrencies(): iterable
|
||||
{
|
||||
return $this->available_currencies;
|
||||
}
|
||||
}
|
21
src/Service/StubQRCodeGenerator.php
Normal file
21
src/Service/StubQRCodeGenerator.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Entity\QRCode\QRCode;
|
||||
|
||||
readonly final class StubQRCodeGenerator implements QRCodeGeneratorInterface
|
||||
{
|
||||
public function __construct(private string $imagePath) {}
|
||||
|
||||
public function generateQRCodeFromEntity(QRCode $entity): string
|
||||
{
|
||||
$content = file_get_contents($this->imagePath);
|
||||
|
||||
return match ($content) {
|
||||
false => throw new \InvalidArgumentException('Image path is invalid'),
|
||||
default => base64_encode($content)
|
||||
};
|
||||
}
|
||||
}
|
31
src/Validator/BankPaymentIdentificationNumber.php
Normal file
31
src/Validator/BankPaymentIdentificationNumber.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
class BankPaymentIdentificationNumber extends Constraint
|
||||
{
|
||||
public const NOT_CURRENCY_ERROR = '478a18e7-95ba-473d-9101-cabd45e49115';
|
||||
|
||||
protected const ERROR_NAMES = [
|
||||
self::NOT_CURRENCY_ERROR => 'NOT_BANKID_NUMBER_ERROR',
|
||||
];
|
||||
|
||||
public string $message = 'This value should be bank identification number.';
|
||||
|
||||
/**
|
||||
* @param mixed|null $options
|
||||
* @param string|null $message
|
||||
* @param array<string>|null $groups
|
||||
* @param mixed|null $payload
|
||||
*/
|
||||
public function __construct(mixed $options = null, string $message = null, array $groups = null, mixed $payload = null)
|
||||
{
|
||||
parent::__construct($options ?? [], $groups, $payload);
|
||||
|
||||
$this->message = $message ?? $this->message;
|
||||
}
|
||||
}
|
35
src/Validator/BankPaymentIdentificationNumberValidator.php
Normal file
35
src/Validator/BankPaymentIdentificationNumberValidator.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
class BankPaymentIdentificationNumberValidator extends ConstraintValidator
|
||||
{
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof BankPaymentIdentificationNumber) {
|
||||
throw new UnexpectedTypeException($constraint, Money::class);
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return ;
|
||||
}
|
||||
|
||||
if (is_string($value) === false) {
|
||||
throw new UnexpectedValueException($value, "string");
|
||||
}
|
||||
|
||||
if (strlen($value) <= 10 && filter_var($value, FILTER_VALIDATE_INT) !== false) {
|
||||
return ;
|
||||
}
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setParameter('{{ string }}', $value)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
31
src/Validator/Currency.php
Normal file
31
src/Validator/Currency.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
class Currency extends Constraint
|
||||
{
|
||||
public const NOT_CURRENCY_ERROR = '548618e7-95ba-473d-9101-cabd45e49115';
|
||||
|
||||
protected const ERROR_NAMES = [
|
||||
self::NOT_CURRENCY_ERROR => 'NOT_CURRENCY_ERROR',
|
||||
];
|
||||
|
||||
public string $message = 'This value should be currency.';
|
||||
|
||||
/**
|
||||
* @param mixed|null $options
|
||||
* @param string|null $message
|
||||
* @param array<string>|null $groups
|
||||
* @param mixed|null $payload
|
||||
*/
|
||||
public function __construct(mixed $options = null, string $message = null, array $groups = null, mixed $payload = null)
|
||||
{
|
||||
parent::__construct($options ?? [], $groups, $payload);
|
||||
|
||||
$this->message = $message ?? $this->message;
|
||||
}
|
||||
}
|
38
src/Validator/CurrencyValidator.php
Normal file
38
src/Validator/CurrencyValidator.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use App\Service\CurrencyListerInterface;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
class CurrencyValidator extends ConstraintValidator {
|
||||
public function __construct(private readonly CurrencyListerInterface $currencyLister) {}
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof Currency) {
|
||||
throw new UnexpectedTypeException($constraint, Currency::class);
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return ;
|
||||
}
|
||||
|
||||
if (is_string($value) === false) {
|
||||
throw new UnexpectedValueException($value, "string");
|
||||
}
|
||||
|
||||
$availCurrencies = iterator_to_array($this->currencyLister->getCurrencies());
|
||||
|
||||
if (in_array($value, $availCurrencies, true)) {
|
||||
return ;
|
||||
}
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setParameter('{{ string }}', $value)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
31
src/Validator/Money.php
Normal file
31
src/Validator/Money.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
|
||||
class Money extends Constraint
|
||||
{
|
||||
public const NOT_CURRENCY_ERROR = '478618e7-95ba-473d-9101-cabd45e49115';
|
||||
|
||||
protected const ERROR_NAMES = [
|
||||
self::NOT_CURRENCY_ERROR => 'NOT_CURRENCY_ERROR',
|
||||
];
|
||||
|
||||
public string $message = 'This value should be currency.';
|
||||
|
||||
/**
|
||||
* @param mixed|null $options
|
||||
* @param string|null $message
|
||||
* @param array<string>|null $groups
|
||||
* @param mixed|null $payload
|
||||
*/
|
||||
public function __construct(mixed $options = null, string $message = null, array $groups = null, mixed $payload = null)
|
||||
{
|
||||
parent::__construct($options ?? [], $groups, $payload);
|
||||
|
||||
$this->message = $message ?? $this->message;
|
||||
}
|
||||
}
|
34
src/Validator/MoneyValidator.php
Normal file
34
src/Validator/MoneyValidator.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Validator;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedValueException;
|
||||
|
||||
class MoneyValidator extends ConstraintValidator {
|
||||
public function validate(mixed $value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof Money) {
|
||||
throw new UnexpectedTypeException($constraint, Money::class);
|
||||
}
|
||||
|
||||
if (null === $value || '' === $value) {
|
||||
return ;
|
||||
}
|
||||
|
||||
if (is_string($value) === false) {
|
||||
throw new UnexpectedValueException($value, "string");
|
||||
}
|
||||
|
||||
if (preg_match('/^\d+([\.,]\d{0,2})?$/', $value)) {
|
||||
return ;
|
||||
}
|
||||
|
||||
$this->context->buildViolation($constraint->message)
|
||||
->setParameter('{{ string }}', $value)
|
||||
->addViolation();
|
||||
}
|
||||
}
|
85
symfony.lock
85
symfony.lock
@ -35,6 +35,35 @@
|
||||
"phpstan.dist.neon"
|
||||
]
|
||||
},
|
||||
"phpunit/phpunit": {
|
||||
"version": "9.6",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "9.6",
|
||||
"ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"phpunit.xml.dist",
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/asset-mapper": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.4",
|
||||
"ref": "568d44f71388f41f49dc382768fee20d52569359"
|
||||
},
|
||||
"files": [
|
||||
"assets/app.js",
|
||||
"assets/styles/app.css",
|
||||
"config/packages/asset_mapper.yaml",
|
||||
"importmap.php"
|
||||
]
|
||||
},
|
||||
"symfony/console": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
@ -78,6 +107,21 @@
|
||||
"src/Kernel.php"
|
||||
]
|
||||
},
|
||||
"symfony/phpunit-bridge": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "1f5830c331065b6e4c9d5fa2105e322d29fcd573"
|
||||
},
|
||||
"files": [
|
||||
".env.test",
|
||||
"bin/phpunit",
|
||||
"phpunit.xml.dist",
|
||||
"tests/bootstrap.php"
|
||||
]
|
||||
},
|
||||
"symfony/routing": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
@ -91,6 +135,19 @@
|
||||
"config/routes.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/translation": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.3",
|
||||
"ref": "64fe617084223633e1dedf9112935d8c95410d3e"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/translation.yaml",
|
||||
"translations/.gitignore"
|
||||
]
|
||||
},
|
||||
"symfony/twig-bundle": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
@ -103,5 +160,33 @@
|
||||
"config/packages/twig.yaml",
|
||||
"templates/base.html.twig"
|
||||
]
|
||||
},
|
||||
"symfony/validator": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "5.3",
|
||||
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/validator.yaml"
|
||||
]
|
||||
},
|
||||
"symfony/web-profiler-bundle": {
|
||||
"version": "7.0",
|
||||
"recipe": {
|
||||
"repo": "github.com/symfony/recipes",
|
||||
"branch": "main",
|
||||
"version": "6.1",
|
||||
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
|
||||
},
|
||||
"files": [
|
||||
"config/packages/web_profiler.yaml",
|
||||
"config/routes/web_profiler.yaml"
|
||||
]
|
||||
},
|
||||
"twig/extra-bundle": {
|
||||
"version": "v3.8.0"
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,19 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Welcome!{% endblock %}</title>
|
||||
<title>{% block title %}{{ "QR code generator" | trans }}{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text></svg>">
|
||||
{% block stylesheets %}
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{% block importmap %}{{ importmap('app') }}{% endblock %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<div class="d-flex w-100 p-3 justify-content-center header">
|
||||
{{ "QR code generator" | trans }}
|
||||
</div>
|
||||
{% block body %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,5 +1,104 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
Homepage
|
||||
<div class="container">
|
||||
{% if qrCodeImage %}
|
||||
<div class="d-flex p-3 justify-content-center qr-code-image">
|
||||
<img src="data:image/png;base64,{{ qrCodeImage }}" alt="QR code" />
|
||||
</div>
|
||||
<h2 class="header-another-code">{{ "You can generate another QR code here" | trans }}:</h2>
|
||||
{% endif %}
|
||||
{{ form_start(form) }}
|
||||
<div class="mb-3 col-auto">
|
||||
{{ form_label(form.iban) }}
|
||||
{{ form_widget(form.iban) }}
|
||||
|
||||
<small>{{ form_help(form.iban) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.iban) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 col-auto">
|
||||
{{ form_label(form.dueDate) }}
|
||||
{{ form_widget(form.dueDate) }}
|
||||
|
||||
<small>{{ form_help(form.dueDate) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.dueDate) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3 col-auto">
|
||||
{{ form_label(form.message) }}
|
||||
{{ form_widget(form.message) }}
|
||||
|
||||
<small>{{ form_help(form.message) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.message) }}
|
||||
</div>
|
||||
</div>
|
||||
{# money #}
|
||||
<div class="mb-3 col-auto">
|
||||
<div class="d-flex">
|
||||
<div class="p-2">
|
||||
{{ form_label(form.money.amount) }}
|
||||
{{ form_widget(form.money.amount) }}
|
||||
|
||||
<small>{{ form_help(form.money.amount) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.money.amount) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
{{ form_label(form.money.currency) }}
|
||||
{{ form_widget(form.money.currency) }}
|
||||
|
||||
<small>{{ form_help(form.money.currency) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.money.currency) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{# payment identification #}
|
||||
<div class="mb-3 col-auto">
|
||||
<div class="d-flex">
|
||||
<div class="p-2">
|
||||
{{ form_label(form.paymentIdentification.variableSymbol) }}
|
||||
{{ form_widget(form.paymentIdentification.variableSymbol) }}
|
||||
|
||||
<small>{{ form_help(form.paymentIdentification.variableSymbol) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.paymentIdentification.variableSymbol) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
{{ form_label(form.paymentIdentification.constantSymbol) }}
|
||||
{{ form_widget(form.paymentIdentification.constantSymbol) }}
|
||||
|
||||
<small>{{ form_help(form.paymentIdentification.constantSymbol) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.paymentIdentification.constantSymbol) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
{{ form_label(form.paymentIdentification.specificSymbol) }}
|
||||
{{ form_widget(form.paymentIdentification.specificSymbol) }}
|
||||
|
||||
<small>{{ form_help(form.paymentIdentification.specificSymbol) }}</small>
|
||||
|
||||
<div class="form-error">
|
||||
{{ form_errors(form.paymentIdentification.specificSymbol) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
92
tests/Controller/IndexControllerTest.php
Normal file
92
tests/Controller/IndexControllerTest.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use App\Service\QRCodeGeneratorInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
class IndexControllerTest extends WebTestCase
|
||||
{
|
||||
public function testFormIsPresentAfterLoadHomepage(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/');
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSelectorExists('form');
|
||||
}
|
||||
|
||||
public function testImageIsPresentAfterSubmitForm(): void {
|
||||
$client = $this->generateClientForSubmitTest();
|
||||
$response = $client->request('GET', '/');
|
||||
$this->assertResponseIsSuccessful();
|
||||
$form = $response->selectButton('Vygenerovat QR kód')->form();
|
||||
$dt = new \DateTime('now+1 hour'); // In case of 23:59:59
|
||||
$client->submit($form, [
|
||||
'qr_code' => [
|
||||
'iban' => 'CZ5508000000001234567899',
|
||||
'dueDate' => $dt->format('Y-m-d'),
|
||||
'message' => 'foo',
|
||||
'money' => [
|
||||
'currency' => 'CZK',
|
||||
'amount' => '100'
|
||||
],
|
||||
'paymentIdentification' => [
|
||||
'variableSymbol' => '123',
|
||||
'constantSymbol' => '123',
|
||||
'specificSymbol' => '123'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSelectorExists('form');
|
||||
$this->assertSelectorExists('img');
|
||||
}
|
||||
|
||||
public function testImageIsNotPresentAfterFailSubmitForm(): void {
|
||||
$client = $this->generateClientForSubmitTest();
|
||||
$response = $client->request('GET', '/');
|
||||
$this->assertResponseIsSuccessful();
|
||||
$form = $response->selectButton('Vygenerovat QR kód')->form();
|
||||
$dt = new \DateTime('now+1 hour'); // In case of 23:59:59
|
||||
$client->submit($form, [
|
||||
'qr_code' => [
|
||||
'iban' => 'IBAN_THAT_NOT_EXISTS',
|
||||
'dueDate' => $dt->format('Y-m-d'),
|
||||
'message' => 'foo',
|
||||
'money' => [
|
||||
'currency' => 'CZK',
|
||||
'amount' => '100'
|
||||
],
|
||||
'paymentIdentification' => [
|
||||
'variableSymbol' => '123',
|
||||
'constantSymbol' => '123',
|
||||
'specificSymbol' => '123'
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
$this->assertResponseIsUnprocessable();
|
||||
$this->assertSelectorExists('form');
|
||||
$this->assertSelectorNotExists('img');
|
||||
}
|
||||
|
||||
private function generateClientForSubmitTest(): KernelBrowser
|
||||
{
|
||||
$mock = $this->createMock(QRCodeGeneratorInterface::class);
|
||||
$mock->expects($this->any())
|
||||
->method('generateQRCodeFromEntity')
|
||||
->will($this->returnValue('foo'));
|
||||
|
||||
|
||||
$client = static::createClient();
|
||||
$client->disableReboot();
|
||||
|
||||
self::getContainer()->set('App\Service\QRCodeGeneratorInterface', $mock);
|
||||
|
||||
return $client;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Validator;
|
||||
|
||||
use App\Validator\BankPaymentIdentificationNumber;
|
||||
use App\Validator\BankPaymentIdentificationNumberValidator;
|
||||
|
||||
class BankPaymentIdentificationNumberValidatorTest extends ValidatorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider successDp
|
||||
* @param string $generatedValue
|
||||
* @return void
|
||||
*/
|
||||
public function testSuccess(string $generatedValue): void {
|
||||
$bankPaymentIdentificationValidator = new BankPaymentIdentificationNumberValidator();
|
||||
$bankPaymentIdentificationValidator->initialize($this->createExecutionContext(false));
|
||||
$bankPaymentIdentificationValidator->validate($generatedValue, new BankPaymentIdentificationNumber(['message' => 'foo']));
|
||||
}
|
||||
|
||||
private function successDp(): array {
|
||||
return [['122'], ['1234567890'], ['1']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider failureDp
|
||||
* @param string $generatedValue
|
||||
* @return void
|
||||
*/
|
||||
public function testFailure(string $generatedValue): void {
|
||||
$bankPaymentIdentificationValidator = new BankPaymentIdentificationNumberValidator();
|
||||
$bankPaymentIdentificationValidator->initialize($this->createExecutionContext(true));
|
||||
$bankPaymentIdentificationValidator->validate($generatedValue, new BankPaymentIdentificationNumber(['message' => 'foo']));
|
||||
}
|
||||
|
||||
private function failureDp(): array {
|
||||
return [['122.b'], ['a.a'], ['a'], ['2.040'], ['2,a'], ['122.1'], ['12345678901']];
|
||||
}
|
||||
}
|
33
tests/Validator/CurrencyValidatorTest.php
Normal file
33
tests/Validator/CurrencyValidatorTest.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Validator;
|
||||
|
||||
use App\Service\CurrencyListerInterface;
|
||||
use App\Validator\Currency;
|
||||
use App\Validator\CurrencyValidator;
|
||||
|
||||
class CurrencyValidatorTest extends ValidatorTestCase
|
||||
{
|
||||
public function testSuccess(): void {
|
||||
$currencyValidator = $this->createCurrencyValidator();
|
||||
$currencyValidator->initialize($this->createExecutionContext(false));
|
||||
|
||||
$currencyValidator->validate('USD', new Currency(['message' => 'foo']));
|
||||
}
|
||||
|
||||
public function testFailure(): void {
|
||||
$currencyValidator = $this->createCurrencyValidator();
|
||||
$currencyValidator->initialize($this->createExecutionContext(true));
|
||||
$currencyValidator->validate('EUR', new Currency(['message' => 'foo']));
|
||||
}
|
||||
|
||||
private function createCurrencyValidator(): CurrencyValidator {
|
||||
$mock = $this->createMock(CurrencyListerInterface::class);
|
||||
$mock->expects($this->once())
|
||||
->method('getCurrencies')
|
||||
->will($this->returnValue(['USD', 'CZK']));
|
||||
|
||||
return new CurrencyValidator($mock);
|
||||
}
|
||||
}
|
40
tests/Validator/MoneyValidatorTest.php
Normal file
40
tests/Validator/MoneyValidatorTest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Validator;
|
||||
|
||||
use App\Validator\Money;
|
||||
use App\Validator\MoneyValidator;
|
||||
|
||||
class MoneyValidatorTest extends ValidatorTestCase
|
||||
{
|
||||
/**
|
||||
* @dataProvider successDp
|
||||
* @param string $generatedValue
|
||||
* @return void
|
||||
*/
|
||||
public function testSuccess(string $generatedValue): void {
|
||||
$moneyValidator = new MoneyValidator();
|
||||
$moneyValidator->initialize($this->createExecutionContext(false));
|
||||
$moneyValidator->validate($generatedValue, new Money(['message' => 'foo']));
|
||||
}
|
||||
|
||||
private function successDp(): array {
|
||||
return [['122'], ['122.0'], ['122,00'], ['122'], ['2.05'], ['2,04']];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider failureDp
|
||||
* @param string $generatedValue
|
||||
* @return void
|
||||
*/
|
||||
public function testFailure(string $generatedValue): void {
|
||||
$moneyValidator = new MoneyValidator();
|
||||
$moneyValidator->initialize($this->createExecutionContext(true));
|
||||
$moneyValidator->validate($generatedValue, new Money(['message' => 'foo']));
|
||||
}
|
||||
|
||||
private function failureDp(): array {
|
||||
return [['122.b'], ['a.a'], ['a'], ['2.040'], ['2,a']];
|
||||
}
|
||||
}
|
19
tests/Validator/ValidatorTestCase.php
Normal file
19
tests/Validator/ValidatorTestCase.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Validator;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Validator\Context\ExecutionContextInterface;
|
||||
|
||||
abstract class ValidatorTestCase extends TestCase
|
||||
{
|
||||
protected function createExecutionContext(bool $violation): ExecutionContextInterface {
|
||||
$expected = $violation ? $this->once() : $this->never();
|
||||
|
||||
$context = $this->createMock(ExecutionContextInterface::class);
|
||||
$context->expects($expected)
|
||||
->method('buildViolation');
|
||||
|
||||
return $context;
|
||||
}
|
||||
}
|
15
tests/bootstrap.php
Normal file
15
tests/bootstrap.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) {
|
||||
require dirname(__DIR__).'/config/bootstrap.php';
|
||||
} elseif (method_exists(Dotenv::class, 'bootEnv')) {
|
||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||
}
|
||||
|
||||
if ($_SERVER['APP_DEBUG']) {
|
||||
umask(0000);
|
||||
}
|
0
translations/.gitignore
vendored
Normal file
0
translations/.gitignore
vendored
Normal file
11
translations/messages.cs.yaml
Normal file
11
translations/messages.cs.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
Due date: "Datum splatnosti"
|
||||
Message: "Zpráva pro přijemce"
|
||||
Amount: "Částka"
|
||||
Currency: "Měna"
|
||||
Variable symbol: "Variabilní symbol"
|
||||
Constant symbol: "Konstantní symbol"
|
||||
Specific symbol: "Specifický symbol"
|
||||
Generate qr code: "Vygenerovat QR kód"
|
||||
Iban: "IBAN"
|
||||
QR code generator: "Generátor bankovních QR kódů"
|
||||
You can generate another QR code here: "Další QR kód si můžete vygenerovat zde"
|
10
translations/validation_errors.cs.yaml
Normal file
10
translations/validation_errors.cs.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
messages:
|
||||
not_variable_symbol: "Neplatný variabilní symbol. Variabilní symbol musí být max. 10 číslic"
|
||||
not_specific_symbol: "Neplatný specifický symbol. Specifický symbol musí být max. 10 číslic"
|
||||
not_constant_symbol: "Neplatný konstantní symbol. Konstatní symbol musí být max. 10 číslic"
|
||||
not_a_iban_number: "Neplatný formát IBAN"
|
||||
not_valid_amount: "Neplatná částka"
|
||||
invalid_date: "Neplatné datum. Datum musí být v budoucnosti nebo dnes"
|
||||
fill_value: "Prosím vyplňte hodnotu"
|
||||
scale_must_be_positive: "Číslo musí být > 0."
|
||||
margin_must_be_positive: "Margin musí být > 0."
|
Loading…
Reference in New Issue
Block a user