Buy

Inserting new Objects

Fearless aquanauts are constantly discovering and re-classifying deep-sea animals. If a new genus needed to be added to the system, what would that look like? Well, we would probably have a URL like /genus/new. The user would fill out a form, hit submit, and the database fairies would insert a new record into the genus table.

Sounds good to me! In GenusController, create a newAction() with the URL /genus/new. I won't give the route a name yet - that's not needed until we link to it:

67 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 11
class GenusController extends Controller
{
/**
* @Route("/genus/new")
*/
public function newAction()
{
... line 19
}
/**
* @Route("/genus/{genusName}")
*/
public function showAction($genusName)
{
... lines 27 - 46
}
... lines 48 - 65
}

Be Careful with Route Ordering!

Oh, and side-note: I put newAction() above showAction(). Does that matter? In this case, absolutely. Remember, routes match from top to bottom. If I had put newAction() below showAction(), going to /genus/new would have matched showAction() - passing the word "new" as the genusName(). To avoid this, put your most generic-matching routes near the bottom.

Go Deeper!

You can often also use route requirements to make a wildcard only match certain patterns (instead of matching everything).

Inserting a Genus

Ok, back to the database-world. Let's be really lazy and skip creating a form - there's a whole series on forms later, and, I'd just hate to spoil the fun.

Instead, insert some hardcoded data. How? Simple: start with $genus = new Genus(), put some data on that object, and tell Doctrine to save it:

67 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 4
use AppBundle\Entity\Genus;
... lines 6 - 11
class GenusController extends Controller
{
/**
* @Route("/genus/new")
*/
public function newAction()
{
$genus = new Genus();
}
... lines 21 - 65
}

Doctrine wants you to stop thinking about queries, and instead think about objects.

Right now... name is the only real field we have. And it's a private property, so we can't actually put data on it. To make this mutable - to use a really fancy term that means "changeable" - go to the bottom of the class and open the "Code"->"Generate" menu. Select "Getters and Setters" - we'll need the getter function later:

35 lines src/AppBundle/Entity/Genus.php
... lines 1 - 10
class Genus
{
... lines 13 - 24
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
}

Great! Now use $genus->setName() and call it Octopus with a random ending to make things more fun!

74 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 11
class GenusController extends Controller
{
... lines 14 - 16
public function newAction()
{
$genus = new Genus();
$genus->setName('Octopus'.rand(1, 100));
... lines 21 - 26
}
... lines 28 - 72
}

Object, check! Populated with data, check! The last step is to say:

Hey Doctrine. I want you to save this to our genus table.

Remember how everything in Symfony is done with a service? Doctrine is no exception: it has one magical service that saves and queries. It's called, the entity manager. In fact, it's so hip that it has its own controller shortcut to get it: $em = $this->getDoctrine()->getManager(). What a celebrity.

To save data, use two methods $em->persist($genus) and $em->flush():

74 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 18
$genus = new Genus();
$genus->setName('Octopus'.rand(1, 100));
$em = $this->getDoctrine()->getManager();
$em->persist($genus);
$em->flush();
... lines 25 - 74

And yes, you need both lines. The first just tells Doctrine that you want to save this. But the query isn't made until you call flush(). What's really cool is that you'll use these exact two lines whether you're inserting a new Genus or updating an existing one. Doctrine figures out the right query to use.

Finishing the New Page

Ok, let's finish up! Do you remember what a controller must always return? Yep! A Response object. Skip a template and just return new Response() - the one from the HttpFoundation component - with Genus Created:

74 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 11
class GenusController extends Controller
{
... lines 14 - 16
public function newAction()
{
... lines 19 - 25
return new Response('Genus created!');
}
... lines 28 - 72
}

Deep breath. Head to /genus/new. Okay, okay - no errors. I think we're winning?

Debugging with the Web Debug Toolbar

The web debug toolbar has a way to see the queries that were made... but huh, it's missing! Why? Because we don't have a full, valid HTML page. That's a bummer - so go back to the controller and hack in some HTML markup into the response:

74 lines src/AppBundle/Controller/GenusController.php
... lines 1 - 25
return new Response('<html><body>Genus created!</body></html>');
... lines 27 - 74

Try it again! Ah, there you are fancy web debug toolbar. There are actually three database queries. Interesting. Click the icon to enter the profiler. Ah, there's the insert query, hiding inside a transaction.

And by the way, how sweet is this for debugging? You can see a formatted query, a runnable version, or run EXPLAIN on a slow query.

Running SQL Queries in the Terminal

I still can't believe it's working - things never work on the first try! To triple-check it, head to the terminal. To run a raw SQL query, use:

./bin/console doctrine:query:sql 'SELECT * FROM genus'

There they are. So inserting objects with Doctrine... pretty darn easy.

Leave a comment!

  • 2016-11-27 vp_arth

    In php, later, you always can to make field private and add your business logic to __set/__get magic.

  • 2016-11-14 Victor Bocharsky

    Oh, interesting to know! I don't know this. And yes, forgetting restart server is the most common mistake - good investigation :)

  • 2016-11-14 somecallmetim27

    Figured it out. Part of the confusion comes from the fact that php_msql.dll has been deprecated in php7 and replaced by php_mysqli.dll

    My specific problem was I forgot to restart the php webserver after altering the php.ini file

  • 2016-11-14 somecallmetim27

    Thanks. I'll take a look and let you know what I find...

  • 2016-11-14 Victor Bocharsky

    Ah, it difficult to say for windows - it depends on how did you install PHP on it. I doesn't work on Windows, so I think you just need to google "How to install pdo_mysql PHP %your_version_here% extension for Windows".

    Cheers!

  • 2016-11-14 somecallmetim27

    Awesome. Thanks for the reply. How do I install that on a Windows machine? (I typically work on Ubuntu, but this is my gaming/hobby computer I've been doing this on).

    [Edit] PS just checked. My php.ini has this line of code uncommented

    extension=php_pdo_mysql.dll

    and I have a file called php_pdo_mysql.dll in my ext folder in php\ext

    Am I maybe missing some kind of screwy windows configuration setting or something?

  • 2016-11-14 Victor Bocharsky

    Hey somecallmetim27 ,

    I suppose you're trying to use MySQL DB, so It looks like you don't have php5-pdo_mysql (or php70-pdo_mysql) PHP extension. You can easily install it with `apt-get` package manager on Ubuntu/Debian machine. If it doesn't help, or it's already installed, then please, double check your php.ini configuration and ensure you include mysql extension

    Cheers!

  • 2016-11-14 somecallmetim27

    Anyone else run into this issue when trying to create the table?

    "An exception occured in driver: could not find driver
    500 Internal Server Error - DriverException
    2 linked Exceptions: PDOException » PDOException »"

    Everything worked ok when creating the database itself. The database is remote, but creating it from my local machine worked out ok (though I did get a similar error my first time through until I uncommented a line in php.ini)

  • 2016-10-28 weaverryan

    Yo Terry!

    You can absolutely just make the properties public. And you're also right about *why* we do this (i.e. needing to tweak how it works later). And no, there's no way to do this later. If you make the public property, then all of your code will look like $myObj->name = 'foo'. But then later if you need to add some logic, your only option is to add a setter, which means also needing to update your code everywhere to look like $myObj->setName('foo').

    It is a bit of a shortcoming imo with PHP. Other languages (and this is still be discussed for PHP, I believe) allow you to *just* have the property, but use the getter/setter syntax (so that later, if you *do* need logic, THEN you can add the getter/setter without needing to change any other code).

    Cheers!

  • 2016-10-26 Terry Caliendo

    Do you need getters and setters for Doctrine/Symfony to work? Or can you just make the Entity properties public?

    Also... along the same lines... why even use getters and setters? I've read that its so that you can later add further functionality to do more in the "get" or "set" function if you need to, but if you did need to inject that extra functionality later, couldn't you just set the variable equal to an anonymous function or a closure? (or is that not allowed in a php property?)

  • 2016-05-16 sena

    hi
    yes, I am on windows and double quotes work for me (too) :)

    Thanks a lot

  • 2016-05-13 weaverryan

    Hi there!

    Hmm - try removing the ; at the end - that doesn't need to be there (or, it should be inside the quotes at least!). Also, if that doesn't work, try double-quotes. You should be able to wrap the argument with single or double quotes, but just in case, try it - if you happen to be using Windows, sometimes weird things happen. Basically, this error is saying that the doctrine:query:sql command takes a single argument and you are giving it more than 1 argument. Arguments are separated by spaces, so it appears to think that you are passing it 2 or more arguments. It could just be that extra ; :)

    Cheers!

  • 2016-05-13 sena

    I can insert my data. I checked it via mysql workbench and i could see the data ^^.
    But when try this : php bin/console doctrine:query:sql 'SELECT * FROM genus';

    this sweet exception said hi to me;

    [Symfony\Component\Console\Exception\RuntimeException]
    Too many arguments.

  • 2016-04-05 weaverryan

    Ah, fascinating - I didn't know Windows didn't like the single quotes. Thanks for sharing!

  • 2016-04-04 OnePanda

    For query, single quotes doesn't work for me (I'm on windows). But it's ok with double quotes.

  • 2016-03-29 weaverryan

    Nice catch! This was included in the code block, but the `use` line wasn't being displayed by default. I'm updating the code block right now to show it :).

    Thanks!

  • 2016-03-26 Zef

    It is not mentioned to add " use AppBundle\Entity\Genus; " in the Controller. Without this an exception occurs.

  • 2016-03-20 weaverryan

    Hey Andrew! Here's the magic: https://knpuniversity.com/blog... I have a love template for "action" - very handy!

  • 2016-03-19 Andrew Grudin

    When you just start to type word ( action ) you get choice ( action ) and then make auto complete of function and @Rote annotation above, two in one. How do you manage this?))
    When I type ( action ) I have only one choice ( abstract ). There is no ( action ) at all. ((