add orm and tests

This commit is contained in:
Jonas 2024-06-10 20:22:44 +02:00
parent a32bf4ce7d
commit a541338909
Signed by: lubiana
SSH key fingerprint: SHA256:gkqM8DUX4Blf6P52fycW8ISTd+4eAHH+Uzu9iyc8hAM
23 changed files with 1666 additions and 23 deletions

View file

@ -4,3 +4,4 @@ APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999 SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots
DATABASE_URL="sqlite:///:memory:"

View file

@ -4,7 +4,7 @@
"minimum-stability": "stable", "minimum-stability": "stable",
"prefer-stable": true, "prefer-stable": true,
"require": { "require": {
"php": ">=8.2", "php": ">=8.3",
"ext-ctype": "*", "ext-ctype": "*",
"ext-iconv": "*", "ext-iconv": "*",
"doctrine/dbal": "^3", "doctrine/dbal": "^3",
@ -14,9 +14,13 @@
"symfony/console": "7.1.*", "symfony/console": "7.1.*",
"symfony/dotenv": "7.1.*", "symfony/dotenv": "7.1.*",
"symfony/flex": "^2", "symfony/flex": "^2",
"symfony/form": "7.1.*",
"symfony/framework-bundle": "7.1.*", "symfony/framework-bundle": "7.1.*",
"symfony/runtime": "7.1.*", "symfony/runtime": "7.1.*",
"symfony/security-csrf": "7.1.*",
"symfony/twig-bundle": "7.1.*",
"symfony/uid": "7.1.*", "symfony/uid": "7.1.*",
"symfony/validator": "7.1.*",
"symfony/yaml": "7.1.*" "symfony/yaml": "7.1.*"
}, },
"require-dev": { "require-dev": {
@ -35,7 +39,10 @@
"symfony/runtime": true, "symfony/runtime": true,
"dealerdirect/phpcodesniffer-composer-installer": true "dealerdirect/phpcodesniffer-composer-installer": true
}, },
"sort-packages": true "sort-packages": true,
"platform": {
"php": "8.3"
}
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

1170
composer.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,9 @@
<?php declare(strict_types=1); <?php
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\MakerBundle\MakerBundle;
return [ return [
FrameworkBundle::class => [ Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
'all' => true, Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
MakerBundle::class => [ Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
'dev' => true, Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
],
DoctrineBundle::class => [
'all' => true,
],
DoctrineMigrationsBundle::class => [
'all' => true,
],
]; ];

16
config/packages/twig.php Normal file
View file

@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('twig', [
'file_name_pattern' => '*.twig',
]);
if ($containerConfigurator->env() === 'test') {
$containerConfigurator->extension('twig', [
'strict_variables' => true,
]);
}
};

View file

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $containerConfigurator): void {
$containerConfigurator->extension('framework', [
'validation' => null,
]);
if ($containerConfigurator->env() === 'test') {
$containerConfigurator->extension('framework', [
'validation' => [
'not_compromised_password' => false,
],
]);
}
};

View file

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240610175726 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE food_vendor (id BLOB NOT NULL --(DC2Type:ulid)
, name VARCHAR(50) NOT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE food_vendor');
}
}

1
public/static/css/water.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,81 @@
<?php
namespace App\Controller;
use App\Entity\FoodVendor;
use App\Form\FoodVendorType;
use App\Repository\FoodVendorRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
#[Route('/food/vendor')]
class FoodVendorController extends AbstractController
{
#[Route('/', name: 'app_food_vendor_index', methods: ['GET'])]
public function index(FoodVendorRepository $foodVendorRepository): Response
{
return $this->render('food_vendor/index.html.twig', [
'food_vendors' => $foodVendorRepository->findAll(),
]);
}
#[Route('/new', name: 'app_food_vendor_new', methods: ['GET', 'POST'])]
public function new(Request $request, EntityManagerInterface $entityManager): Response
{
$foodVendor = new FoodVendor();
$form = $this->createForm(FoodVendorType::class, $foodVendor);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($foodVendor);
$entityManager->flush();
return $this->redirectToRoute('app_food_vendor_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('food_vendor/new.html.twig', [
'food_vendor' => $foodVendor,
'form' => $form,
]);
}
#[Route('/{id}', name: 'app_food_vendor_show', methods: ['GET'])]
public function show(FoodVendor $foodVendor): Response
{
return $this->render('food_vendor/show.html.twig', [
'food_vendor' => $foodVendor,
]);
}
#[Route('/{id}/edit', name: 'app_food_vendor_edit', methods: ['GET', 'POST'])]
public function edit(Request $request, FoodVendor $foodVendor, EntityManagerInterface $entityManager): Response
{
$form = $this->createForm(FoodVendorType::class, $foodVendor);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
return $this->redirectToRoute('app_food_vendor_index', [], Response::HTTP_SEE_OTHER);
}
return $this->render('food_vendor/edit.html.twig', [
'food_vendor' => $foodVendor,
'form' => $form,
]);
}
#[Route('/{id}', name: 'app_food_vendor_delete', methods: ['POST'])]
public function delete(Request $request, FoodVendor $foodVendor, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$foodVendor->getId(), $request->getPayload()->getString('_token'))) {
$entityManager->remove($foodVendor);
$entityManager->flush();
}
return $this->redirectToRoute('app_food_vendor_index', [], Response::HTTP_SEE_OTHER);
}
}

39
src/Entity/FoodVendor.php Normal file
View file

@ -0,0 +1,39 @@
<?php
namespace App\Entity;
use App\Repository\FoodVendorRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UlidGenerator;
use Symfony\Bridge\Doctrine\Types\UlidType;
use Symfony\Component\Uid\Ulid;
#[ORM\Entity(repositoryClass: FoodVendorRepository::class)]
class FoodVendor
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UlidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: UlidGenerator::class)]
private Ulid|null $id = null;
#[ORM\Column(length: 50)]
private ?string $name = null;
public function getId(): ?Ulid
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Form;
use App\Entity\FoodVendor;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FoodVendorType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name')
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => FoodVendor::class,
]);
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\FoodVendor;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<FoodVendor>
*/
class FoodVendorRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, FoodVendor::class);
}
}

View file

@ -138,6 +138,19 @@
"config/routes.yaml" "config/routes.yaml"
] ]
}, },
"symfony/twig-bundle": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.4",
"ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
},
"files": [
"config/packages/twig.yaml",
"templates/base.html.twig"
]
},
"symfony/uid": { "symfony/uid": {
"version": "7.1", "version": "7.1",
"recipe": { "recipe": {
@ -146,5 +159,17 @@
"version": "7.0", "version": "7.0",
"ref": "0df5844274d871b37fc3816c57a768ffc60a43a5" "ref": "0df5844274d871b37fc3816c57a768ffc60a43a5"
} }
},
"symfony/validator": {
"version": "7.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "7.0",
"ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
},
"files": [
"config/packages/validator.yaml"
]
} }
} }

12
templates/base.html.twig Normal file
View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{% block title %}Welcome!{% 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><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
<link rel="stylesheet" href="/static/css/water.min.css">
</head>
<body>
{% block body %}{% endblock %}
</body>
</html>

View file

@ -0,0 +1,4 @@
<form method="post" action="{{ path('app_food_vendor_delete', {'id': food_vendor.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
<input type="hidden" name="_token" value="{{ csrf_token('delete' ~ food_vendor.id) }}">
<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,13 @@
{% extends 'base.html.twig' %}
{% block title %}Edit FoodVendor{% endblock %}
{% block body %}
<h1>Edit FoodVendor</h1>
{{ include('food_vendor/_form.html.twig', {'button_label': 'Update'}) }}
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
{{ include('food_vendor/_delete_form.html.twig') }}
{% endblock %}

View file

@ -0,0 +1,35 @@
{% extends 'base.html.twig' %}
{% block title %}FoodVendor index{% endblock %}
{% block body %}
<h1>FoodVendor index</h1>
<table class="table">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>actions</th>
</tr>
</thead>
<tbody>
{% for food_vendor in food_vendors %}
<tr>
<td>{{ food_vendor.id }}</td>
<td>{{ food_vendor.name }}</td>
<td>
<a href="{{ path('app_food_vendor_show', {'id': food_vendor.id}) }}">show</a>
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="3">no records found</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{ path('app_food_vendor_new') }}">Create new</a>
{% endblock %}

View file

@ -0,0 +1,11 @@
{% extends 'base.html.twig' %}
{% block title %}New FoodVendor{% endblock %}
{% block body %}
<h1>Create new FoodVendor</h1>
{{ include('food_vendor/_form.html.twig') }}
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
{% endblock %}

View file

@ -0,0 +1,26 @@
{% extends 'base.html.twig' %}
{% block title %}FoodVendor{% endblock %}
{% block body %}
<h1>FoodVendor</h1>
<table class="table">
<tbody>
<tr>
<th>Id</th>
<td>{{ food_vendor.id }}</td>
</tr>
<tr>
<th>Name</th>
<td>{{ food_vendor.name }}</td>
</tr>
</tbody>
</table>
<a href="{{ path('app_food_vendor_index') }}">back to list</a>
<a href="{{ path('app_food_vendor_edit', {'id': food_vendor.id}) }}">edit</a>
{{ include('food_vendor/_delete_form.html.twig') }}
{% endblock %}

View file

@ -0,0 +1,120 @@
<?php
namespace App\Test\Controller;
use App\Entity\FoodVendor;
use App\Kernel;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class FoodVendorControllerTest extends WebTestCase
{
private KernelBrowser $client;
private EntityManagerInterface $manager;
private EntityRepository $repository;
private string $path = '/food/vendor/';
private function initDataBase(): void
{
(new SchemaTool($this->manager))
->createSchema($this->manager->getMetadataFactory()->getAllMetadata());
}
protected function setUp(): void
{
$this->client = static::createClient();
$this->manager = static::getContainer()->get('doctrine')->getManager();
$this->initDataBase();
$this->repository = $this->manager->getRepository(FoodVendor::class);
foreach ($this->repository->findAll() as $object) {
$this->manager->remove($object);
}
$this->manager->flush();
}
public function testIndex(): void
{
$crawler = $this->client->request('GET', $this->path);
self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('FoodVendor index');
// Use the $crawler to perform additional assertions e.g.
// self::assertSame('Some text on the page', $crawler->filter('.p')->first());
}
public function testNew(): void
{
$this->markTestIncomplete();
$this->client->request('GET', sprintf('%snew', $this->path));
self::assertResponseStatusCodeSame(200);
$this->client->submitForm('Save', [
'food_vendor[name]' => 'Testing',
]);
self::assertResponseRedirects($this->path);
self::assertSame(1, $this->repository->count([]));
}
public function testShow(): void
{
$this->markTestIncomplete();
$fixture = new FoodVendor();
$fixture->setName('My Title');
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
self::assertResponseStatusCodeSame(200);
self::assertPageTitleContains('FoodVendor');
// Use assertions to check that the properties are properly displayed.
}
public function testEdit(): void
{
$this->markTestIncomplete();
$fixture = new FoodVendor();
$fixture->setName('Value');
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s/edit', $this->path, $fixture->getId()));
$this->client->submitForm('Update', [
'food_vendor[name]' => 'Something New',
]);
self::assertResponseRedirects('/food/vendor/');
$fixture = $this->repository->findAll();
self::assertSame('Something New', $fixture[0]->getName());
}
public function testRemove(): void
{
$this->markTestIncomplete();
$fixture = new FoodVendor();
$fixture->setName('Value');
$this->manager->persist($fixture);
$this->manager->flush();
$this->client->request('GET', sprintf('%s%s', $this->path, $fixture->getId()));
$this->client->submitForm('Delete');
self::assertResponseRedirects('/food/vendor/');
self::assertSame(0, $this->repository->count([]));
}
}

View file

@ -1,6 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace App\Tests\Controler; namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

View file

@ -1,5 +1,6 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Dotenv\Dotenv; use Symfony\Component\Dotenv\Dotenv;
require dirname(__DIR__) . '/vendor/autoload.php'; require dirname(__DIR__) . '/vendor/autoload.php';