diff --git a/.env b/.env index c974a948..da73f121 100644 --- a/.env +++ b/.env @@ -26,7 +26,7 @@ APP_SECRET=cf9b8a37ccaf3e584b1bbb006b79af57 # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" -DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8" +DATABASE_URL="postgresql://127.0.0.1:5432/db?serverVersion=16&charset=utf8" ###< doctrine/doctrine-bundle ### ###> symfony/messenger ### diff --git a/.idea/codeception.xml b/.idea/codeception.xml new file mode 100644 index 00000000..330f2dd3 --- /dev/null +++ b/.idea/codeception.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/guestbook.iml b/.idea/guestbook.iml index 724647e6..303cbcd8 100644 --- a/.idea/guestbook.iml +++ b/.idea/guestbook.iml @@ -22,6 +22,7 @@ + @@ -107,6 +108,7 @@ + @@ -125,6 +127,7 @@ + @@ -134,6 +137,7 @@ + diff --git a/.idea/php.xml b/.idea/php.xml index 70685a15..955b0572 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -142,6 +142,10 @@ + + + + diff --git a/.idea/phpspec.xml b/.idea/phpspec.xml new file mode 100644 index 00000000..ec7e1d44 --- /dev/null +++ b/.idea/phpspec.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/composer.json b/composer.json index 40153059..f31bfac1 100644 --- a/composer.json +++ b/composer.json @@ -10,8 +10,9 @@ "blackfireio/blackfire-symfony-meta": "^1.0", "doctrine/dbal": "^3", "doctrine/doctrine-bundle": "^2.12", - "doctrine/doctrine-migrations-bundle": "^3.3", + "doctrine/doctrine-migrations-bundle": "^3.0", "doctrine/orm": "^3.2", + "easycorp/easyadmin-bundle": "4.x-dev", "phpdocumentor/reflection-docblock": "^5.4", "phpstan/phpdoc-parser": "^1.29", "symfony/asset": "6.4.*", @@ -45,7 +46,8 @@ "symfony/yaml": "6.4.*", "symfonycorp/platformsh-meta": "^1.0", "twig/extra-bundle": "^2.12|^3.0", - "twig/twig": "^2.12|^3.0" + "twig/intl-extra": "^3", + "twig/twig": "^3.0" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index 8a888eef..dc092fe1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "604c452ad904d46864e1d33c1fc03787", + "content-hash": "f80efdaab3b23805c28afbfbe55fb366", "packages": [ { "name": "blackfire/php-sdk", @@ -1492,6 +1492,102 @@ }, "time": "2024-05-08T08:12:09+00:00" }, + { + "name": "easycorp/easyadmin-bundle", + "version": "4.x-dev", + "source": { + "type": "git", + "url": "https://github.com/EasyCorp/EasyAdminBundle.git", + "reference": "35953b58e09901b59061b0975384da0964bf7af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/35953b58e09901b59061b0975384da0964bf7af4", + "reference": "35953b58e09901b59061b0975384da0964bf7af4", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.5", + "doctrine/orm": "^2.10|^3.0", + "ext-json": "*", + "php": ">=8.0.2", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/deprecation-contracts": "^3.0", + "symfony/doctrine-bridge": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/security-bundle": "^5.4|^6.0|^7.0", + "symfony/string": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/twig-bundle": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "symfony/validator": "^5.4|^6.0|^7.0" + }, + "require-dev": { + "doctrine/doctrine-fixtures-bundle": "^3.4|3.5.x-dev", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-phpunit": "^1.2", + "phpstan/phpstan-strict-rules": "^1.4", + "phpstan/phpstan-symfony": "^1.2", + "psr/log": "^1.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", + "symfony/debug-bundle": "^5.4|^6.0|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/phpunit-bridge": "^5.4|^6.0|^7.0" + }, + "default-branch": true, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "EasyCorp\\Bundle\\EasyAdminBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Project Contributors", + "homepage": "https://github.com/EasyCorp/EasyAdminBundle/graphs/contributors" + } + ], + "description": "Admin generator for Symfony applications", + "homepage": "https://github.com/EasyCorp/EasyAdminBundle", + "keywords": [ + "admin", + "backend", + "generator" + ], + "support": { + "issues": "https://github.com/EasyCorp/EasyAdminBundle/issues", + "source": "https://github.com/EasyCorp/EasyAdminBundle/tree/4.x" + }, + "funding": [ + { + "url": "https://github.com/javiereguiluz", + "type": "github" + } + ], + "time": "2024-07-01T17:43:06+00:00" + }, { "name": "egulias/email-validator", "version": "4.0.2", @@ -5445,6 +5541,85 @@ ], "time": "2024-06-19T12:35:24+00:00" }, + { + "name": "symfony/polyfill-uuid", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-uuid.git", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/2ba1f33797470debcda07fe9dce20a0003df18e9", + "reference": "2ba1f33797470debcda07fe9dce20a0003df18e9", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-uuid": "*" + }, + "suggest": { + "ext-uuid": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Uuid\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for uuid functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, { "name": "symfony/process", "version": "v6.4.8", @@ -6946,6 +7121,80 @@ ], "time": "2024-05-31T14:49:08+00:00" }, + { + "name": "symfony/uid", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/uid.git", + "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/uid/zipball/35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "reference": "35904eca37a84bb764c560cbfcac9f0ac2bcdbdf", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-uuid": "^1.15" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Uid\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Grégoire Pineau", + "email": "lyrixx@lyrixx.info" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to generate and represent UIDs", + "homepage": "https://symfony.com", + "keywords": [ + "UID", + "ulid", + "uuid" + ], + "support": { + "source": "https://github.com/symfony/uid/tree/v6.4.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, { "name": "symfony/ux-turbo", "version": "v2.18.0", @@ -7563,6 +7812,70 @@ ], "time": "2024-05-11T07:35:57+00:00" }, + { + "name": "twig/intl-extra", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/intl-extra.git", + "reference": "693f6beb8ca91fc6323e01b3addf983812f65c93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/intl-extra/zipball/693f6beb8ca91fc6323e01b3addf983812f65c93", + "reference": "693f6beb8ca91fc6323e01b3addf983812f65c93", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/intl": "^5.4|^6.4|^7.0", + "twig/twig": "^3.10" + }, + "require-dev": { + "symfony/phpunit-bridge": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Twig\\Extra\\Intl\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + } + ], + "description": "A Twig extension for Intl", + "homepage": "https://twig.symfony.com", + "keywords": [ + "intl", + "twig" + ], + "support": { + "source": "https://github.com/twigphp/intl-extra/tree/v3.10.0" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2024-05-11T07:35:57+00:00" + }, { "name": "twig/twig", "version": "v3.10.3", @@ -9973,7 +10286,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "easycorp/easyadmin-bundle": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { diff --git a/config/bundles.php b/config/bundles.php index 4e3a5607..e5daac00 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -13,4 +13,5 @@ return [ Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle::class => ['all' => true], ]; diff --git a/config/packages/framework.yaml b/config/packages/framework.yaml index 980ee45c..6174be28 100644 --- a/config/packages/framework.yaml +++ b/config/packages/framework.yaml @@ -9,7 +9,7 @@ framework: # Enables session support. Note that the session will ONLY be started if you read or write from it. # Remove or comment this section to explicitly disable session support. session: - handler_id: null + handler_id: '%env(resolve:DATABASE_URL)%' cookie_secure: auto cookie_samesite: lax diff --git a/config/packages/uid.yaml b/config/packages/uid.yaml new file mode 100644 index 00000000..01520944 --- /dev/null +++ b/config/packages/uid.yaml @@ -0,0 +1,4 @@ +framework: + uid: + default_uuid_version: 7 + time_based_uuid_version: 7 diff --git a/migrations/Version20240703083040.php b/migrations/Version20240703083040.php new file mode 100644 index 00000000..40b85b27 --- /dev/null +++ b/migrations/Version20240703083040.php @@ -0,0 +1,61 @@ +addSql('CREATE SEQUENCE comment_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE SEQUENCE conference_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); + $this->addSql('CREATE TABLE comment (id INT NOT NULL, conference_id INT NOT NULL, author VARCHAR(255) NOT NULL, text TEXT NOT NULL, email VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, photo_filename VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_9474526C604B8382 ON comment (conference_id)'); + $this->addSql('COMMENT ON COLUMN comment.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE TABLE conference (id INT NOT NULL, city VARCHAR(255) NOT NULL, year VARCHAR(4) NOT NULL, is_international BOOLEAN NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE messenger_messages (id BIGSERIAL NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); + $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); + $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); + $this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'(DC2Type:datetime_immutable)\''); + $this->addSql('CREATE OR REPLACE FUNCTION notify_messenger_messages() RETURNS TRIGGER AS $$ + BEGIN + PERFORM pg_notify(\'messenger_messages\', NEW.queue_name::text); + RETURN NEW; + END; + $$ LANGUAGE plpgsql;'); + $this->addSql('DROP TRIGGER IF EXISTS notify_trigger ON messenger_messages;'); + $this->addSql('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON messenger_messages FOR EACH ROW EXECUTE PROCEDURE notify_messenger_messages();'); + $this->addSql('ALTER TABLE comment ADD CONSTRAINT FK_9474526C604B8382 FOREIGN KEY (conference_id) REFERENCES conference (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + $this->addSql('DROP TABLE sessions'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP SEQUENCE comment_id_seq CASCADE'); + $this->addSql('DROP SEQUENCE conference_id_seq CASCADE'); + $this->addSql('CREATE TABLE sessions (sess_id VARCHAR(128) NOT NULL, sess_data BYTEA NOT NULL, sess_lifetime INT NOT NULL, sess_time INT NOT NULL, PRIMARY KEY(sess_id))'); + $this->addSql('CREATE INDEX sess_lifetime_idx ON sessions (sess_lifetime)'); + $this->addSql('ALTER TABLE comment DROP CONSTRAINT FK_9474526C604B8382'); + $this->addSql('DROP TABLE comment'); + $this->addSql('DROP TABLE conference'); + $this->addSql('DROP TABLE messenger_messages'); + } +} diff --git a/migrations/Version20240703090223.php b/migrations/Version20240703090223.php new file mode 100644 index 00000000..2220cbb9 --- /dev/null +++ b/migrations/Version20240703090223.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE sessions (sess_id VARCHAR(128) NOT NULL, sess_data BYTEA NOT NULL, sess_lifetime INT NOT NULL, sess_time INT NOT NULL, PRIMARY KEY(sess_id))'); + $this->addSql('CREATE INDEX sess_lifetime_idx ON sessions (sess_lifetime)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('DROP TABLE sessions'); + } +} diff --git a/src/Controller/Admin/CommentCrudController.php b/src/Controller/Admin/CommentCrudController.php new file mode 100644 index 00000000..3e3d0061 --- /dev/null +++ b/src/Controller/Admin/CommentCrudController.php @@ -0,0 +1,64 @@ +setEntityLabelInSingular('Conference Comment') + ->setEntityLabelInPlural('Conference Comments') + ->setSearchFields(['author', 'text', 'email']) + ->setDefaultSort(['createdAt' => 'DESC']) + ; + } + + public function configureFilters(Filters $filters): Filters + { + return $filters + ->add(EntityFilter::new('conference')) + ; + } + + public function configureFields(string $pageName): iterable + { + yield AssociationField::new('conference'); + yield TextField::new('author'); + yield EmailField::new('email'); + yield TextareaField::new('text') + ->hideOnIndex() + ; + yield TextField::new('photoFilename') + ->onlyOnIndex() + ; + + $createdAt = DateTimeField::new('createdAt')->setFormTypeOptions([ + 'years' => range(date('Y'), date('Y') + 5), + 'widget' => 'single_text', + ]); + if (Crud::PAGE_EDIT === $pageName) { + yield $createdAt->setFormTypeOption('disabled', true); + } else { + yield $createdAt; + } + } + +} diff --git a/src/Controller/Admin/ConferenceCrudController.php b/src/Controller/Admin/ConferenceCrudController.php new file mode 100644 index 00000000..fcab0318 --- /dev/null +++ b/src/Controller/Admin/ConferenceCrudController.php @@ -0,0 +1,28 @@ +container->get(AdminUrlGenerator::class); + $url = $routeBuilder->setController(ConferenceCrudController::class)->generateUrl(); + + return $this->redirect($url); + + // Option 1. You can make your dashboard redirect to some common page of your backend + // + // $adminUrlGenerator = $this->container->get(AdminUrlGenerator::class); + // return $this->redirect($adminUrlGenerator->setController(OneOfYourCrudController::class)->generateUrl()); + + // Option 2. You can make your dashboard redirect to different pages depending on the user + // + // if ('jane' === $this->getUser()->getUsername()) { + // return $this->redirect('...'); + // } + + // Option 3. You can render some custom template to display a proper dashboard with widgets, etc. + // (tip: it's easier if your template extends from @EasyAdmin/page/content.html.twig) + // + // return $this->render('some/path/my-dashboard.html.twig'); + } + + public function configureDashboard(): Dashboard + { + return Dashboard::new() + ->setTitle('Guestbook'); + } + + public function configureMenuItems(): iterable + { + yield MenuItem::linktoRoute('Back to the website', 'fas fa-home', 'homepage'); + yield MenuItem::linkToCrud('Conferences', 'fas fa-map-marker-alt', Conference::class); + yield MenuItem::linkToCrud('Comments', 'fas fa-comments', Comment::class); + } +} diff --git a/src/Controller/ConferenceController.php b/src/Controller/ConferenceController.php index 262396ee..14432ba1 100644 --- a/src/Controller/ConferenceController.php +++ b/src/Controller/ConferenceController.php @@ -2,22 +2,36 @@ namespace App\Controller; +use App\Entity\Conference; +use App\Repository\CommentRepository; +use App\Repository\ConferenceRepository; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Attribute\Route; + class ConferenceController extends AbstractController { #[Route('/', name: 'homepage')] - public function index(): Response + public function index(ConferenceRepository $conferenceRepository): Response { - return new Response(<< - - - - - EOF - ); + return $this->render('conference/index.html.twig', [ + 'conferences' => $conferenceRepository->findAll(), + ]); + } + + #[Route('/conference/{id}', name: 'conference')] + public function show(Request $request, Conference $conference, CommentRepository $commentRepository): Response + { + $offset = max(0, $request->query->getInt('offset', 0)); + $paginator = $commentRepository->getCommentPaginator($conference, $offset); + + return $this->render('conference/show.html.twig', [ + 'conference' => $conference, + 'comments' => $paginator, + 'previous' => $offset - CommentRepository::COMMENTS_PER_PAGE, + 'next' => min(count($paginator), $offset + CommentRepository::COMMENTS_PER_PAGE), + ]); } } diff --git a/src/Entity/Comment.php b/src/Entity/Comment.php new file mode 100644 index 00000000..58e62b5b --- /dev/null +++ b/src/Entity/Comment.php @@ -0,0 +1,112 @@ +id; + } + + public function getAuthor(): ?string + { + return $this->author; + } + + public function setAuthor(string $author): static + { + $this->author = $author; + + return $this; + } + + public function getText(): ?string + { + return $this->text; + } + + public function setText(string $text): static + { + $this->text = $text; + + return $this; + } + + public function getEmail(): ?string + { + return $this->email; + } + + public function setEmail(string $email): static + { + $this->email = $email; + + return $this; + } + + public function getCreatedAt(): ?\DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(\DateTimeImmutable $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } + + public function getConference(): ?Conference + { + return $this->conference; + } + + public function setConference(?Conference $conference): static + { + $this->conference = $conference; + + return $this; + } + + public function getPhotoFilename(): ?string + { + return $this->photoFilename; + } + + public function setPhotoFilename(?string $photoFilename): static + { + $this->photoFilename = $photoFilename; + + return $this; + } +} diff --git a/src/Entity/Conference.php b/src/Entity/Conference.php new file mode 100644 index 00000000..21c8251c --- /dev/null +++ b/src/Entity/Conference.php @@ -0,0 +1,112 @@ + + */ + #[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'conference', orphanRemoval: true)] + private Collection $comments; + + public function __construct() + { + $this->comments = new ArrayCollection(); + } + public function __toString(): string + { + return $this->city.' '.$this->year; + } + + public function getId(): ?int + { + return $this->id; + } + + public function getCity(): ?string + { + return $this->city; + } + + public function setCity(string $city): static + { + $this->city = $city; + + return $this; + } + + public function getYear(): ?string + { + return $this->year; + } + + public function setYear(string $year): static + { + $this->year = $year; + + return $this; + } + + public function isInternational(): ?bool + { + return $this->isInternational; + } + + public function setIsInternational(bool $isInternational): static + { + $this->isInternational = $isInternational; + + return $this; + } + + /** + * @return Collection + */ + public function getComments(): Collection + { + return $this->comments; + } + + public function addComment(Comment $comment): static + { + if (!$this->comments->contains($comment)) { + $this->comments->add($comment); + $comment->setConference($this); + } + + return $this; + } + + public function removeComment(Comment $comment): static + { + if ($this->comments->removeElement($comment)) { + // set the owning side to null (unless already changed) + if ($comment->getConference() === $this) { + $comment->setConference(null); + } + } + + return $this; + } +} diff --git a/src/Repository/CommentRepository.php b/src/Repository/CommentRepository.php new file mode 100644 index 00000000..fb410e6b --- /dev/null +++ b/src/Repository/CommentRepository.php @@ -0,0 +1,60 @@ + + */ +class CommentRepository extends ServiceEntityRepository +{ + public const COMMENTS_PER_PAGE = 2; + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Comment::class); + } + + public function getCommentPaginator(Conference $conference, int $offset): Paginator + { + $query = $this->createQueryBuilder('c') + ->andWhere('c.conference = :conference') + ->setParameter('conference', $conference) + ->orderBy('c.createdAt', 'DESC') + ->setMaxResults(self::COMMENTS_PER_PAGE) + ->setFirstResult($offset) + ->getQuery() + ; + + return new Paginator($query); + } + + // /** + // * @return Comment[] Returns an array of Comment objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('c') + // ->andWhere('c.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('c.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Comment + // { + // return $this->createQueryBuilder('c') + // ->andWhere('c.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/src/Repository/ConferenceRepository.php b/src/Repository/ConferenceRepository.php new file mode 100644 index 00000000..23fb8e15 --- /dev/null +++ b/src/Repository/ConferenceRepository.php @@ -0,0 +1,43 @@ + + */ +class ConferenceRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Conference::class); + } + + // /** + // * @return Conference[] Returns an array of Conference objects + // */ + // public function findByExampleField($value): array + // { + // return $this->createQueryBuilder('c') + // ->andWhere('c.exampleField = :val') + // ->setParameter('val', $value) + // ->orderBy('c.id', 'ASC') + // ->setMaxResults(10) + // ->getQuery() + // ->getResult() + // ; + // } + + // public function findOneBySomeField($value): ?Conference + // { + // return $this->createQueryBuilder('c') + // ->andWhere('c.exampleField = :val') + // ->setParameter('val', $value) + // ->getQuery() + // ->getOneOrNullResult() + // ; + // } +} diff --git a/symfony.lock b/symfony.lock index cdeeb9b1..dfd7a9d8 100644 --- a/symfony.lock +++ b/symfony.lock @@ -38,6 +38,15 @@ "migrations/.gitignore" ] }, + "easycorp/easyadmin-bundle": { + "version": "4.9999999", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.0", + "ref": "b131e6cbfe1b898a508987851963fff485986285" + } + }, "phpunit/phpunit": { "version": "9.6", "recipe": { @@ -260,6 +269,18 @@ "templates/base.html.twig" ] }, + "symfony/uid": { + "version": "6.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.2", + "ref": "d294ad4add3e15d7eb1bae0221588ca89b38e558" + }, + "files": [ + "config/packages/uid.yaml" + ] + }, "symfony/ux-turbo": { "version": "v2.18.0" }, diff --git a/templates/conference/index.html.twig b/templates/conference/index.html.twig index 401adad0..485501b3 100644 --- a/templates/conference/index.html.twig +++ b/templates/conference/index.html.twig @@ -8,13 +8,10 @@ .example-wrapper code { background: #F5F5F5; padding: 2px 6px; } -
-

Hello {{ controller_name }}! ✅

- - This friendly message is coming from: -
    -
  • Your controller at /home/nkolosnjaj/symfony/guestbook/src/Controller/ConferenceController.php
  • -
  • Your template at /home/nkolosnjaj/symfony/guestbook/templates/conference/index.html.twig
  • -
-
+ {% for conference in conferences %} +

{{ conference }}

+

+ View +

+ {% endfor %} {% endblock %} diff --git a/templates/conference/show.html.twig b/templates/conference/show.html.twig new file mode 100644 index 00000000..2b31cacb --- /dev/null +++ b/templates/conference/show.html.twig @@ -0,0 +1,31 @@ +{% extends 'base.html.twig' %} + +{% block title %}Conference Guestbook - {{ conference }}{% endblock %} + +{% block body %} +

{{ conference }} Conference

+ + {% if comments|length > 0 %} +
There are {{ comments|length }} comments.
+ {% for comment in comments %} + {% if comment.photofilename %} + + {% endif %} + +

{{ comment.author }}

+ + {{ comment.createdAt|format_datetime('medium', 'short') }} + + +

{{ comment.text }}

+ {% endfor %} + {% if previous >= 0 %} + Previous + {% endif %} + {% if next < comments|length %} + Next + {% endif %} + {% else %} +
No comments have been posted yet for this conference.
+ {% endif %} +{% endblock %} \ No newline at end of file