gstreamer0.10-ffmpeg
gstreamer0.10-plugins-good
packages.
Yo guys! Time to level-up our project in a big way. I mean, big. This series is all about an incredible library called Dog-trine. Wait, that's not right - it's Doctrine. But anyways, Doctrine is kinda like a dog: a dog fetches a bone, Doctrine fetches data from the database. But Doctrine does not pee in the house. That's part of what makes it awesome.
But back up: is Doctrine part of Symfony? Nope. Symfony doesn't care how or if you talk to a database at all. You could use a direct PDO connection, use Doctrine, or do something else entirely. As usual, you're in control.
If you want to code along - which you should - then download the code from the
screencast page and move into the directory called start
. I already have the start
code downloaded, so I'll go straight to opening up a new terminal and starting the
built-in sever with:
./bin/console server:run
Tip
You may also need to run composer install
and a few other tasks. Check the README
in the download for those details.
Perfect!
Doctrine is an ORM: object relational mapper. In short, that means that every table -
like genus
- will have a corresponding PHP class that we will create. When you query
the genus
table, Doctrine will give you a Genus
object. Every property in the
class maps to a column in the table. Keep this simple idea in mind as we go along:
this mapping between a table and a PHP class is Doctrine's main goal.
Oh, and before we hop in, I want you to remember something very important: all the tools in Symfony are optional, including Doctrine. If Doctrine - or any tool - does more harm than good while solving a problem, skip it and do something simpler. Tools are meant to serve you, not the other way around.
Our sweet app displays information about different ocean-living genuses... but so far, all that info is hardcoded. That's so sad.
Instead, let's create a genus
table in the database and load all of this dynamically
from there. How do you create a database table with Doctrine? You don't! Your job
is to create a class, then Doctrine will create the table based on that class.
It's pretty sweet. Oh, and the whole setup is going to take about 2 minutes and
25 lines of code. Watch.
Create an Entity
directory in AppBundle
and then create a normal class inside
called Genus
:
... lines 1 - 2 | |
namespace AppBundle\Entity; | |
class Genus | |
{ | |
} |
You're going to hear this word - entity - a lot with Doctrine. Entity - it sounds like an alien parasite. Fortunately, it's less scary than that: an entity is just a class that Doctrine will map to a database table.
To do that - Doctrine needs to know two things: what the table should be called and
what columns it needs. To help it out, we're going to use... drumroll... annotations!
Remember, whenever you use an annotation, you need a use
statement for it. This
will look weird, but add a use
for a Column
class and let it auto-complete from
Doctrine\ORM\Mapping
. Remove the Column
part and add as ORM
:
... lines 1 - 2 | |
namespace AppBundle\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
... lines 6 - 15 |
Tip
You can also configure Doctrine with YAML, XML or PHP, instead of annotations. Check Add Mapping Information to see how to configure it.
Every entity class will have that same use
statement. Next, put your cursor inside
the class and open up the "Code"->"Generate" menu - cmd
+N
on a Mac. Ooh, one of the options
is ORM Class
. Click that... and boom! It adds two annotations - @ORM\Entity
and
@ORM\Table
above the class:
... lines 1 - 6 | |
/** | |
* @ORM\Entity | |
* @ORM\Table(name="genus") | |
*/ | |
class Genus | |
{ | |
} |
Doctrine now knows this class should map to a table called genus
.
But that table won't have any columns yet. Lame. Add two properties to get us rolling:
id
and name
. To tell Doctrine that these should map to columns, open up the
"Code"->"Generate" menu again - or cmd
+N
. This time, select ORM Annotation
and
highlight both properties. And, boom again!
... lines 1 - 6 | |
/** | |
* @ORM\Entity | |
* @ORM\Table(name="genus") | |
*/ | |
class Genus | |
{ | |
/** | |
* @ORM\Id | |
* @ORM\GeneratedValue(strategy="AUTO") | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="string") | |
*/ | |
private $name; | |
} |
Now we have annotations above each property. The id
columns is special - it will
almost always look exactly like this: it basically says that id
is the primary
key.
After that, you'll have whatever other columns you need. Hey, look at the type
option that's set to string
. That's a Doctrine "type", and it will map to a varchar
in MySQL. There are other Doctrine types for strings, floats and text - we'll talk
about those soon!
And with just 25 lines of code, we're done! In a second, we'll ask Doctrine to create
the genus
table for us and we'll be ready to start saving stuff. Well, let's get
to it!
Entirelly true, +1 ian to your post!
I'm usually the kind that gets bored at this kind of videos, normally never really watching for long, the casters (screencasters) tend to be dead boring, but here at kpnu is a whole different story! I'm as engaged as I can be! just started yesterday and I'm already finding myself in the situation wake up->go to work->work fast if possible->""agr wana get home to continue knpu tracks""->4pm->lock workstation->drive home->login knpu
Hey Jo,
Thanks for sharing this "Code"->"Generate" menu shortcut with our Windows OS friends!
Cheers!
In my company we work with Symfony but for Doctrine they says: "we will not use Doctrine because of performance issue with tons of data". Are they right? If not give me arguments to convince them they are wrong.
Hey Pawel,
First of all, Doctrine has a low level library to interact with DB, see Doctrine DBAL. It's just a wrapper around PDO, which give you some useful benefits. So, instead directly use PDO I'd recommend Doctrine DBAL which has the same performance, but better DX.
But unfortunately, they are right if we're talking about Doctrine ORM, it really has performance issue but in favor of better DX. If you love OOP - you'd love ORM where you're working with objects instead of plain arrays. Well, to choose whether to use ORM or no depends on your project. If you're using with a lot of data on the same page - then yes, better do not use ORM. But for simple things you still can use it. Also, you can tune up queries with query builder to improve performance in some cases. And on those pages where you need to work with big amount of data you can do not hydrate data, i.e. get the same plain arrays instead of entities to avoid wasting time on entities creation.
In shorts, we do have many data on KnpU, but we do use Doctrine ORM. Often we use Doctrine repositories to build custom queries, but there where we work with tons of data we avoid hydration, i.e. do not allow Doctrine to create entities and fetch simple arrays from the DB instead. However, most of the time we work with entities to get benefits of ORM. I bet you can google a lot of them as well as disadvantages. Anyway, choosing whether to use ORM or no is depends on project.
Cheers!
Why doesn't PHP Storm autocomplete "Private" when I start typing it? Seems like that should be a common thing to help autocomplete.
Hey Terry,
Yes, you're right - it's the best practice to use lowercase for modifiers and other reserved keywords.
Cheers!
Similar to Islam, I have never paid for a single online course... Yours is the first and IMO its worth it.
I have a question tho. It seems that you have a PhpStorm plugin that shows you the value of Symfony YAML constant when you click on it. Would you mind sharing its name? As I am not able to find it.
Hey MiChAeLoKGB,
Thanks for your kind words!
Sure! It's not a secret plugin and we try to mention it in our screencast every time :) It's a Symfony Plugin, which you can install and then enable in PhpStorm preferences. Please, check out this free screencast to get more details how to install and use it: https://knpuniversity.com/s...
Cheers!
Maybe useful to know, for a Windows computer to open the Code generate inside the class (at 3:56) use the shortcode Alt + Insert.
I copied the project from start and I'm getting this erro when I run php bin/console server:run
PHP Warning: require(/home/uertas/Downloads/symfony-doctrine/start/app/../vendor/autoload.php): failed to open stream: No such file or directory in /home/uertas/Downloads/symfony-doctrine/start/app/autoload.php on line 11
Hey Ugur,
Ah, that's because you didn't install Composer dependencies. You need to run $ composer install
after download a course. Check the README.md
in the "start/" directory of downloaded code.
Cheers!
Hi everybody,
I have a question about Doctrine : if, i have to connect to an existing database, with tables and foreign keys, will
Doctrine still be okay to do that ?
Thanks
Hey Gremet,
If this table was generated with Doctrine - most likely so. You can easily check it by yourselves: when DB credentials is pointed, execute the next command:
$ bin/console doctrine:schema:validate
It will show you whether your DB schema is OK.
Cheers!
Hi Victor, Thanks.
In fact i think about an application that could work with several databases.
And in this case those databases would not have been built with doctrine.
For instance a symphony application working with two databases : one database called "db_specific" built with doctrine, and another database called "db_erp" already existing and being also used by others applications.
Hi Gremet,
Oh, I see... Well, I think the article How to Generate Entities from an Existing Database could help for you. I think most cases should work with it, but anyways, if you're going to use Doctrine ORM for that DB, I'm afraid you have to do some tweaks in DB schema before your schema will be valid. Actually, it depends on *that* DB, difficult to say, so as I said you have to try it: fill in credentials for "db_erp" and execute `bin/console doctrine:schema:validate` to see if it will work.
Cheers!
I usually never buy courses on the internet, but it's the first time I actually find paying $25 worth it. Best dollars spent online in my life haha
Symfony is not hard, it just got SOO many things that one need to remember. So the difference between your tutorials and others', is that you try to explain it simply while still covering all the important parts and aspects. Keep up the great work !
You made my day! Cheers! And yea, Symfony gives you a lot of cool stuff. Or, said differently.... there is a lot to know to use all of Symfony :).
Keep up the good work yourself!
Hi, sorry to bother many times, your support tutorials and support for students is great by the way.
I've installed composer, but I've got this error:
[Symfony\Component\Debug\Exception\ContextErrorException]
Warning: Class __PHP_Incomplete_Class has no unserializer
It worked at the start of this tutorial, but after all the changes when I tried to restart the server It gets me the error above
Hey Josk,
Most likely it's due to the cache problems. In which environment do you see this error: prod, dev or test? Try to clear the cache. I even recommend you to do it manually with $ rm -rf var/cache/*
console command. Let us know if it doesn't help.
Cheers!
Hey, I downloaded the source code and copied the start folder in my project folder. But now I get an error;
(1/1) ClassNotFoundException
Attempted to load class "KnpMarkdownBundle" from namespace "Knp\Bundle\MarkdownBundle".
Did you forget a "use" statement for another namespace?
in AppKernel.php (line 19)
line 19 in AppKernel.php is;
new Knp\Bundle\MarkdownBundle\KnpMarkdownBundle(),
How can I fix this?
Hey Loes!
I believe you just forgot to run $ composer install
Give it a try, and if is not the case, let me know
Ah, also there is a Readme file, it might help you too
Cheers!
which text editor are you using, I am using sublime and I dont have these right click or Cmd M option as yours :(
Hey Bushra Rehan!
That's ok :). They are all just shortcuts - which are GREAT, but not actually required. If you're ever not sure (by watching the video) what code was generated with one of my shortcuts, you can look at the code blocks on this page below the video.
Cheers!
Hi
Two questions regarding doctrine:
1) when I want to save to the db the images from a multi-select form field, I can't save them using doctrine's way. Example:
/**
* Creates a new product entity.
*
* @Route("/new", name="product_new")
* @Method({"GET", "POST"})
*/
public function newAction(Request $request)
{
$product = new Product();
$form = $this->createForm('AppBundle\Form\ProductType', $product);
$form->handleRequest($request);
$cat = $request->query->get('category');
$category = $this->getDoctrine()->getRepository('AppBundle:Category')->findOneby(['name'=>$cat]);
if (!$category) {
throw $this->createNotFoundException('The Category '.$category.' does not exist');
}
if ($form->isSubmitted() && $form->isValid()) {
$images = $form->get('photo')->getData();
foreach ($images as $img){
//this is a
$name = $this->get('app.image_uploader')->upload($img);
// $product->setPhoto($name);
// $product->setActive('yes');
// $product->setOrder(99);
// $product->setCategory($category);
// $em = $this->getDoctrine()->getManager();
// $em->persist($product);
// $em->flush();
$em = $this->getDoctrine()->getManager();
$RAW_QUERY = 'INSERT INTO products SET photo=?, active="yes", order=99, category_id=?';
$stmt = $em->getConnection()->prepare($RAW_QUERY);
$stmt->execute([$name, $categoria->getId()]);
}
$this->addFlash('success', 'The products has been successfully created');
return $this->redirectToRoute('category_show', array('id' => $category->getId()));
}
return $this->render('product/new.html.twig', array(
'product' => $product,
'category' => $category,
'form' => $form->createView(),
));
}
The doctrine's way is the commented code, and it just inserts the last element of the loop. Strange thing is that all the images are uploaded correctly into the upload directory, only the insertion into db doesn't work. I figured out that, somehow, when my images are uploaded, they don't have the right owner (I'm using Ubuntu). It should be dan dan, but instead it's www-data www-data. And because the upload is getting run before the db saving, this might cause the problem?
2) After saving the images to the db, I'm having a page to edit the record, and beside other fields, I need to edit the image too. The problem is that I can't figure out a way to prepopulate the file field with the saved image for that product.
So if my product is:
id name photo
1 candy candy.png
I want my file field inside the edit form to be pre-populated with the candy.png value. Because, if the user doesn't want to edit the image, but only the name, if he submits the form, the new value for the photo field inside the db, will be null.
How can I achieve this?
Thanks!
Yo Dan!
So, upload can be tricky :). But, I'm sure we can get this right!
1) I clearly see that you have multiple "photo" fields in your form (you're looping over these). But, I'm a little confused, because - according to your code - if I upload 5 photos, then you want 5 records to be inserted into the products table. Is this correct? I was expecting that maybe you wanted 1 Product, but then 5 inserts into some related "product_photos" table. But, let's assume that you DO want 5 products (which is totally valid - I am probably just misunderstanding). In that case, you need to create 5 "new Product" objects. Currently, there is only one Product object, so Doctrine inserts the first time through the loop (which it hits `$em->flush()</code), and then updates that one record on each loop. And actually, if the purpose of your form is to insert multiple Product objects (not just one), then your ProductType form class should not be bound to a Product object. In other words, you should pass nothing as the second argument to createForm(), and then should just create the new Product objects in your loop by hand. They key thing is that the purpose of your form is not to create/update a single Product (which is when it would make sense to bind it to a Product class), its purpose is to create an array of Product objects. So, don't bind it :).
2) About pre-populating on the "edit" form. The correct way to do this depends on your answers for my above points :). Basically, your newAction - since is allows you to create many products at once, not just one product - is a bit odd. The proper way to setup your editAction will depend on exactly what you're trying to accomplish. So, let me know!
3) About the www-data user/group. The issue is that when you upload a photo, the photo is being saved by your web server, not you. And, your web-server (e.g. Apache) has a user & group of www-data. This is an annoying, but classic problem, and you can fix it a few ways. First, most easily, when you save the file, you can give it 777 permissions. The downside of this is that if someone gained access to your server, they could view/edit/delete these uploaded files (if they're non-sensitive images, probably not a big deal). Another solution is to give them 755 permissions, and make sure that your dan user is in the www-data group. Then, both your web server and you can modify/delete them. Another solution is to configure your web server to actually run as dan:dan (the downside here being that whatever dan has access to, a hacker might have access to, e.g. if they somehow upload a PHP file to your server and execute it, that PHP file could read/delete/edit whatever stuff dan can do).
Phew! I hope this at least gives you a start of things to look into!
Cheers!
Yes, you've understood correctly. If one selects 5 images, then I need to upload each image to the specified directory (functionality already done & working), *and* I need to insert 5 paths into the db, to the respective images.
So should I instantiate a new product object inside the foreach loop, and then set $product->setPhoto($img), then persist + flush?
And where you guide me to read more about editing such file filed?
Thanks for the effort!
Hey Dan!
> So should I instantiate a new product object inside the foreach loop, and then set $product->setPhoto($img), then persist + flush?
Yep, exactly :). On the edit page, will the user be editing just *one* product at a time, or will they be editing many products at once (similar to how the new page allows you to create multiple products)? Does this product table have other fields? I think I was confused because it seems odd to have a "products" table that only has an image field :).
Cheers!
Yes, the user will edit just one product at a time. The products table has many fields, but for the simplicity of the question (to be understandable), I've posted just that field I was interested at.
I've found a solution on SO: http://stackoverflow.com/a/.... Do you consider it still valid(and the way of solving the problem) for Symfony 3.2.1?
Thanks!
Hey Dan!
Hmm, that solution is great... but seems like overkill to me. So, assuming that your edit screen is a nice, traditional product edit, where you're editing just one product, then there is a 2 step process:
1) In your form, you need to call your field something other than "photo". The problem is that the "photo" property on your entity is a string filename. But when you use a File field in your form, when you submit, this will be an UploadedFile object. So, temporarily (before you do the processing), your string field is actually an object... which is weird (then later, you move the uploaded file and re-set this field to the filename string). But, on edit, this causes a real problem because if you do not choose to upload a new file, then when you submit, suddenly your "photo" field is overwritten with "null". And that's not what we want! So, rename your field to "photoFile" and give it a special option 'mapped' => false
This makes it "ok" that you don't have an photoFile
property on your Product entity. But you can still access the value like you did before: $form['photoFile']->getData()
(except that this time, this should be a single object, not an array, since you can only edit one photo at a time on your edit screen).
2) With just the above change, if the user doesn't re-upload a file, it won't replace the value stored in the database. Well, to make sure this happens, you'll surround your upload logic with an if statement:
// see if the user actually uploaded something! If not, do nothing
if ($form['photoFile']->getData()) {
// do the file processing stuff
// and set the photo property on your Product entity
}
3) In your template, everything is simple: just render your photoFile upload field. If you want to get fancy and render the current image, you can totally do that. Just make sure to pass your product as a variable into the template
{% if product.photo %}
{# this line will change based on where you are storing your uploads #}
<img src="{{ asset('uploads/'~product.photo) }}" />
{% endif %}
{{ form_row(form.photoFile) }}
Cheers!
Thanks a lot for the example, I'll try it immediately, and I'll let you know when I'm done.
LE
You're a life savior! Works as expected.
So, to resume, for the ones beginners, like me:
->add('photoFile', FileType::class, [
'data_class' => null,
'multiple' => true,
'label' => null,
'mapped' => false,
])
In the edit action method in the controller, I needed to create another form, containing the same fields, but this time without the 'multiple' option, because the user can edit one product at a time, so no multi-upload functionality involved.
/**
* @Route("/edit/{id}", name="edit_route")
*/
public function editAction(Request $request, Product $product)
{
$form = $this->createFormBuilder($product)
->setMethod('POST')
->setAction($this->generateUrl('edit_route',['id' => $product->getId()]))
//add extra fields here, if any
->add('photoFile', FileType::class, [
'data_class' => null,
'label' => null,
'mapped' => false,
])
->getForm()
;
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$photo = $form['photoFile']->getData();
if(null != $photo){ // if the user did selected another image in order to edit the exiting one...
$img = $this->get('app.image_upload')->upload($form['photoFile']->getData()); //...upload it
$product->setPhoto($img); //set the name of the photo for saving it into the db
//set extra fields here
$this->getDoctrine()->getManager()->flush(); // save the image name into db
return $this->redirectToRoute('homepage');
} else { // if the user didn't wanted to edit the image, update only the extra fields, if he updated any of them
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('homepage');
}
}
return $this->render('default/edit.html.twig', ['form'=>$form->createView(), 'product' => $product]);
}
In the default/edit.html.twig template, the form for
{{ form_start(form) }}
<!-- render here other fields you might have -->
{% if product.photo %}
<img src="{{ asset('upload/' ~ product.photo) }}" width="400" height="200">
{% endif %}
{{ form_row(form.photoFile) }}
<br>
<button type="submit">Go</button>
{{ form_end(form) }}
Now, if a user edits or not any of the fields inside the edit form, when he submits it, it won't get null as the value for the photo field inside the db anymore. Or, if the user wants to upload a different image, then this new image will be saved instead of the old one.
I don't get any options to generate annotations. Any idea why is this happening? Do i need to add some kind of configurations to Phpstorm?
Here is the screenshot
http://imgur.com/a/EZ5sa
Hi Ammar!
Also install the "PHP Annotations" plugin - I neglected to mention this in the first course (we've added a note, but it's still easy to miss). This will likely fix your issue :).
Cheers!
Hi,
I havent dowload the code, but this is great to learn!!
I did not use this tutorial code cause I had use the tutorial for symfony 4 foundamantal,
Just by curious, I had tried to installed Ductrine by this command:
$ composer require doctrine maker
And next I had no see the file called: config/parametre.yaml ( which I belived this is the file to config the database varaibles)
or it should be add and configer in doctrine.yaml instead?
I think this is why as I tried to create database: $ php bin/console doctrine:database:create
I had follow error:
In AbstractMySQLDriver.php line 108:
An exception occurred in driver: SQLSTATE[HY000] [1045] Access denied for user 'db_user'@'localhost' (using password: YES)
Sorry if this is not concerned about this chapter, asking by curiousity (:
cheers
Hey Tess Hsu
This tutorial was made on Symfony3, so things have changed considerable in Symfony4. The file "config/paramaters.yml" does not exist anymore but its equivalent is ".env". As you figure it out, in "config/packages/doctrine.yaml" is the new place to configure your Database, you should be able to setup that file and continue with this tutorial.
Cheers!
So, when I set up a text field, I don't think about how long the information is that goes in that field? I just leave the standard length set to 255? Or does a responsible developer still think about the maximum size of a field?
By the way, your comments do not allow for the new e-mail addresses (tld solutions for example)...
Hi Hermen!
Good question.. and it basically... it depends. I often don't worry about the length... other than making sure to use "text" instead of "string" if I know it'll be longer than 255. But if you have a "string" field and decide to set its length specifically (e.g. to 100), you're doing this purely so that your database is a little bit smaller. Is this is a good thing? Of course (unless you set it to 100 and then later try to save strings longer than 100 - I have totally done this in the past)! Will it make a big difference? Hmm, probably not - unless you really need to optimize things. So, for the most part, I treat setting the length explicitly as "premature optimization" - I have better things to do... and we can always shorten this column later. Of course, a database-administrator would argue with me... but I will probably launch my project faster ;).
P.S. LAME on the comments - we use Disqus... which is a GIANT in the commenting world... so it's crazy that they validate incorrectly against valid domains. Hopefully they'll fix it - but sorry about that!
Cheers!
I'm getting crazy.
I saw more videos from this course and I hope that here is the correct place to ask.
I don't know if a Entity it's the same as a Model into MVC. For example, before this course I was doing something like "Model > User > function login()" (whitout Symfony, of course), but now, I don't sure if inside a "User entity" I can put functions like these or only Doctrine should use that Entity.
Thanks you so much, I really apreciate all your help, teacher.
Hey Juan Nicolás
An entity in Doctrine represents a record in a table for your DB, so that's why you see a lot of DB metadata, such as relationships, field types, etc. Most of the time they have pure data, but you can put more logic on it (Business logic), the only constraint is, an Entity can't access to any services you have, so you can't do "User->login();"
For that case you may want to have a UserManager service
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.1.*", // v3.1.4
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.6.4
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.0
"symfony/swiftmailer-bundle": "^2.3", // v2.3.11
"symfony/monolog-bundle": "^2.8", // 2.11.1
"symfony/polyfill-apcu": "^1.0", // v1.2.0
"sensio/distribution-bundle": "^5.0", // v5.0.22
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.16
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"knplabs/knp-markdown-bundle": "^1.4", // 1.4.2
"doctrine/doctrine-migrations-bundle": "^1.1" // 1.1.1
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.0.7
"symfony/phpunit-bridge": "^3.0", // v3.1.3
"nelmio/alice": "^2.1", // 2.1.4
"doctrine/doctrine-fixtures-bundle": "^2.3" // 2.3.0
}
}
Dogtrine .. You rule sir. lol
I really like the way you teach. It really motivates me to listen to you. Keep up the good work.
P.S : I will surely save some bucks to buy a subscription on this site ..
Regards,
Kev
From Mauritius