Symfony 6 budowanie aplikacji MVC: Wyświetlanie, dodawanie, edycja i usuwanie rekordów z wykorzystaniem Bootstrap 5.3

W poprzednim artykule omówiliśmy podstawy tworzenia aplikacji MVC w Symfony, konfigurację bazy danych oraz współpracę z Doctrine ORM. W tym artykule kontynuujemy rozwój naszej aplikacji, dodając funkcje wyświetlania wszystkich rekordów, dodawania, edycji i usuwania rekordów encji Person. Dodatkowo, zastosujemy Bootstrap 5.3, aby poprawić wygląd i responsywność naszej aplikacji.

Aby zacząć, upewnij się, że masz zainstalowany Symfony, skonfigurowaną bazę danych oraz utworzoną encję Person, jak opisano w poprzednim artykule. W tym artykule wykonamy następujące kroki:

  1. Instalacja Bootstrap 5.3 w projekcie Symfony.
  2. Dodawanie akcji kontrolera do wyświetlania listy osób, dodawania, edycji i usuwania rekordów.
  3. Tworzenie szablonów z wykorzystaniem Bootstrap 5.3.
  4. Użycie formularzy Symfony do obsługi formularzy dodawania i edycji rekordów.
  5. Integracja z Doctrine ORM do zarządzania danymi w bazie danych.

Po zakończeniu tego artykułu będziesz posiadać wiedzę na temat podstawowych operacji CRUD (Create, Read, Update, Delete) w aplikacji Symfony z wykorzystaniem Doctrine ORM oraz Bootstrap 5.3 do stylizacji interfejsu.

Instalacja Bootstrapa

Aby zainstalować Bootstrap 5.3 w projekcie Symfony, będziemy korzystać z Webpack Encore, który jest oficjalnym narzędziem do zarządzania zasobami frontendowymi w Symfony. Jeśli jeszcze nie masz skonfigurowanego Webpack Encore, postępuj zgodnie z instrukcjami poniżej:

Otwórz terminal w katalogu projektu i zainstaluj Webpack Encore za pomocą menedżera pakietów Composer:

composer require symfony/webpack-encore-bundle

Zainstaluj zależności frontendowe za pomocą npm:

npm install

Zainstaluj jQuery i Popper.js jako zależności deweloperskie:

npm install jquery @popperjs/core --save-dev

Utwórz plik assets/styles/global.scss z następującą zawartością:

// assets/styles/global.scss

// customize some Bootstrap variables
$primary: darken(#428bca, 20%);

// the ~ allows you to reference things in node_modules
@import "~bootstrap/scss/bootstrap";

Dodaj następujące linijki do pliku assets/app.js:

// Import the global SCSS file
import './styles/global.scss';

// Import jQuery and assign it to the global $ variable
const $ = require('jquery');

// Import and initialize Bootstrap
require('bootstrap');

// Initialize Bootstrap popovers
$(document).ready(function() {
    $('[data-toggle="popover"]').popover();
});

Skompiluj zasoby frontendowe, uruchamiając polecenie:

npm run dev

Konfiguracja CSS i JS

Teraz, gdy Bootstrap jest zainstalowany, upewnij się, że plik base.html.twig znajdujący się w katalogu templates załadował skompilowane pliki CSS i JS. W pliku base.html.twig, znajdziesz już odpowiednie bloki dla arkuszy stylów (stylesheets) oraz skryptów JavaScript (javascripts). Plik base.html.twig powinien wyglądać następująco:

<!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></svg>">
        {# Run `composer require symfony/webpack-encore-bundle` to start using Symfony UX #}
        {% block stylesheets %}
            {{ encore_entry_link_tags('app') }}
        {% endblock %}

        {% block javascripts %}
            {{ encore_entry_script_tags('app') }}
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>

Blok {% block stylesheets %} oraz {% block javascripts %} w pliku base.html.twig odpowiadają za włączanie odpowiednich plików CSS i JS skompilowanych przez Webpack Encore. Wewnątrz tych bloków używane są funkcje encore_entry_link_tags('app') i encore_entry_script_tags('app'), które automatycznie generują odpowiednie znaczniki HTML dla załączania plików CSS i JS.

{{ encore_entry_link_tags('app') }} generuje znacznik <link> dla załączania pliku CSS skompilowanego przez Webpack Encore, a dokładniej plik app.css znajdujący się w katalogu public/build.

{{ encore_entry_script_tags('app') }} generuje znacznik <script> dla załączania pliku JS skompilowanego przez Webpack Encore, a dokładniej plik app.js znajdujący się w katalogu public/build.

Dzięki tym funkcjom, łatwo możemy zarządzać plikami CSS i JS w naszym projekcie, ponieważ Webpack Encore automatycznie dba o ich kompilację, optymalizację i załączanie w odpowiednich miejscach.

Dodanie metod kontrolera

Mając już skonfigurowanego Bootstrapa skupimy się teraz na dodawaniu akcji kontrolera, które będą odpowiadały za wyświetlanie listy osób, dodawanie, edycję oraz usuwanie rekordów. Nauczysz się, jak napisać odpowiedni kod dla każdej z tych metod oraz jak wykorzystać różne funkcje Symfony i Doctrine ORM. Po zaimplementowaniu tych metod będziemy gotowi do tworzenia szablonów z wykorzystaniem Bootstrap 5.3.

W pliku src/Controller/PersonController.php, stworzymy następujące metody:

  • index(): Wyświetla listę osób
  • create(): Wyświetla formularz do dodawania nowej osoby i zapisuje nowy rekord
  • edit(): Wyświetla formularz do edycji istniejącej osoby i aktualizuje rekord
  • delete(): Usuwa istniejącą osobę

Dodamy kolejne metody do naszego kontrolera PersonController. Zacznijmy od metody index(), która będzie wyświetlać listę wszystkich osób w bazie danych.

#[Route('/person', name: 'app_person')]
public function index(EntityManagerInterface $entityManager): Response
{
    $people = $entityManager->getRepository(Person::class)->findAll();

    return $this->render('person/index.html.twig', [
        'people' => $people,
    ]);
}

W powyższej metodzie index():

  1. Pobieramy wszystkie rekordy z bazy danych, korzystając z metody findAll() repozytorium encji Person.
  2. Renderujemy szablon person/index.html.twig, przekazując mu tablicę $people z pobranymi rekordami.

Teraz dodamy metodę create(), która umożliwi tworzenie nowych rekordów encji Person. W tej metodzie użyjemy formularza Symfony, aby zbierać dane od użytkownika.

#[Route('/person/create', name: 'app_person_create')]
public function create(Request $request, EntityManagerInterface $entityManager): Response
{
    $person = new Person();

    $form = $this->createFormBuilder($person)
        ->add('name', TextType::class)
        ->add('surname', TextType::class)
        ->add('age', IntegerType::class)
        ->add('email', EmailType::class)
        ->add('save', SubmitType::class, ['label' => 'Create Person'])
        ->getForm();

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $entityManager->persist($person);
        $entityManager->flush();

        return $this->redirectToRoute('app_person');
    }

    return $this->render('person/create.html.twig', [
        'form' => $form->createView(),
    ]);
}

W powyższej metodzie create():

  1. Tworzymy nowy obiekt encji Person.
  2. Tworzymy formularz za pomocą metody createFormBuilder() i dodajemy odpowiednie pola formularza (name, surname, age, email oraz przycisk zapisu).
  3. Obsługujemy żądanie formularza, sprawdzając czy został przesłany i czy jest poprawny.
  4. Jeśli formularz jest przesłany i poprawny, zapisujemy nowy rekord do bazy danych.
  5. Po zapisaniu rekordu, przekierowujemy użytkownika do listy osób (app_person).
  6. Renderujemy szablon person/create.html.twig, przekazując mu obiekt formularza.

Teraz dodajmy metodę edit(), która pozwoli na edycję istniejących rekordów encji Person. Podobnie jak w przypadku metody create(), użyjemy formularza Symfony do obsługi danych.

W metodzie edit() będziemy potrzebować parametru id, aby zidentyfikować, który rekord encji Person ma zostać edytowany. Oto jak powinien wyglądać kod metody edit():

#[Route('/person/{id}/edit', name: 'app_person_edit', requirements: ['id' => '\d+'])]
public function edit(Request $request, EntityManagerInterface $entityManager, Person $person): Response
{
    $form = $this->createFormBuilder($person)
        ->add('name', TextType::class)
        ->add('surname', TextType::class)
        ->add('age', IntegerType::class)
        ->add('email', EmailType::class)
        ->add('save', SubmitType::class, ['label' => 'Update Person'])
        ->getForm();

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $entityManager->flush();

        return $this->redirectToRoute('app_person');
    }

    return $this->render('person/edit.html.twig', [
        'form' => $form->createView(),
    ]);
}

W powyższej metodzie create():

  1. Tworzymy nowy obiekt encji Person.
  2. Tworzymy formularz za pomocą metody createFormBuilder() i dodajemy odpowiednie pola formularza (name, surname, age, email oraz przycisk zapisu).
  3. Obsługujemy żądanie formularza, sprawdzając czy został przesłany i czy jest poprawny.
  4. Jeśli formularz jest przesłany i poprawny, zapisujemy nowy rekord do bazy danych.
  5. Po zapisaniu rekordu, przekierowujemy użytkownika do listy osób (app_person).
  6. Renderujemy szablon person/create.html.twig, przekazując mu obiekt formularza.

Teraz dodajmy metodę edit(), która pozwoli na edycję istniejących rekordów encji Person. Podobnie jak w przypadku metody create(), użyjemy formularza Symfony do obsługi danych.

W metodzie edit() będziemy potrzebować parametru id, aby zidentyfikować, który rekord encji Person ma zostać edytowany. Oto jak powinien wyglądać kod metody edit():

#[Route('/person/{id}/edit', name: 'app_person_edit', requirements: ['id' => '\d+'])]
public function edit(Request $request, EntityManagerInterface $entityManager, Person $person): Response
{
    $form = $this->createFormBuilder($person)
        ->add('name', TextType::class)
        ->add('surname', TextType::class)
        ->add('age', IntegerType::class)
        ->add('email', EmailType::class)
        ->add('save', SubmitType::class, ['label' => 'Update Person'])
        ->getForm();

    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $entityManager->flush();

        return $this->redirectToRoute('app_person');
    }

    return $this->render('person/edit.html.twig', [
        'form' => $form->createView(),
    ]);
}

W powyższej metodzie edit():

  1. Przekazujemy parametr id w trasie, aby zidentyfikować edytowany rekord.
  2. Symfony automatycznie konwertuje id na obiekt encji Person dzięki argumentowi Person $person.
  3. Tworzymy formularz za pomocą metody createFormBuilder() i dodajemy odpowiednie pola formularza (name, surname, age, email oraz przycisk zapisu).
  4. Obsługujemy żądanie formularza, sprawdzając czy został przesłany i czy jest poprawny.
  5. Jeśli formularz jest przesłany i poprawny, aktualizujemy rekord w bazie danych poprzez wywołanie metody flush() na menedżerze encji.
  6. Po zaktualizowaniu rekordu, przekierowujemy użytkownika do listy osób (app_person).
  7. Renderujemy szablon person/edit.html.twig, przekazując mu obiekt formularza.

Wreszcie, dodajmy metodę delete(), która pozwoli na usuwanie rekordów encji Person. W tym przypadku nie będziemy potrzebować formularza Symfony. Oto jak powinien wyglądać kod metody delete():

#[Route('/person/{id}/delete', name: 'app_person_delete', requirements: ['id' => '\d+'], methods: ['POST'])]
public function delete(EntityManagerInterface $entityManager, Person $person): Response
{
    $entityManager->remove($person);
    $entityManager->flush();

    return $this->redirectToRoute('app_person');
}

W powyższej metodzie delete():

  1. Przekazujemy parametr id w trasie, aby zidentyfikować rekord do usunięcia.
  2. Symfony automatycznie konwertuje id na obiekt encji Person dzięki argumentowi Person $person.
  3. Wymagamy, aby żądanie było typu POST przez dodanie methods: [’POST’] do dekoratora trasy.
  4. Usuwamy rekord z menedżera encji za pomocą metody $entityManager->remove($person) i zatwierdzamy zmiany za pomocą $entityManager->flush().
  5. Po usunięciu rekordu przekierowujemy użytkownika z powrotem do listy osób za pomocą $this->redirectToRoute(’app_person’).

Dodane powyżej metody wykożystują następujące zależności:

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

Należy to dodać na początku pliku.

Dodanie widoków

Teraz skupimy się na tworzeniu widoków dla metody index() w naszym kontrolerze PersonController. Widoki to fragmenty kodu odpowiedzialne za prezentowanie danych na stronie internetowej. W Symfony, do tworzenia widoków używamy systemu szablonów o nazwie Twig. Szablony Twig to pliki tekstowe, które definiują strukturę HTML oraz zawierają w sobie specjalne znaczniki i funkcje, które pozwalają na dynamiczne generowanie zawartości strony. W przypadku metody index() naszym celem jest wyświetlenie listy osób.

Zacznijmy od utworzenia nowego pliku szablonu Twig dla metody index(). W katalogu templates, utwórz podkatalog person (jeśli jeszcze nie istnieje) i w nim nowy plik o nazwie index.html.twig. Będziemy w tym pliku definiować strukturę HTML oraz korzystać z funkcji dostarczanych przez Twig, aby wyświetlić listę osób.

W pliku index.html.twig, rozpocznij od rozszerzenia szablonu base.html.twig, który dostarcza podstawową strukturę strony:

{% extends 'base.html.twig' %}

{% block title %}Lista osób{% endblock %}

Teraz dodajemy blok body, w którym umieścimy zawartość specyficzną dla tej strony:

{% block body %}
    <h1>Lista osób</h1>

    <table class="table">
        <thead>
            <tr>
                <th>Imię</th>
                <th>Nazwisko</th>
                <th>Wiek</th>
                <th>Email</th>
                <th>Akcje</th>
            </tr>
        </thead>
        <tbody>
            {% for person in people %}
                <tr>
                    <td>{{ person.name }}</td>
                    <td>{{ person.surname }}</td>
                    <td>{{ person.age }}</td>
                    <td>{{ person.email }}</td>
                    <td>
                        <a href="{{ path('app_person_edit', {'id': person.id}) }}">Edytuj</a>
                        <form method="post" action="{{ path('app_person_delete', {'id': person.id}) }}" style="display: inline;">
                            <input type="submit" value="Usuń" onclick="return confirm('Czy na pewno chcesz usunąć?')">
                        </form>
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

    <a href="{{ path('app_person_create') }}" class="btn btn-primary">Dodaj nową osobę</a>
{% endblock %}

W powyższym szablonie:

  1. Rozszerzamy base.html.twig, aby korzystać z jego podstawowej struktury HTML.
  2. Nadpisujemy blok title, aby ustawić tytuł strony na „Lista osób”.
  3. Definiujemy blok body, w którym umieszczamy tabelę z listą osób oraz przycisk do dodawania nowych osób.
  4. Iterujemy po liście osób przekazanej z kontrolera, korzystając z pętli {% for person in people %}, a następnie wypełniamy wiersze tabeli danymi z obiektów Person.
  5. Dodajemy linki do edycji i usuwania obiektów Person. Aby usunąć rekord, korzystamy z formularza i przekierowujemy żądanie POST do akcji app_person_delete.

Stwórzmy teraz widok dla metody create, który będzie wyświetlał formularz do dodawania nowej osoby. Utwórz plik create.html.twig w katalogu templates/person i wklej poniższy kod:

{% extends 'base.html.twig' %}

{% block title %}Dodaj osobę{% endblock %}

{% block body %}
    <div class="container">
        <h1 class="my-3">Dodaj osobę</h1>
        
        <form action="{{ path('app_person_create') }}" method="post">
            <div class="mb-3">
                <label for="name" class="form-label">Imię</label>
                <input type="text" class="form-control" id="name" name="name" required>
            </div>
            <div class="mb-3">
                <label for="surname" class="form-label">Nazwisko</label>
                <input type="text" class="form-control" id="surname" name="surname" required>
            </div>
            <div class="mb-3">
                <label for="age" class="form-label">Wiek</label>
                <input type="number" class="form-control" id="age" name="age" min="1" required>
            </div>
            <div class="mb-3">
                <label for="email" class="form-label">Email</label>
                <input type="email" class="form-control" id="email" name="email" required>
            </div>
            <button type="submit" class="btn btn-primary">Dodaj</button>
        </form>
    </div>
{% endblock %}

W szablonie create.html.twig:

  1. Rozszerzamy szablon base.html.twig, aby korzystać z jego struktury i stylów, używając dyrektywy {% extends 'base.html.twig' %}.
  2. Ustalamy tytuł strony na „Dodaj osobę” za pomocą bloku {% block title %}.
  3. W bloku {% block body %}, definiujemy zawartość strony. Tworzymy kontener z formularzem, który pozwala użytkownikowi wprowadzić dane nowej osoby.
  4. W formularzu, używamy funkcji formularza Symfony (form_start(), form_row() i form_end()) do generowania odpowiednich pól na podstawie formularza utworzonego w kontrolerze.
  5. Dla każdego pola formularza, używamy funkcji form_row(), aby wygenerować odpowiednie pole z odpowiednimi atrybutami, takimi jak label, class czy min. W ten sposób mamy większą kontrolę nad wyglądem i zachowaniem pól formularza.
  6. Na końcu, dodajemy przycisk do wysłania formularza za pomocą funkcji form_row(), ustawiając jego etykietę na „Dodaj” i dodając klasę btn btn-primary, aby przycisk był odpowiednio sformatowany zgodnie z Bootstrapem.

W wyniku tych kroków, formularz jest teraz przesyłany do akcji kontrolera app_person_create po kliknięciu przycisku „Dodaj”, a nowa osoba zostaje zapisana w bazie danych.

Ostatni widok jaki tworzymy to edit.html.twig, który odpowiada za edycję istniejącego rekordu osoby.

Kod widoku edit.html.twig może wyglądać następująco:

{% extends 'base.html.twig' %}

{% block title %}Edytuj osobę{% endblock %}

{% block body %}
    <div class="container">
        <h1 class="my-3">Edytuj osobę</h1>

        {{ form_start(form, {'action': path('app_person_edit', {'id': person.id})}) }}
        {{ form_row(form.name, { 'label': 'Imię', 'attr': {'class': 'form-control'} }) }}
        {{ form_row(form.surname, { 'label': 'Nazwisko', 'attr': {'class': 'form-control'} }) }}
        {{ form_row(form.age, { 'label': 'Wiek', 'attr': {'class': 'form-control', 'min': 1} }) }}
        {{ form_row(form.email, { 'label': 'Email', 'attr': {'class': 'form-control'} }) }}
        {{ form_row(form.save, { 'label': 'Edytuj', 'attr': {'class': 'btn btn-primary'} }) }}
        {{ form_end(form) }}
    </div>
{% endblock %}

W szablonie edit.html.twig:

  1. Rozszerzamy szablon base.html.twig, aby korzystać z jego struktury i stylów, używając dyrektywy {% extends 'base.html.twig' %}.
  2. Ustalamy tytuł strony na „Edytuj osobę” za pomocą bloku {% block title %}.
  3. W bloku {% block body %}, definiujemy zawartość strony. Tworzymy kontener z formularzem, który umożliwia użytkownikowi edycję danych istniejącej osoby.
  4. W formularzu, używamy funkcji formularza Symfony (form_start(), form_row() i form_end()) do generowania odpowiednich pól na podstawie formularza utworzonego w kontrolerze.
  5. Funkcja form_start() przyjmuje jako opcjonalny parametr słownik z atrybutami dla elementu <form>. W tym przypadku, używamy parametru action, aby ustawić adres URL, na który formularz ma być wysłany, na podstawie ścieżki app_person_edit, która wymaga podania identyfikatora osoby (parametr id).
  6. Dla każdego pola formularza, używamy funkcji form_row(), aby wygenerować odpowiednie pole z odpowiednimi atrybutami, takimi jak label, class czy min. W ten sposób mamy większą kontrolę nad wyglądem i zachowaniem pól formularza.
  7. Na końcu, dodajemy przycisk do zapisania zmian w formularzu za pomocą funkcji form_row(), ustawiając jego etykietę na „Edytuj” i dodając klasę btn btn-primary, aby przycisk był odpowiednio sformatowany zgodnie z Bootstrapem.

W wyniku tych kroków, formularz jest teraz przesyłany do akcji kontrolera app_person_edit po kliknięciu przycisku „Edytuj”, a istniejąca osoba zostaje zaktualizowana w bazie danych.

Szablonu dla metody delete() nie musimy tworzyć, gdyż podczas kasowania rekordu nie jest wyświetlany żaden widok, tylko przekierowuje nas do strony wyświetlającej wszystkie wpisy. Warto pamiętać, że przy kasowaniu rekordów z bazy danych, istnieje ryzyko usunięcia danych w wyniku przypadkowego kliknięcia przycisku „Usuń”. Aby zabezpieczyć się przed takim przypadkiem, dodaliśmy potwierdzenie przed usunięciem rekordu.

Podsumowanie

W tym artykule zaprezentowaliśmy jak zaimplementować podstawowe operacje CRUD (Create, Read, Update, Delete) w aplikacji Symfony, wykorzystując Doctrine ORM i Bootstrap 5.3 do stylizacji interfejsu użytkownika.

Nauczyliśmy się, jak wyświetlać listę rekordów, dodawać, edytować i usuwać rekordy za pomocą formularzy Symfony, jak również jak zainstalować i skonfigurować Bootstrap 5.3 w projekcie Symfony.

Dzięki zastosowaniu szablonów i komponentów Symfony, nasza aplikacja jest teraz bardziej modułowa i łatwiejsza w utrzymaniu, co pozwala nam skupić się na biznesowej logice naszej aplikacji, zamiast na tworzeniu interfejsu użytkownika od zera.

Kod z tego i poprzedniego artykułu znajduje się na GitHubie.

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *


Scroll to Top