Symfony 4: a CRUD tutorial, second edition

This tutorial follows another guide that I made, always about Symfony, but this time we’re talking about Symfony 4.

As the framework evolves into new releases there might be some changes in methods, classes and tools. They may become deprecated or obsolete and new tools may be added, although some kind of retrocompatibility may be assured.

Symfony the web framework

If you want to know more about that, you can visit Symfony’s roadmap and discover how it will evolve and how long the various versions will be maintained.

As a prerequisite for this tutorial I advice you to install Xampp as environment for the application and a good editor such as Visual Studio Code.

The first thing to do after installing Xampp is to create a Virtual Host and edit your hosts file. Let’s suppose we’re on Windows, but the same may apply on Linux and Mac, with some slight changes.

Let’s edit our C:\xampp\apache\conf\extra\httpd-vhosts.conf and add the following lines:

<VirtualHost blog.local:80>
    ##ServerAdmin webmaster@dummy-host2.example.com
    DocumentRoot "C:\xampp\htdocs\blog\public"
    ServerName blog.local
	
    <Directory "C:\xampp\htdocs\blog\public">
        AllowOverride None
        Require all granted
        Allow from All

        <IfModule mod_rewrite.c>
            Options -MultiViews
            RewriteEngine On
            RewriteCond %{REQUEST_FILENAME} !-f
            RewriteRule ^(.*)$ index.php [QSA,L]
        </IfModule>
    </Directory>	
	
    ##ErrorLog "logs/dummy-host2.example.com-error.log"
    ##CustomLog "logs/dummy-host2.example.com-access.log" common
</VirtualHost>

And open C:\Windows\System32\drivers\etc\hosts inside your Notepad, running it as administrator and pasting the following line:

127.0.0.1	blog.local

Next we can setup our project and install all the necessary dependencies, do it like this at your shell:

C:\Users\benedict>cd C:\xampp\htdocs
C:\xampp\htdocs>composer create-project symfony/skeleton blog

C:\xampp\htdocs>cd blog

C:\xampp\htdocs\blog>composer require symfony/orm-pack
C:\xampp\htdocs\blog>composer require --dev symfony/maker-bundle
C:\xampp\htdocs\blog>composer require symfony/twig-bundle
C:\xampp\htdocs\blog>composer require symfony/translation
C:\xampp\htdocs\blog>composer require symfony/form

Take a look at the output of the create-project command and make sure you’ve installed the 4.3.x version of the framework. We can now run our Xampp Apache and see the Symfony welcome page opening your browser at http://blog.local/.

For our purposes we have to edit the .env file at the root of your project and modify the subsequent line of code, to make it look like this:

DATABASE_URL="mysql://root:yourpassword@127.0.0.1:3306/blog"

We can now create our database, which will be called blog:

C:\xampp\htdocs\blog>php bin/console doctrine:database:create

Let’s create an entity, with four fields title, author, body, url. They are all of the string type, they’re relative lenghts are 100, 50, 1000, 200 and the last field is nullable. Type those values when asked by the following command:

C:\xampp\htdocs\blog>php bin/console make:entity

and it will generate:

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
 */
class Article
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=100)
     */
    private $title;

    /**
     * @ORM\Column(type="string", length=50)
     */
    private $author;

    /**
     * @ORM\Column(type="string", length=1000)
     */
    private $body;

    /**
     * @ORM\Column(type="string", length=200, nullable=true)
     */
    private $url;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getTitle(): ?string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->title = $title;

        return $this;
    }

    public function getAuthor(): ?string
    {
        return $this->author;
    }

    public function setAuthor(string $author): self
    {
        $this->author = $author;

        return $this;
    }

    public function getBody(): ?string
    {
        return $this->body;
    }

    public function setBody(string $body): self
    {
        $this->body = $body;

        return $this;
    }

    public function getUrl(): ?string
    {
        return $this->url;
    }

    public function setUrl(?string $url): self
    {
        $this->url = $url;

        return $this;
    }
}

As you can see it is also generating all the getters and settes. And now we’re going to make a database migration and run it, the result is a database table with all the entity fields:

C:\xampp\htdocs\blog>php bin/console make:migration
C:\xampp\htdocs\blog>php bin/console doctrine:migrations:migrate

The next step is to create a new controller for our application:

C:\xampp\htdocs\blog>php bin/console make:controller ArticleController

And again, we have to create an ArticleType class, to make up our form. Let’s do it with the following command:

C:\xampp\htdocs\blog>php bin/console make:form

Let’s see what we created (our ArticleController.php and ArticleType.php) and how to modify those files to make the application work, you’ll see the complete code here:

namespace App\Controller;

use App\Entity\Article;
use App\Form\ArticleType;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;

class ArticleController extends AbstractController
{
    public function createArticle(Request $request)
    {
        $article = new Article();
        $form = $this->createForm(ArticleType::class, $article);

        $form->handleRequest($request);

        if ($form->isSubmitted()) {

            $article = $form->getData();

            $em = $this->getDoctrine()->getManager();
            $em->persist($article);
            $em->flush();

            return $this->redirect('/view-article/' . $article->getId());

        }

        return $this->render(
            'edit.html.twig',
            array('form' => $form->createView())
        );

    }

    public function viewArticle($id)
    {
        $article = $this->getDoctrine()
            ->getRepository('App\Entity\Article')
            ->find($id);

        if (!$article) {
            throw $this->createNotFoundException(
                'There are no articles with the following id: ' . $id
            );
        }

        return $this->render(
            'view.html.twig',
            array('article' => $article)
        );
    }

    public function showArticles()
    {
        $articles = $this->getDoctrine()
            ->getRepository('App\Entity\Article')
            ->findAll();

        return $this->render(
            'show.html.twig',
            array('articles' => $articles)
        );
    }

    public function deleteArticle($id)
    {
        $em = $this->getDoctrine()->getManager();
        $article = $em->getRepository('App\Entity\Article')->find($id);

        if (!$article) {
            throw $this->createNotFoundException(
                'There are no articles with the following id: ' . $id
            );
        }

        $em->remove($article);
        $em->flush();

        return $this->redirect('/show-articles');
    }

    public function updateArticle(Request $request, $id)
    {
        $em = $this->getDoctrine()->getManager();
        $article = $em->getRepository('App\Entity\Article')->find($id);

        if (!$article) {
            throw $this->createNotFoundException(
                'There are no articles with the following id: ' . $id
            );
        }

        $form = $this->createForm(ArticleType::class, $article);

        $form->handleRequest($request);

        if ($form->isSubmitted()) {
            $article = $form->getData();
            $em->flush();
            return $this->redirect('/view-article/' . $id);
        }

        return $this->render(
            'edit.html.twig',
            array('form' => $form->createView())
        );
    }
}

and then:

namespace App\Form;

use App\Entity\Article;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class ArticleType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('title', TextType::class)
            ->add('author', TextType::class)
            ->add('body', TextareaType::class)
            ->add('url', TextType::class,
                    ['required' => false, 'attr' => ['placeholder' => 'www.example.com']])
            ->add('save', SubmitType::class,
                    ['label' => 'Save Article'])
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => Article::class,
        ]);
    }
}

One thing I wanted to make different from the previous tutorial is the way I’m handling routes. This time we will use config/routes.yaml instead of annotations, you will find them missing in the controller class.

create:
  path: /create-article
  controller: App\Controller\ArticleController::createArticle

view:
  path: /view-article/{id}
  controller: App\Controller\ArticleController::viewArticle

show:
  path: /show-articles
  controller: App\Controller\ArticleController::showArticles

delete:
  path: /delete-article/{id}
  controller: App\Controller\ArticleController::deleteArticle

update:
  path: /update-article/{id}
  controller: App\Controller\ArticleController::updateArticle

The last thing to remember is our Twig templates, you will find them on this repo, I didn’t include them here for brevity.

Another difference you may have noticed from the previous tutorial is the use of a separate class for building our form. That allows us to remove clutter from the controller class, making it more readable. And we will be able to reuse the form class code around all the application, should we need to.

The rest of the code is similar and reflects also a different directory structure of Symfony 4 compared to the 3rd version.

And to wrap up, you learned how to build a minimal CRUD Symfony 4 web application. Contact me, should you have any question. Otherwise please follow my GitHub profile for the code of this tutorial and all the others. See you next time.

Did you like this post? Please, share it on your preferred social networks or comment here below, thank you!

10 thoughts on “Symfony 4: a CRUD tutorial, second edition

  1. I’m sorry,
    I don’t like this tutorial.
    It does not go deep into the matter and I don’t find it particularly interesting.
    One thing though, you talk about many different technologies: seems like you have real passion… try to improve yourself!

  2. My apologies,
    my tutorials are meant for beginners,
    and there is too little room for deepening here.
    I’m only trying to tackle single aspects of languages and frameworks, without going too deep.
    I chose a not too general nor too specific format for my tutorials, and thank you for your advice, I’ll be thinking of new topics and new ways of presenting them.

    Any further advice from you and from other readers is welcome, and I’ll put it into practice.
    Thanks again

  3. I’ll speak to you bluntly:
    I’m not particularly fond of internet tutorials, but this one is really well made, I don’t agree with any point that made Alexander.
    Keep improving your stuff and I’ll be surely returning to visit your blog.
    Thanks

  4. Hello Judy,
    thank you too for your support. I’m always eager to help people understand software development and I welcome them to visit this website.
    See you

  5. Thanks to the @author, I’ve found this post useful. Hope to return soon & find more stuff like this to benefit 🙂

  6. Thanks Ellie,
    It’s for people like you it is rewarding and worth it to blog on the internet.
    Keep in touch,

    Mirko

  7. Usually I don’t read article on blogs, but I wish to say that this write-up very compelled me to take a look at and I did so! Your writing style amazed me. Thanks, great article.

  8. Hello Connie,
    thanks a lot, technical questions are appreciated as well…

Leave a Reply

Give me your opinion, I will be grateful.