Symfony 3: Doctrine & the Database
Unlock the full potential of Doctrine in Symfony 3, learning entity creation, migrations management, and custom queries creation.
About this course
With already two courses checked off, you are primed and ready to put Symfony to work! And that's what this course is all about: making things more interesting by talking to the database with Doctrine.
Doctrine has a reputation for being tough to learn, and that's unfortunate, because it's an amazing tool. In this tutorial, we'll get you rolling with Doctrine: learn how to use it, how to get hard work done easily and how to avoid the common pitfalls that can make Doctrine hard:
- Creating entity classes
- Managing migrations
- Saving new data
- Querying for data
- Custom repositories
- Custom queries and the query builder
- Fixtures (Dummy data) using Faker
... and (like always) a lot more goodies are mixed in.
Next courses in the Symfony 3: Starting in Symfony 3 section of the Symfony 3 Track!
85 Comments
Hey Paweł C.!
If you need to write raw SQL, I always say, just do it instead of using the Query Builder :). But yea, you have the special problem where the EntityType is *forcing* you to use the QueryBuilder. So, your best option is to
instead of specifying the query_builder option, just query for the entities that you need with your custom query and pass them directly to the choices option: https://symfony.com/doc/cur.... You'll need to dependency inject the entity manager or your repository into your form class to make this query. Also, the tricky part is that, Symfony *does* need the entity objects in order for the EntityType to work. So, you'll need to write your custom query to only select the id field, then make another, normal query where you return all of the actual entity objects that match that array of ids.
If you really want to completely avoid querying for entities, then don't use the EntityType, just use the ChoiceType. But then, you'll need to use a data transformer to ultimately transform the submitted value back into an entity - https://symfony.com/doc/cur.... So.... using EntityType is easier :).
Cheers!
I cant find nowhere one thing, working correctly, its weird, because it should be so useful for many people. When I call getSQL() on query builder, I get only query with ? marks. I want to get often query with filled paramters in question marks, because I want to run it in my database and debug results. And everytyme I do it, I have to manually insert values in ? places. Have tried functions on stackowerflow which claim doing so but for me they did not replace. Or replaces only some times. I assume somewhere has to be this funcgtiotn because profiler shows those values with replaced question marks. Only problem with profiler is when there are like 10 or more queries, it takes tiem to find which you wanted. I use xdebug and when i go to query builder when it is generated, I want to get sql string with replaced values. And also when I run symfony command, probably there is no way to see in profiler those at all.
Hey Lijana Z.
You can run your commands passing in -vvv
flag to see the exact queries being executed. Give it a try and let me know if it worked
Cheers!
It shows sql queryies but with ? marks and array of parameters after them. I need those ? marks replaced with parameters. I found in stackoverflow one asnwer how to do that but it did not worked with date strings when they were parameters, but I have made a fix, did not test much, but maybe it will work. Then will need to find more convenient way to access it from anywhere.
Hmm, yeah, I think Doctrine can't do much more for you but you may want to enable MySql logging then. It's not recommended, it may slow down your app but I believe you will use it only for developing
https://stackoverflow.com/a...
Cheers!
Hey Igor!
This is the 2nd episode in the Symfony 3 series after knpuniversity.com/screencas..., which we're just finishing now. My hope is to get this episode this month. In the mean time, Doctrine hasn't changed that much, so here are some links that might be useful:
* https://knpuniversity.com/s... (and the chapter after)
* https://knpuniversity.com/s...
* and most of https://knpuniversity.com/s...
I hope that helps!
Hi Ryan,
I am stuck in a situation and hoping that your expertise will push me in right direction.
It is a really simple task, in admin panel i need to give admin an option to add image and add the description, this is working fine but as soon the admin edit's the description and saves it the image name becomes null in database. so it works the first time and then later on, on edit the image name goes missing.
This is the action, in this i am using currentProfileImage
and saving the existing image in case user does not provide the new one while editing so i dont lose the only that is already there, but this does not work.
public function editAction(Request $request, College $college)
{
$em = $this->getDoctrine()->getManager();
$editForm = $this->createForm('AppBundle\Form\CollegeType', $college);
$editForm->handleRequest($request);
$currentProfileImage = $college->getLogo();
if ($editForm->isSubmitted() && $editForm->isValid()) {
if ($editForm->get('logo')->getData() == null) {
// if an image has not been provided
$college->setLogo($college->getLogo());
$em->persist($college);
} else {
$profileImageDir = $this->container->getParameter('kernel.root_dir') . '/../web/uploads';
$file = $college->getLogo();
// Generate a unique name for the file before saving it
$fileName = md5(uniqid() . $file) . '.' . $file->guessExtension();
// Move the file to the directory where profile images are stored
$file->move($profileImageDir, $fileName);
$college->setLogo($fileName);
$em->persist($college);
}
$em->flush();
//display successful message
$this->get('session')->getFlashBag()->add(
'success',
$this->get('translator')->trans('general_success_message')
);
return $this->render('AppBundle:admin/college:edit.html.twig', array(
'college' => $college,
'edit_form' => $editForm->createView(),
));
}
return $this->render('AppBundle:admin/college:edit.html.twig', array(
'college' => $college,
'edit_form' => $editForm->createView(),
));
}
What am i doing wrong here?
Hey Shairyar!
Hmm, I'm not sure. But! To find out, here's what I would do. Suppose that the persisted property on College for the image is called imagePath. First, I'd see if anything in College modifies the imagePath property other than setImagePath(). If anything else *does* modify imagePath, then temporarily comment that out and edit the College - see if the image is lost. Also, throw a new \Exception() in setImagePath() to see if something is calling it on College edit.
My guess is that you might be setting $this->imagePath = null; inside setLogo() or something similar, but I'm not sure :).
Cheers!
Hi,
All i had to do was move '$currentProfileImage = $college->getLogo();' at the beginning of the action and it worked :)
Thanks for your comments and looking into it anyways.
Baig
Hello Ryan
when I create an entity I have this error:
The class 'xxx' was not found in the chain configured namespaces
Do you have any idea to resolve them??
Hi Andrés!
Hmm, that usually means that you are trying to persist and entity (e.g. Genus) - but Doctrine does not think it's an entity. For example, if you mistakenly put `Genus` in a directory called `Model` instead of `Entity`, then Doctrine wouldn't know to read its annotation metadata, and it would look like a normal class, not an entity. This would cause this error. It's a very uncommon error, exception in edge-cases when you're working with third-party libraries that give you entities. In those cases, sometimes you have to add configuration so Doctrine is aware of the extra entity classes :).
I hope that helps!
Hey Sudhir! I'm still aiming for the next few weeks :)
hey guys, I'm having an issue with the migrations here.
/**
* @ORM\Column(type="boolean")
*/
private $isRead;
when I run console doctrine:migrations:diff i get the following error
jl@josefo-d: /var/www/vhosts/project-s3 on master [!?]
$ console doctrine:migrations:diff
[Doctrine\DBAL\Schema\SchemaException]
There is no column with name 'isRead' on table 'contact'.
what could be the issue here?
ok my bad, found the issue, was on the indexes, as im using isRead variable, but the table name is in fact "is_read"
Awesome :) - glad you got it figured out. It's configurable, but out-of-the box with Doctrine in Symfony, property names are "snaked-cased" like this, which I kinda like (but it can surprise at first!)
Cheers @JLChafardet!
@weaverryan my subscription expired, but ill renew it next month, this month im tied down horribly with a laravel project that i gota take care of. (:S didnt want to write in laravel, but the customer's mind is dead set on it, wanted to try to get them to use symfony)
Hey! No worries :) Laravel is not my preference, but it's nice too - you can probably bring back some nice tools/lessons from it back to Symfony.
I look forward to having you back! - I noticed you hadn't commented in awhile ;)
@weaverryan yeah! suddenly i became swamped in work, a company in the netherlands hired some hours (i work as a freelance contractor some hours a week aside from my business, you know, extra coins wont hurt ever, as long as they dont take up sleep time nor weekends [I myself used to work even weekends for the past 18 years, lost a lot of time on that, so now im properly taking care of not just my mental health but also my body's health and my spiritual peace haha]) so got 2 projects going on my 8h/day mon-fri work routine, 3 extra hours a day contracted in the NL for this laravel project, havent had much time to browse or comment! but I take to heart everything I learnt here! I'm still much inexperienced, but im growing with each line of code i write, getting to know symfony better bit by bit.
regarding laravel, well its indeed nice, it has a lot of out of the box features that are really cool, yet it feels way too much microframeworkish, thats just overgrown, it doesnt feel by any means a fully featured framework in and on itself, but as stated a "overgrown" microframework. I may be wrong tho.
Hi there!
I'm having the same problem:
My class:
/**
* Course
*
* @ORM\Table(
* name="course",
* uniqueConstraints={
* @ORM\UniqueConstraint(name="unique_course", columns={"name", "institution_id", "user_id"})
* },
* indexes={
* @ORM\Index(name="IDX_169E6FB9A76ED395", columns={"user_id"})
* }
* )
* @ORM\Entity(repositoryClass="MainBundle\Entity\CourseRepository")
* @UniqueEntity(
* fields={"name", "institution", "user"},
* errorPath="name",
* message="unique.course.name"
* )
* @ORM\HasLifecycleCallbacks
*/
class Course extends AbstractEntity
/**
* @var \MainBundle\Entity\User
*
* @ORM\Column(name="user_id")
* @ORM\ManyToOne(targetEntity="MainBundle\Entity\User", inversedBy="series")
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
Then when I run:
console doctrine:migrations:diff
[Doctrine\DBAL\Schema\SchemaException]
There is no column with name 'user' on table 'course'.
Hey Marcelo,
Could you debug it by running bin/console doctrine:schema:validate
command? And let me know its result. It shows whether you have any invalid mapping.
BTW, you probably use user
column name instead of user_id
in some of your indexes or uniqueConstraints. Please, recheck that all your entities (not only Course entity) have user_id
column name instead of user
inside @ORM\Table() annotation. Actually, you have to check all the entities which have relation to the User entity.
Cheers!
Hello, this is a beginner question :D I've an entity called product, one called event, and one called theme. To make an event, you must select a theme; based on theme, the event will require some products and I need to check if that products are available. I need to write custom queries (like select quantity from product_in_theme and verify if is available in product) but..where I write that code? In repository? If yes, the full code, will be in repository? (For example: check the theme passed with form, select the product from the form, etc). I know it's not the beginner situation, and maybe years writing plain php is giving me the wrong idea, so please, help me :)
Hey there!
Welcome! Yes, it's not exactly a beginner problem, but maybe I can help:
1) The only thing that will live in your repository are queries. From flat PHP, think of it this way: any time that you *used* to literally write a query - e.g. $sql = 'SELECT * FROM ...', followed by $conn->execute($sql) or something similar - put that logic as a new method in a repository. That's the only purpose of a repository: to try to prevent query logic from being all around your project. If you're selecting from multiple tables, just put the new method in the repository that you feel most appropriately fits.
2) Depending on your user flow, your form might be quite complicated for this. If the user is creating an event, then adding a drop-down of all of the themes in the system is simple - just use the EntityType. But, if you need to do some crazy AJAX logic once the user selects the theme (e.g. in order to load a list of products in that theme, or something) - that can get more complicated. My advice on the form system is always this: use it when it works for you. But if you get in a real complex situation, it's ok to revert back and use some normal HTML forms and process the input manually in your controller (a bit more like flat PHP)
3) If you need to do some validation after a form is submitted to see if the required products are available, then you can do that in your controller (and just render the template with an error message if there is a problem). If you want to get a bit more advanced, you can create a custom validation constraint. I talk about some of the possibilities on this post: https://knpuniversity.com/s...
Overall, don't worry too much. Symfony gives you a lot of tools, but optional tools. If you feel overwhelmed, it's ok to choose to use less of Symfony's tools, while you get going :).
Good luck!
yo yo @weaverryan
hows all going? I have a question, lol highly important to me atm (maybe its covered in a different tutorial yet i havent had much time lately to browse around, if so would you please point me in the right direction?).
I'm interested in knowing about doctrine behaviors, for example, I use a lot in propel timestampable, softdelete and well of course, i18n
I'm not sure how to even begin to work that out on doctrine the annotations. i checked you have a knp bundle called doctrine-behaviors but they seem to be overly complicated to use, is there nothing of the sort native out of doctrine as they are entirelly and totally native in propel?
I'm attempting to port a project i wrote in silex1+propel1 into S3+dogtrine, to practice and well if possible actually really migrating it, and those behaviors are a requirement.
ok I've been playing with the gedmo/doctrine-extensions, seems i've gotten most of it to work, except translatable, or at least im not seeing it create any tables for the translations, loggable, softdeletable, timestampable, sluggable tho seems to work just fine.
gota keep playing with it it seems.
dogtrine's learning curve is really step! oh well, who doesnt love a good challenge eh!
Yo @JLChafardet!
You beat me to it - but I was going to recommend gedmo - so very happy you found it. It's a great library, though sometimes its documentation isn't AS great as it could be :). Are you using it with StofDoctrineExtensionBundle? It doesn't do much, but if helps alleviate some of the setup work (but it's docs aren't great). I'm not sure about Translatable - that's one I haven't used yet.
But yea, the extensions are super fun - it might be good for a mini-tutorial :)
Cheers!
Heya @weaverryan !
Tried alone first, pure doctrine extensions, installation was a bit rough, but went ok! tho couldnt get translatable to work, then went with StofDE and got it working, believe it or not, was touger to get StofDE working tho.
just find really irritating it seems translatable uses a single table to translate everything, will have to see that a little bit ahead! i will port some apps from silex to s3, but on my free time, for now gota keep up working on what gota be done hehe
Nice work @JLChafardet
I think what happens in a lot of cases with Doctrine is that:
A) it requires more typing to set something up and
B) The documentation often isn't clear (so that it's difficult to know what to type!)
Anyways - nice job, and thanks for sharing your thoughts - I definitely have this on the "idea" list :)
Cheers!
translatable and versionable (loggable) dont seem to be friendly. adding a new translation is interpreted by loggable as a new version of the row.
Those two behaviors specifically will be very difficult to get working together. And it kind of makes sense - versioning is already hard in a relational database (you need to duplicate each row before it's changed). Add onto that the fact that each row now has many translations, and you quickly (and correctly) get a big, ugly matrix. I use some of the more magic behaviors sparingly - learning from Doctrine version 1 that the magic has costs. For example, on our apps, we don't use Loggable/versionable or soft deletable.
So, my best advice is to consider doing *less* - these behaviors try to make a relational database do things that it doesn't naturally do well. If you layer on too many, I don't think you'll be happy.
Cheers!
Consider this tho: this works flawlessly in propel! (just for argument sake, if it works in propel, out of the box, no extra bundle required, i think it would be great for doctrine to have it too).
back on topic:
yeah i think ill get rid of loggable/versionable behavior. as the translatable one is more important for the customer, offering "versions" was an idea of mine and i actually never really offered it, i just "wanted" to give it as an extra
i was pointed at https://github.com/DATA-DOG... seems its a more robust audit tool, which could serve, ill consider it for later implementation to see if it works in my favor
ok, getting on my way to properly have Versioned and Translatable working together! what a freaking long day.
the issue seems to be related to the fact that to add a new language you have to actually "edit" and when you "edit" it generates a new version.
its pissing me off badly.
seems is not possible to use them together. i hate this, i really dont want to implement my own behaviors.
I think it would do great to have a full blown tutorial/track coverage.
I've been playing with the Versioned and Translatable behaviors, and at least in 5 hours of work, i CANNOT get them to work properly.
heres the basic main reason why now that I see how Dogtrine works why some people prefer to use Propel.
I'm going to keep at it, as i just cant accept defeat, but this is just ridiculously complex for something that should be ridiculously simple.
getting "Versioned" fields doesnt seem to work on the fly as it should, what i mean is this:
Entity:
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\Translatable\Translatable;
/**
* @ORM\Entity
* @Gedmo\Loggable()
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
* @ORM\Table(name="blog", indexes={@ORM\Index(name="title_idx", columns={"title"}),@ORM\Index(name="slug_idx", columns={"slug"})})
*/
class Blog implements Translatable
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer", unique=true)
*/
private $id;
/**
* @Gedmo\Translatable()
* @Gedmo\Versioned()
* @ORM\Column(type="string")
*/
private $title;
/**
* @Gedmo\Timestampable(on="create")
* @ORM\Column(type="datetime")
*/
private $createdAt;
/**
* @Gedmo\Timestampable(on="update")
* @ORM\Column(type="datetime", nullable=true)
*/
private $updatedAt;
/**
* @ORM\Column(type="datetime", nullable=true)
*/
private $deletedAt;
public function getId()
{
return $this->id;
}
public function getTitle()
{
return $this->title;
}
public function setTitle($title)
{
$this->title = $title;
return $this;
}
}
?>
Controller:
namespace AppBundle\Controller;
use AppBundle\Entity\Blog;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BlogController extends Controller
{
/**
* @Route("/test")
*/
public function newAction()
{
//we insert
$blog = new Blog();
$blog->setTitle('Titulo 1 ');
$blog->setDescription('Lorem ipsum dolor sit amet, consectetur adipiscing elit.');
$blog->setMainImage('image');
$blog->setContent("<p>Praesent eleifend facilisis sapien a lobortis. Aliquam semper feugiat lectus. Cras non ligula nec odio gravida maximus sed eu sem. Quisque in ornare sem odio</p>");
$blog->setTranslatableLocale('es_ES');
$em = $this->getDoctrine()->getManager();
$em->persist($blog);
$em->flush();
//we retrieve (comment the above code and run the code below this line)
$em = $this->getDoctrine()->getManager();
$blogEm = $this->getDoctrine()
->getRepository('AppBundle:Blog');
$articles = $blogEm->findAll();
dump($articles);
//IT DOESNT get any of the Versioned fields.
the answer from the dump is:
BlogController.php on line 36:
array:1 [â–¼
0 => Blog {#565 â–¼
-id: 1
-title: ""
-slug: "article-one-1"
-mainImage: "image"
-description: ""
-content: ""
-isVisible: false
-locale: null
-createdAt: DateTime {#561 â–¼
+"date": "2016-06-27 15:45:36.000000"
+"timezone_type": 3
+"timezone": "America/Caracas"
}
-updatedAt: DateTime {#562 â–¼
+"date": "2016-06-27 15:45:36.000000"
+"timezone_type": 3
+"timezone": "America/Caracas"
}
-deletedAt: null
}
]
BTW! @weaverryan are you aware that when you download the transcript for the courses, the code is folded when the pdf is created?
you should look into that, so the pdf is generated with the code entirely expanded.
Yep, I am aware of this - it's tricky because if you *do* include the unfolded code blocks, then some of them are HUGE (in some tutorials, we show a cached file that is a few thousand lines long) and you also lose perspective of the 5 lines that we want you to focus on. But, if they're folded, then sometimes you can't see enough :). I don't think there's a perfect way - except to check out the code on the site ;)
Cheers!
Hi Ryan and Leanna, Victor,
thank you for your courses and great effort and work.
It woud be great to hear some advices from You.
I gave 3 entities.
1) Manufacturer, 2) manufacturerModel, 3) ManufacturerModelTyp
they connected many-to-one from 3->2->1
so for example product will looks like
1) BMW,
2) seriie 5
3) E12
and product table (entity) have separate fields for id1, id2, id3
how to handle with that kind of relation in form?
some advices, links to examples?
best regards,
Peter
I already know that I can use EntityType, I'm confused how pass to next part of form variable.
For example 1st is id of BMD category, so next part of form need to have only BMW matches and so on
Hey Peter!
Let me see if I understood you well. What you need is like a three steps form, where you have to choose an option first, so next form (step) gets filled up based on the chosen option (because they are related), and for step three is the same formula. Am I correct?
Cheers!
Great!
For that case I would do it with JS, because you need to generate your forms dynamically. You gonna need an extra endpoint (backend) where you can pass the options selected by user, and then return a rendered form as the response of the AJAX call (I'm assuming you are familiar making AJAX calls, if not, you can watch our JS tutorials, they are awesome!)
Cheers!
Thank you for response.
Let me explain, I would like first - to prepare my spider of manufacturers, then add categories to them, and then - types, or models to categories.
I'm (mostly) not shure how to handle doctrine to write those three entities in the same time. - I have already made the may-to-one relations from last category entity to manufacturer, gave fields passing right Object to 'set' methods in entities
I sow in this tutorial on #3 or #4 episode smth that can help me:
$em->persist($ob1),
$em->persist($ob2)
but how handle the form if I want set only manufacturer? - is it possible?, or better way is just make special form for manufacturer?.
Ajax in symfony is a little bizzare for me...
You may need to create a model class that maps all the fields you need for this form, then create a FormType based on that class, it can contain embedded forms of your entities so you don't repeat yourself.
You can watch an example of creating an embedded form here: https://knpuniversity.com/s...
or in the symfony docs: https://symfony.com/doc/cur...
How to make recursive function in symfony 3 with doctrine?
I have script PHP native like below:
mysql_connect("localhost","root","blablabla");
mysql_select_db("demo");
function html_menu(&$strmenu="", $parent=0) {
$query = "SELECT * FROM menu
WHERE code_parent='$parent'
ORDER BY code_menu";
$sql = mysql_query($query);
if (mysql_num_rows($sql) > 0) {
$strmenu .= '<ul>'.PHP_EOL;
}
// show children
while ($row = mysql_fetch_array($sql)) {
$strmenu .= "<li>".PHP_EOL;
$strmenu .= sprintf(" % s ", $row['link'], $row['nm_menu'], $row['nm_menu']).PHP_EOL;
html_menu($strmenu, $row['code_menu']);
$strmenu .= "</li>".PHP_EOL;
}
if (mysql_num_rows($sql) > 0) {
$strmenu .= '</ul>'.PHP_EOL;
}
}
$strmenu = "";
html_menu($strmenu, 0);
echo $strmenu;
Hi, I need to place a query (that is not possible to write with doctrine entities(?)) in the symfony form query_builder.
SELECT `id` as `categoryId`,`name`,`parent_id` as `parentId`,
( SELECT LPAD(`intra_document_category`.id, 5, '0')
FROM `intra_document_category` parent
WHERE parent.id = `intra_document_category`.id
AND parent.parent_id = 0
UNION
SELECT CONCAT(LPAD(parent.id, 5, '0'), '.', LPAD(child.id, 5, '0'))
FROM `intra_document_category` parent
INNER JOIN `intra_document_category` child
ON (parent.id = child.parent_id)
WHERE child.id = `intra_document_category`.id AND parent.parent_id = 0 ) AS level2
FROM `intra_document_category` order by level2
Is it possible to write raw SQL inside query_builder symfony form type? If not, then what is an alternative?
NATIVE SQL http://docs.doctrine-projec... is the answer?