Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Doctrine & the Database in Symfony 4

1:56:34

What you'll be learning

// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "fzaninotto/faker": "^1.7", // v1.7.1
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.4.0
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}
// composer.json
{
    "require": {
        "php": "^7.1.3",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "knplabs/knp-markdown-bundle": "^1.7", // 1.7.0
        "knplabs/knp-time-bundle": "^1.8", // 1.8.0
        "nexylan/slack-bundle": "^2.0,<2.2.0", // v2.0.0
        "php-http/guzzle6-adapter": "^1.1", // v1.1.1
        "sensio/framework-extra-bundle": "^5.1", // v5.1.4
        "stof/doctrine-extensions-bundle": "^1.3", // v1.3.0
        "symfony/asset": "^4.0", // v4.0.4
        "symfony/console": "^4.0", // v4.0.14
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/framework-bundle": "^4.0", // v4.0.14
        "symfony/lts": "^4@dev", // dev-master
        "symfony/orm-pack": "^1.0", // v1.0.6
        "symfony/twig-bundle": "^4.0", // v4.0.4
        "symfony/web-server-bundle": "^4.0", // v4.0.4
        "symfony/yaml": "^4.0" // v4.0.14
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.0.2
        "easycorp/easy-log-handler": "^1.0.2", // v1.0.4
        "fzaninotto/faker": "^1.7", // v1.7.1
        "symfony/debug-bundle": "^3.3|^4.0", // v4.0.4
        "symfony/dotenv": "^4.0", // v4.0.14
        "symfony/maker-bundle": "^1.0", // v1.4.0
        "symfony/monolog-bundle": "^3.0", // v3.1.2
        "symfony/phpunit-bridge": "^3.3|^4.0", // v4.0.4
        "symfony/profiler-pack": "^1.0", // v1.0.3
        "symfony/var-dumper": "^3.3|^4.0" // v4.0.4
    }
}

Two episodes down! Booya! And we are super ready to put our new skills to the test! It's finally time to make up app come alive by using Doctrine to connect to a database.

Doctrine is an amazing ORM that works great with Symfony and is super powerful. It also has a reputation for being hard to learn and for making you write a lot of code. But that's changing! Thanks to some recent improvements and Symfony Flex, working with Doctrine has never been easier or more rewarding. So, let's get started!

  • Creating (and updating) Entities with make:entity
  • Generating & using migrations
  • Inserting new data
  • Fetching & Querying for data
  • Doctrine Repositories
  • Custom queries and the query builder
  • Fixtures (Dummy data) using Faker
  • Relationships & Associations

Your Guides

Ryan Weaver

Buy Access

Join the Conversation?

28
Login or Register to join the conversation
Paweł C. Avatar
Paweł C. Avatar Paweł C. | posted 4 years ago

Hi, have You maybe wrote something about: How to organize Symfony project without ORM (Doctrine)? My company use Symfony without orm (with plain sql) and as a Symfony developer I have problem how to keep controller slim and clean. I mean with Doctrine I have Entities that have relations etc. Should I still map sql tables as classes? How about controller who receive request with 20 parameters from form, and whose job is to persist it, and by persist I mean sql procedure including 5 tables. Should I map tables as classes and in controller full these objects with request params? That would be mess. And for what benefits, if my job is to pass parameters to procedure. My general question is: How to organize Symfony project without ORM (Doctrine) to keep controllers clean and slim?

2 Reply

Hey Paweł C.!

Fascinating question! Let me answer with various points:

1) Does it make sense for you to map each table to a database class?

Maybe. In some projects, it's quite common to be consistent working with the data for one table. Like, "Hey! Give me the data from the product" table. But, in other projects - especially if you have a complex or legacy database - you are more commonly grabbing data from many different tables and using them all at once. In that context, it's probably not that helpful.

2) How to organize Symfony project without ORM (Doctrine) to keep controllers clean and slim?

In general, the important thing is this: create "model" classes (by this I mean a class that holds data like a Doctrine entity, but is just a normal PHP class) that are meaningful to your "business logic". If this matches your database table structure (e.g. a "product" is a meaningful concept in my code and I also have a "product" table), great! If it does not match your table structure, no big deal. Here's a nice way to think about it: if you aren't modeling your database to PHP classes currently, then you are probably passing around a lot of associative arrays of data. For your application, those arrays are meaningful. And if you are commonly passing around an array with more or less the same data in it, this is probably an "important concept" in your app, and you could convert it into a class. For example, suppose you query 5 database tables at once to retrieve a bunch of "product shipping information". Cool! It might be helpful to create a ProductShippingInfo class and put the data onto that class instead. Then, instead of passing that array around, you are now passing a class around.

So, my general advice is this:

A) Find patterns of data that you fetch from the database. And, if it's helpful, start returning those things as objects. And, the same is true in the other direction: if accept 20 parameters from a form, consider creating a class and putting that data into a new instance of the class. And then passing it around to your system.

B) And apart from creating "model" classes, create a rich service layer. Basically, each time you need to "do" something, put that code into a service class. I also (outside of Doctrine) still follow the Doctrine "repository" method where I have a class that holds all of the queries for each table (so 10 tables == 10 repository classes). This is easy to do even outside of Doctrine. Though, again, if your more commonly querying from 5 tables, then maybe you follow this idea, but it doesn't really match your database structure.

So, here is a "pretend" before and after. Before:


$name = $request->request->get('product_name');
$price = $request->request->get('price');
// 10 other fields

$pdo->query('INSERT INTO ...'); // then you build the query to pass these 10 things

After:


$name = $request->request->get('product_name');
$price = $request->request->get('price');
// .. 10 other fields

$product = new Product($name, $price, ...);

// this service class has a nicely-named method and receives a Product class
// it would take care of the ugly query inside
$productManager->createNewProduct($product;

It's not a science, and there are people that are much better at this than I am. But, generally, turn "data" into classes & objects. And put all of your "work" into service classes.

I hope this helps!

Cheers!

1 Reply
Paweł C. Avatar

Damn good and exhausting answer. Thank You!

1 Reply
Sławomir G. Avatar
Sławomir G. Avatar Sławomir G. | posted 5 years ago

function createMany is pretty nice, but what about Entities which require arguments in constructor?

2 Reply

Hey Sławomir G.!

GREAT question. I was having that same thought after writing it this way :). If I were to recreate this function to be more flexible, I would probably just force the user to do this step. So, it would look more like this:


$this->createMany(10, function($i) {
    $article = new Article();
    // ...

    return $article;
});

Then, the createMany() function would look at the class of the object that was returned to know how to store it as a reference :). I think this is just as easy, but more flexible.

Cheers!

3 Reply
Default user avatar

not related on this tutorial but can i ask this question?

i follow the "little" intro about doctrine on this https://symfony.com/doc/cur...

want i want to ask its when ever i acces http://localhost:8000/product the output keep increse by 1 its like when i open it its keep increse like
Saved new product with id 18
when i refresh
Saved new product with id 19
with same name product keyboard

1 Reply

Hey Lavin

How are you handlig that route? (/product )
It looks like you are creating and saving a new product by only accessing to that route

Cheers!

Reply
Default user avatar
Default user avatar Lavin Bassam | MolloKhan | posted 4 years ago

thank for answer finally found it xD but thank for answering :)

can i ask another question?

in this chapter https://symfony.com/doc/2.8...
part updating the object i create the code like this

/**
* @Route("/product/edit/{productId}")
*/
public function updateAction($productId)
{
$entityManager = $this->getDoctrine()->getManager();
$product = $entityManager->getRepository(Product::class)->find($productId);

if (!$product) {
throw $this->createNotFoundException(
'No product found for id '.$productId
);
}

$product->setName('New product name!');
$entityManager->flush();

return $this->redirectToRoute('product_show', [
'id' => $productId->getId()
]);

//return $this->redirect($this->generateUrl('product_show', array(
// 'product' => $product,
//)));
}

but i got the output like this Call to a member function getId() on string

what should i do to fix it so i can get proper output thank you

Reply

Oh, that's because you are calling "getId()" on $productId variable instead of $product

Reply
Tomasz N. Avatar
Tomasz N. Avatar Tomasz N. | posted 4 years ago | edited

Hi Ryan.
You do very nice job :) I have a question ( sorry for my weak english ) about Doctrine and ( Ouch ) MsSql :/ sorry :) system must work on MsSql.
On my computer i have MsSql express 2017 + Win10 + php 7.3.2

<b>When i use parameter "schema" in entity declaration everything gonna crash :(</b>
I need to set my tables in different schema cause on one database works three programs. We control permisions by the schema.

When i declare entity like this:
`

namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**

  • @ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
  • @ORM\Table(name="tbl_article")
    */
    class Article
    {
    ...
    `

Every things fine.
In CMD i do "php bin/console doctrine:schema:update --force" and then symfony create for me table in my database dbo.tbl_article. I use doctrine:schema:update, cause its simplest for test for me ( --dump-sql).
Next i add some field into entity, again do "php bin/console doctrine:schema:update" first "--dump-sql" and i see "ALTER". Its ok. But when i declare entity witch "schema" paramenter like this:
`

namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**

  • @ORM\Entity(repositoryClass="App\Repository\ArticleRepository")
  • @ORM\Table(name="tbl_article", schema="B2B")
    */
    class Article
    {
    ...
    `

Doctrine always wants to "CREATE" table. On first time its ok, doctrine create "B2B.tbl_article" ( see in management studio ) in my database but when i add some field in entity and then i do "php bin/console doctrine:schema:update ..." for update database then i see error ( table exist ) cause doctrine trying to do CREATE TABLE instead ALTER TABLE. For test i set schema="dbo" and again i see error. Always when i use schema parameter doctrine always try to do CREATE instead ALTER. Why? What i do wrong? Maybe a need to set something in doctrine.yaml ?
in .env i have this:
DATABASE_URL=mssql://login:password@127.0.0.1:1433/databasename
Ofcourse login, password, databasename is proper.
in config/packages/doctrine.yaml driver is set to "pdo_sqlsrv", drivers in PHP.ini is :
extension=php_pdo_sqlsrv_73_ts_x64

Could you help me please :)

Reply

Hey Tomasz N.!

So... this is likely a bug in the MsSQL driver inside Doctrine. I don't know for sure, but I had a Doctrine guy look at this, and, at first glance, what you're trying to do seems reasonable and we don't see any problems. It's probable that when you run doctrine:schema:update, it's looking at the wrong "schema", seeing 0 tables, and so, then doing a CREATE TABLE. The only work around I know of is basically to fix this "by hand". I would use the migrations system (so, run make:migration), then manually modify the migration file to change it to the ALTER TABLE that's needed.

Sorry I can't be of more help! The MsSQL driver doesn't get as much attention as the others.

Cheers!

Reply
Tomasz N. Avatar

Thanks Ryan. I suspected, it's doctrine malfunction. I will do like you say, maybe someday doctrine guys will correct this.

Reply
Paweł C. Avatar
Paweł C. Avatar Paweł C. | posted 4 years ago

this is very nice.

Reply

Hey Pawel,

We're sorry for this spam! Our Disqus account receiving many spam messages lately :/ But good news is that most of them are recognized by Disqus as spam automatically.

Cheers!

Reply
Mohamed K. Avatar
Mohamed K. Avatar Mohamed K. | posted 4 years ago

While trying to create the database using ./bin/console doctrine:database:create i get a couple of errors:
The server requested a authentication method unknown to the client [caching_sha2_password

Reply

Yo Mohamed K.!

Wow! That's a new error for me! Oh boy, it looks like MySQL 8.0.4 my have changed the default way that you send your password to them :/. Here is some info on how people are fixing / working around this:

* https://github.com/laradock...
* https://stackoverflow.com/q...

Also, this was apparently fixed in PHP 7.2.4 - you can read a bit about that here: http://php.net/manual/en/re...

Hopefully one of these will give you an easy fix! It's too early in this tutorial to hit a big snag - you haven't had any fun yet!

Cheers!

Reply
Jimmy S. Avatar
Jimmy S. Avatar Jimmy S. | posted 5 years ago | edited

there I just signed up and loving your tutorials

Reply

Hey Jimmy S.!

Ah, so happy to hear it! If you have any questions, thoughts or suggestions, definitely let us know down in the comments (or shoot us an email).

Cheers!

Reply
odds Avatar

Why is this course not listed in the Symfony 4 track? I have a hard time finding it (or I'm just lazy)...

Reply

Hey odds

Haha, you "may" be lazy but it's our fault as well. For some reason it wasn't added to the track, but I just did it!
Thanks for reporting it :)

Have a nice day

Reply
Default user avatar
Default user avatar Gompali | posted 5 years ago

I thought there was already a "Doctrine & the Database Tutorial". Is it an update ?

Reply

Hey Prout,

Yes, we have the one but in Symfony 3 track. This course was recorded in a Symfony 4 way using Flex and our new "The Spacebar" Symfony 4 project.

Cheers!

Reply
Nizar Avatar

hi,
how can i use Multiple Entity Managers and mutiple databases

Reply

Hey Nizar!

You can read about all of that here: https://symfony.com/doc/current/doctrine/multiple_entity_managers.html. The configuration in that article will need to be a bit different to support environment variables, but hopefully it will get you started. For autowiring all the entity managers, I would use a bind rule (https://knpuniversity.com/screencast/symfony-fundamentals/services-config-bind) to bind to argument names - like $adminEntityManager if you had an em called "admin".

Cheers!

Reply
Default user avatar

Hi guys,
how can i import an existing database in Symfony 4?
In Symfony3 you can do:

php bin/console doctrine:mapping:import --force AppBundle xml
php bin/console doctrine:generate:entities AppBundle
php bin/console doctrine:mapping:convert annotation ./src

but those commands don't work in Symfony4.

Thanks.

Reply

Hey Mauro!

Yea, these commands are still being updated to work with Symfony 4. The big issue is actually pretty simple: you don't have a bundle in Symfony 4 :). Here are some more details: https://github.com/doctrine....

So, as you can see there, right now, there are some workarounds, like temporarily creating a bundle. But pretty soon, I hope it will be updated to work without a bundle.

Cheers!

Reply
Peter-K Avatar
Peter-K Avatar Peter-K | posted 5 years ago

When will be this course available? Cant wait

-1 Reply

Hey Peter K.!

This is next in the Symfony series - and I want to get it out soon. But, it will be at least several weeks, for two reasons. First, we'll release the Webpack Encore & React tutorials first. And second, there's a pull request on MakerBundle that we really *need* for this tutorial. So, it needs to be merged and released still. But I'll try to get it all done as quickly as possible :).

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

userVoice