If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
We can use the play script to create and save our first Event
object.
Start by importing the Event
class's namespace:
// ...
// all our setup is done!!!!!!
use Yoda\EventBundle\Entity\Event;
Let's flex our mad PHP skills and put some data on each property. Remember, this is just a normal PHP object, it doesn't have any jedi magic and doesn't know anything about a database:
use Yoda\EventBundle\Entity\Event;
$event = new Event();
$event->setName('Darth\'s surprise birthday party');
$event->setLocation('Deathstar');
$event->setTime(new \DateTime('tomorrow noon'));
$event->setDetails('Ha! Darth HATES surprises!!!!');
Let's save this wild event! To do that, we use a special object called the
"entity manager". It's basically the most important object in Doctrine and
is in charge of saving objects and fetching them back out. To get the entity
manager, first grab a service from the container called doctrine
and
call getManager
on it:
$em = $container->get('doctrine')->getManager();
Saving is a two-step process: persist()
and then flush()
:
$em = $container->get('doctrine')->getManager();
$em->persist($event);
$em->flush();
Two steps! Yea, and for an awesome reason. The first tells Doctrine "hey,
you should know about this object". But no queries are made yet. When we
call flush()
, Doctrine actually executes the INSERT query.
The awesome is that if you need to save a bunch of objects at once, you can persist each of them and call flush once. Doctrine will then pack these operations into as few queries as possible.
Now, when we execute our play script, it blows up!
PDOException: SQLSTATE[42000][1049] Unknown database 'symfony'
Scroll up to see the error message: "Unknown database symfony". Duh! We skipped one important step: setting up the database config.
Database config is usually stored in app/config/parameters.yml
. Change
the database name to "yoda_event". For my super-secure computer, the database
user root
with no password is perfect:
# app/config/parameters.yml
parameters:
database_driver: pdo_mysql
database_host: 127.0.0.1
database_port: null
database_name: yoda_event
database_user: root
database_password: null
# ...
Now, I know you're super-smart and capable, but let's be lazy again and use
the console to create the database for us with the doctrine:database:create
command:
php app/console doctrine:database:create
There's also a command to drop the database. That's great, until you realize that you just ran it on production... and everyone is running around with their hair on fire. Yea, so that's a healthy reminder to not give your production db user access to drop the database.
We have a database, but no tables. Any ideas who might help us with this?
Oh yeah, our friend console! Run the doctrine:schema:create
command.
This finds all your entities, reads their annotation mapping config, and
creates all the tables:
php app/console doctrine:schema:create
Time to try out the play script again:
php play.php
What? No errors! Did it work? Use the doctrine:query:sql
command to run
a raw query against the database:
php app/console doctrine:query:sql "SELECT * FROM yoda_event"
And voila! There's our event.
Let's get crazy and leave the details
field blank:
// play.php
// ...
$event->setTime(new \DateTime('tomorrow noon'));
//$event->setDetails('Ha! Darth HATES surprises!!!!');
When we run the script, another explosion! Scrolling up, the error straight
from MySQL saying that the details
column can't be null.
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'details' cannot be null
So Doctrine assumes by default that all of your columns should be set to NOT NULL
when creating the table. To change this, add a nullable
option to the details
property inside the entity:
// src/Yoda/EventBundle/Entity/Event.php
/**
* @ORM\Column(name="details", type="text", nullable=true)
*/
private $details;
// ...
Tip
Doctrine has a killer page that shows all of the annotations and their options. See Annotations Reference.
But before this does anything, the actual column in the database needs to
be modified to reflect the change. Hey, console to the rescue! Run the
doctrine:schema:update
command:
php app/console doctrine:schema:update
This is pretty sweet: it looks at your annotations mapping config, compares it against the current state of the database, and figures out exactly what queries we need to run to update the database structure.
But the command didn't do anything yet. Pass --dump-sql
to see the queries
it wants to run and --force
to actually run them:
php app/console doctrine:schema:update --force
Run the play script again. Alright, no errors means that the new event is saved without a problem.
Putting stuff into the database is nice, but let's learn how to get stuff
out. Open up the DefaultController
class we've been playing with. First,
we need to get the all-important entity manager. That's old news for us.
Like before, just get the doctrine
service from the container and call
getManager
on it.
This works, but since we're extending the base controller, we can use its
getDoctrine()
to get the doctrine
service. That'll save us a few
keystrokes:
// src/Yoda/EventBundle/Controller/DefaultController.php
// ...
public function indexAction($count, $firstName)
{
// these 2 lines are equivalent
// $em = $this->container->get('doctrine')->getManager();
$em = $this->getDoctrine()->getManager();
// ...
}
To query for something, we always first get an entity's repository object:
public function indexAction($count, $firstName)
{
// these 2 lines are equivalent
// $em = $this->container->get('doctrine')->getManager();
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('EventBundle:Event');
// ...
}
A repository has just one job: to help query for one type of object, like
Event objects. The EventBundle:Event
string is the same top-secret shortcut
syntax we used when we generated the entity - it's like the entity's nickname.
Tip
If you like typing, you can use the full class name anywhere the entity "alias" is used:
$em->getRepository('Yoda\EventBundle\Entity\Event');
Use the repository's findOneBy()
method to get an Event
object by name.
There are other shortcut methods too, like findAll()
, findBy()
, and find()
:
// src/Yoda/EventBundle/Controller/DefaultController.php
// ...
public function indexAction($count, $firstName)
{
// these 2 lines are equivalent
// $em = $this->container->get('doctrine')->getManager();
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('EventBundle:Event');
$event = $repo->findOneBy(array(
'name' => 'Darth\'s surprise birthday party',
));
return $this->render(
'EventBundle:Default:index.html.twig',
array(
'name' => $firstName,
'count' => $count,
'event'=> $event,
)
);
}
Tip
In Episode 2, we'll add more methods to the repository and write some custom queries.
Ok - let's pass the Event object into the template as a variable. We can
use Twig's render syntax to print out the name and location properties. Internally,
Twig is smart enough to call getName()
and getLocation()
, since the properties
are private:
{% block body %}
{# ... #}
{{ event.name }}<br/>
{{ event.location }}<br/>
{% endblock %}
Refresh the page! I can see our event data, so all the magic Doctrine querying
must be working. Actually, check out out the web debug toolbar. The cute
box icon jumped from zero to one, which is the number of queries used for
the page. When we click the little boxes, we can even see what those queries
are and even run EXPLAIN
on them.
Good work young jedi! Seriously, you know the basics of Doctrine, and that's not easy. In the next 2 episodes, we'll create custom queries and use cool things like events that let you "hook" into Doctrine as entities are inserted, updated or removed from the database.
Oh, and don't forget Doctrine has its own documentation,
though the most helpful pages are the Annotations Reference
and Doctrine Mapping Types reference pages. And by the way,
when you see annotations in the Doctrine docs, prefix them with @ORM\
before putting
them in Symfony. That's because of this use
statement above our entity:
// src/Yoda/EventBundle/Entity/Event.php
use Doctrine\ORM\Mapping as ORM;
// ..
If that's in your class and you have @ORM\
at the start of all of your
Doctrine annotations, you're killing it.
Hey there!
Hmm, so the class - Yoda\EventBundle\Entity\Event - looks good to me, so the likely cause is the autoloader. Do you still have the original autoload require line? https://github.com/symfony/... And is your code after this? If it *is* there, post your full file on a Gist and we can figure it out :).
Cheers!
Hey, I sort of fixed it just before I received your message. I reset everything to the first commit and followed the tutorial again, and now it works!
I think I made some mistakes while trying to reset to my first commit earlier and some code may have been doubled or erased.
Anyway, thanks for your swift reply. I'm really enjoying the course so far!
Hi Ryan,
Could you please instruct us how to use Twig with Bootstrap CSS framework in Symfony2?
P.S. updated tutorials are fantastic, and thanks for the free all access. :-)
Hey Enkhbilguun!
My pleasure! If you want to use Twig and Bootstrap together, that really has (almost) nothing to do with Symfony - just include the CSS file in your base template and start using the markup. The only place where they integrate is when you're rendering forms. If you're asking about that, it *can* get quite tricky. Fortunately, Symfony 2.6 comes with a Twitter Bootstrap form theme out of the box. You can read about it here: https://github.com/symfony/.... If you are not using Symfony 2.6 (it's not even out yet), you can just copy the form theme into your project and use it as a form theme as if you had created it yourself. The source file is here: https://github.com/symfony/...
I hope that helps!
I'm getting an exception and an error right after pressing enter button multiple times for parameters. I'm trying to install the latest version v2.5 with "composer create-project symfony/framework-standard-edition starwarevents @stable" command.
What could I do?
==========
Writing lock file
Generating autoload files
Would you like to install Acme demo bundle? [y/N]
Creating the "app/config/parameters.yml" file
Some parameters are missing. Please provide them.
database_driver (pdo_mysql):
database_host (127.0.0.1):
database_port (null):
database_name (symfony):
database_user (root):
database_password (null):
mailer_transport (smtp):
mailer_host (127.0.0.1):
mailer_user (null):
mailer_password (null):
locale (en):
secret (ThisTokenIsNotSoSecretChangeIt):
debug_toolbar (true):
debug_redirects (false):
use_assetic_controller (true):
[Symfony\Component\Debug\Exception\ContextErrorException]
Warning: date_default_timezone_get(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/beregu/Dropbox/Sites/starwarevents/vendor/monolog/monolog/src/Monolog/Logger.php line 233
cache:clear [--no-warmup] [--no-optional-warmers]
Script Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::clearCache handling the post-install-cmd event terminated with an exception
[RuntimeException]
An error occurred when executing the "'cache:clear --no-warmup'" command.
create-project [-s|--stability="..."] [--prefer-source] [--prefer-dist] [--repository-url="..."] [--dev] [--no-dev] [--no-plugins] [--no-custom-installers] [--no-scripts] [--no-progress] [--keep-vcs] [--no-install] [package] [directory] [version]
[SOLVED]
It looks my composer was using my php built-in with Mac OSX Yosemite while I was thinking it was running on MAMP.
Pointed the path to the PHP in MAMP, and the installation has been completed smoothly. For those who may experience the same, have a look at: http://laravel.io/forum/05-...
For a scalar types I don't like use nullable=true
option. I prefer to set a default value on property definition and then in setter I use type casting to correct scalar type or add simple check like isset($description) ? $description : 'Some default value'
. Please, check my entity example where description
and price
properties are optional:
/**
* Class Payment
*/
class Payment
{
/**
* @var string
*
* @ORM\Column(type="string")
*/
private $name;
/**
* @var string
*
* @ORM\Column(type="text")
*/
private $description = '';
/**
* @var int
*
* @ORM\Column(type="integer")
*/
private $price = 0;
public function setName($name)
{
$this->name = $name;
}
public function setDescription($description)
{
$this->description = (string) $description;
}
public function setPrice($price)
{
$this->price = (int) $price;
}
}
Ryan, what do you think about this way?
Hey!
I like it just fine! I don't usually type-hint, because Doctrine will eventually cast (for example) a string to an int. But technically, this is more strict - since your object will *always* (for example) have an int for a price, instead of a string. I'll probably start doing this more when scalar type-casting is available. But I like it just fine :).
About nullable=false, I use this specifically if I want to guarantee that I don't mess something up and forget to set a field. For example, I might set nullable=false on price, because I *want* there to be a huge exception if I somehow create a Product and forget to set this fields - i.e. having a real value here is so important, the site doesn't really work without explicitly setting it. So I think both methods are valid: nullable = false to be crazy strict, and using default values when having a default value makes sense.
What do you think?
Thanks for your reply!
The reason to avoid `nullable=true` for scalar types nice uncovered in http://stackoverflow.com/a/... answer. I think `nullable=true` mainly should use for foreign keys, however in some cases it could applied for scalar types, but such cases not much.
Also, I'm looking forward to a PHP7 stable release with its scalar type hinting and super performance :)
Hi guys, thanks so much for the super helpful tutorials. Unfortunately I've hit an odd wall with a PDOException: "could not find driver", presumably for pdo_mysql. Ive confirmed that it is installed/enabled. I can access tables with php app/console doctrine:query:sql, and anything I run from the play.php script works fine. When I move on to running it in the browser, I get this driver error. I loaded phpinfo on a page and produced a path to my cli/php.ini. Double checked that and everything seems to be ok there. Any other thoughts? Thanks for your help.
Hi Alex!
First, I think you're probably right about the missing pdo_mysql driver. This sounds like a classic case of having 2 PHP's installed on the system - the one from the command line has pdo_mysql installed, but the one used by your web server is using a different PHP that does not.
Are you using the built-in PHP web server? If you are, then I'm wrong :). But if you aren't, let's try it. So, from the root of your project:
php app/console server:run
Then go to http://localhost:8000 in your browser. This will run your site, but use your cli PHP, which we're pretty sure has pdo_mysql installed. If this page loads, then the problem is with a different PHP being used by your web server. If you still get the error, then something else is going on.
Let me know!
The autocompletion for ->getManager is not working in my phpstorm IDE though the code is executed without problems - am I missing something? Thanks!
Hey Max!
Hmm. Assuming you're on Symfony 3, make sure you right click on the var/cache directory and "Mark Directory As" -> "Excluded". We do it at about the 2:00 mark in this video: http://knpuniversity.com/sc...
Lemme know if that works! The cache directory holds some duplicate Symfony files (for performance reasons) but this can confuse PhpStorm. If that is *not* the problem/fix... lemme know!
Cheers!
Nope, I'm running symfony 2.8 and sadly there is no var/cache directory...
$em=$this->getDoctrine()->getManager(); works fine and I get autocompletion for every step.
$em2=$this->container->get('doctrine')->getManager(); stops the autocompletion after get('doctrine').
Hey Max!
Ah, then 2 things:
1) I repeat my advice from earlier about "Excluding" the directory - but instead, include app/cache. Symfony had app/cache in 2.x and var/cache in 3.x (they're the same thing, we just changed the dir name).
2) Do you have the Symfony plugin installed (*and* enabled for this project)? https://knpuniversity.com/s... Also make sure you have the "PHP Annotations" plugin
Lemme know if either of these help :)
You are a problem-solving-monster :D I had the plugin installed, though not activated... now everything runs smoothly!
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "~2.4", // v2.4.2
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.2
"doctrine/doctrine-bundle": "~1.2", // v1.2.0
"twig/extensions": "~1.0", // v1.0.1
"symfony/assetic-bundle": "~2.3", // v2.3.0
"symfony/swiftmailer-bundle": "~2.3", // v2.3.5
"symfony/monolog-bundle": "~2.4", // v2.5.0
"sensio/distribution-bundle": "~2.3", // v2.3.4
"sensio/framework-extra-bundle": "~3.0", // v3.0.0
"sensio/generator-bundle": "~2.3", // v2.3.4
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"doctrine/doctrine-fixtures-bundle": "~2.2.0" // v2.2.0
}
}
Hi!
When I run the play.php in the cli, I get the following error message:
Fatal error: Class 'Yoda\EventBundle\Entity\Event' not found in C:\wamp\www\starwarsevent\play.php on line 26
Call Stack:
0.0002 243240 1. {main}() C:\wamp\www\starwarsevent\play.php:0
What can I do to fix this? I'm using Symfony 2.7 (newest).