Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Refactoring

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Ok back to work, let's finish up this MovieController. Command+O, MovieController, and now we've got that file open.

We need to save the POST'ed movie to the database. Start with getting our entity manager with $em = $this->getDoctrine()->getManager();. Then $em->persist($movie);, and $em->flush();:

... lines 1 - 11
class MovieController extends Controller
{
... lines 14 - 16
public function newAction(Request $request)
{
... lines 19 - 23
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($movie);
$em->flush();
... lines 28 - 32
}
... lines 34 - 38
}
... lines 40 - 53
}

Nice and simple!

While we're here, I'll copy the getDoctrine() line and paste it into listAction() so we can make that work. Here we'll query for the movies with $em ->getRepository() and the first cool thing here is that... you guessed it! We get autocomplete on this. Type the entity name, select the right one and hit tab:

... lines 1 - 11
class MovieController extends Controller
{
... lines 14 - 43
public function listAction()
{
$em = $this->getDoctrine()->getManager();
$movies = $em->getRepository('AppBundle:Movie')
... lines 48 - 52
}
}

The second cool thing is that it autocompletes the methods on the repository, and this includes any custom methods you have inside of there. We'll use the normal findAll();. Down below, we'll return $this->render('movies/list.html.twig'); which doesn't exist yet, with an array and we'll pass it movies:

... lines 1 - 11
class MovieController extends Controller
{
... lines 14 - 43
public function listAction()
{
$em = $this->getDoctrine()->getManager();
$movies = $em->getRepository('AppBundle:Movie')
->findAll();
return $this->render('movie/list.html.twig', array(
'movies' => $movies,
));
}
}

Awesome!

Let's go create that file: list.html.twig. And to save some time, I'll just paste in some code for this template: it's really straight forward:

{% extends 'base.html.twig' %}
{% block body %}
<table class="table">
<thead>
<tr>
<th>Movie name</th>
<th>Character</th>
</tr>
</thead>
<tbody>
{% for movie in movies %}
<tr>
<td>{{ movie.title }}</td>
<td>{{ movie.samsCharacterName }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

The only interesting thing here is that you do get autocomplete on the different properties for movie, Which really is pretty amazing. The reason that works is because PHPStorm knows that the MovieRepository returns Movie objects, so that cascades all the way down into Twig.

So as long as you have good PHPDoc that says what types of objects functions return, that should connect down to how things auto-complete in Twig.

Let's try this whole thing out. But before we do that we need to setup our database. Head over to parameters.yml by using our shortcut command+shift+O to open it up. Everything seems to be in order here, so let's just change our database to be phpstorm.

And just when you thought I couldn't get any lazier with my shortcuts I'm going to use the built in terminal from PHPStorm. This drops me right into the correct directory so I can run:

./app/console doctrine:database:create
./app/console doctrine:schema:create

Hide this little terminal, head back and refresh our browser. And we see... oops we have a small rendering problem, which is probably in our form template.

Back to PHPstorm, command+shift+O and search _form.html.twig. And yep there it is, change form-control to form-group - that's the correct Bootstrap class. Refresh again. That looks way better.

Now we can fill out the form. Let's see, which one of our favorite Samuel L Jackson movies should we choose first - so many choices. Let's just go with the obvious best: "Snakes on a Plane", with our character, Neville Flynn. Neville is the main character, and clearly this film was a 10 out of 10. I mean come on, this is Sam at his best! And I think everyone remembers the fateful day this film was released: August 18, 2006. Let's save this small piece of history.

Refactoring to a Method

Beautiful! Now for a little refactoring. First, we have $em = $this->getDoctrine()->getManager(); in a couple of places. So I'm going to refactor that: select that line and we'll see another important shortcut, control+t. This is the refactor shortcut. If you ever forget it, just go to the Refactor menu at the top and select "Refactor This" from the list.

We've got our line selected, press control+t. There's a lot of options in here, but we want the one called method. In the "Extract Method" form, add getEm as the name, make sure that the private radio button is selected and refactor!

... lines 1 - 11
class MovieController extends Controller
{
... lines 14 - 43
public function listAction()
{
$em = $this->getEm();
... lines 47 - 52
}
... lines 54 - 57
private function getEm()
{
$em = $this->getDoctrine()->getManager();
return $em;
}
}

Boom! It puts this on the bottom and it changes the original code to $this->getEm();. Copy this line, pressing control+c will select the whole line and we'll paste it right up here:

... lines 1 - 11
class MovieController extends Controller
{
... lines 14 - 16
public function newAction(Request $request)
{
... lines 19 - 24
$em = $this->getEm();
... lines 26 - 38
}
... lines 40 - 62
}

Refactoring to a BaseController

This shortcut is going to be useful in more than just this controller, so this is the perfect time to create a BaseController for my whole project. Click the Controller directory at the top of your IDE, and from there press command+n, select Controller from the menu and name it BaseController. To start, remove that public function and add abstract before the class:

<?php
namespace AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class BaseController extends Controller
{
}

This is now the spot for our own shortcuts. In MovieController update it to extend BaseController. This use statement is showing up in a darker gray to indicate that it isn't being used any more and the same for the Response use statement. Delete both of those:

... lines 1 - 10
class MovieController extends BaseController
{
... lines 13 - 61
}

Tip

Or you can go "Code" -> "Optimize Imports" instead (Control + Alt + O on a Mac) to remove unused imports, add missing imports, and organize import statements in the current file.

With the BaseController in action, let's refactor getEm() into the BaseController. Select the method, one cool way to do that is hit option+up which will select larger and larger contexts. Now hit control+t. Select "Pull Members Up" from the menu, which will pull them up to the parent class. It already recognizes that BaseController is the destination, and at the bottom it's warning us that the access will be changed from private to protected, which is awesome! Hit refactor and we can see it's gone from this file, because now it lives inside of BaseController:

... lines 1 - 6
class BaseController extends Controller
{
/**
* @return \Doctrine\Common\Persistence\ObjectManager|object
*/
protected function getEm()
{
$em = $this->getDoctrine()->getManager();
return $em;
}
}

That there is a really fast way to refactor things!

Refactoring to Rename a Method

Now that I have this here I'm realizing that getEm() is not that great of a name choice. So back to command+t, select rename from the menu and change it to getEntityManager to make my code a little clearer. When we do that I get a summary down here of all the spots in our project where PHPStorm sees getEm(). Click the "Do Refactor" button to rename this and all the other spots in the code that need updating:

... lines 1 - 6
class BaseController extends Controller
{
/**
* @return \Doctrine\Common\Persistence\ObjectManager|object
*/
protected function getEntityManager()
{
$em = $this->getDoctrine()->getManager();
return $em;
}
}

... lines 1 - 10
class MovieController extends BaseController
{
... lines 13 - 15
public function newAction(Request $request)
{
... lines 18 - 23
$em = $this->getEntityManager();
... lines 25 - 37
}
... lines 39 - 42
public function listAction()
{
$em = $this->getEntityManager();
... lines 46 - 51
}
}

Sah-weet!

There are a lot of other things you can do with the refactor feature in PHPStorm. For example, we can extract this out to a variable. This could add a level of clarity to what things are.

Or, say you get into a spot where you messed up your formatting in some terrible way. Oof that looks just awful. Make it stop! At any point, you can go up to the code menu at the top and select reformat code, which is also command+option+L and it will tidy that back up for you.

So let's hit command+A to select all the code I want to reformat, then command+option+L and now everything is back into place based on our coding standards, which you can of course control.

Leave a comment!

14
Login or Register to join the conversation
Default user avatar
Default user avatar Kevin Nagurski | posted 5 years ago

Awesome set of videos, but at the end you say that you can press command a to select all and then reformat everything... with nothing selected, reformat will do the entire document. Simples. One less step 😁

1 Reply
Rizky A. Avatar
Rizky A. Avatar Rizky A. | posted 2 years ago

I get this errors :(
[1] Symfony\Component\Debug\Exception\ContextErrorException: Warning: count(): Parameter must be an array or an object that implements Countable
at n/a
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveContextualValidator.php line 749

at Symfony\Component\Debug\ErrorHandler->handleError('2', 'count(): Parameter must be an array or an object that implements Countable', 'E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveContextualValidator.php', '749', array('value' => object(OrderedHashMap), 'object' => object(Form), 'cacheKey' => '00000000026da31d000000000e4f5a66:children', 'metadata' => object(PropertyMetadata), 'propertyPath' => 'children', 'groups' => array('Default'), 'cascadedGroups' => null, 'traversalStrategy' => '1', 'context' => object(ExecutionContext), 'group' => 'Default', 'key' => '0', 'cascadingStrategy' => '2'))
in line

at count(null)
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveContextualValidator.php line 749

at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateGenericNode(object(OrderedHashMap), object(Form), '00000000026da31d000000000e4f5a66:children', object(PropertyMetadata), 'children', array('Default'), null, '1', object(ExecutionContext))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveContextualValidator.php line 617

at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateClassNode(object(Form), '00000000026da31d000000000e4f5a66', object(ClassMetadata), '', array('Default'), null, '1', object(ExecutionContext))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveContextualValidator.php line 373

at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validateObject(object(Form), '', array('Default'), '1', object(ExecutionContext))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveContextualValidator.php line 162

at Symfony\Component\Validator\Validator\RecursiveContextualValidator->validate(object(Form), null, array('Default'))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Validator\Validator\RecursiveValidator.php line 132

at Symfony\Component\Validator\Validator\RecursiveValidator->validate(object(Form))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener.php line 68

at Symfony\Component\Form\Extension\Validator\EventListener\ValidationListener->validateForm(object(FormEvent), 'form.post_bind', object(EventDispatcher))
in line

at call_user_func(array(object(ValidationListener), 'validateForm'), object(FormEvent), 'form.post_bind', object(EventDispatcher))
in E:\code\symfony\phpstorm\app\cache\dev\classes.php line 1858

at Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(array(array(object(ValidationListener), 'validateForm'), array(object(DataCollectorListener), 'postSubmit')), 'form.post_bind', object(FormEvent))
in E:\code\symfony\phpstorm\app\cache\dev\classes.php line 1773

at Symfony\Component\EventDispatcher\EventDispatcher->dispatch('form.post_bind', object(FormEvent))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\EventDispatcher\ImmutableEventDispatcher.php line 43

at Symfony\Component\EventDispatcher\ImmutableEventDispatcher->dispatch('form.post_bind', object(FormEvent))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 660

at Symfony\Component\Form\Form->submit(array(), true)
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler.php line 113

at Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationRequestHandler->handleRequest(object(Form), object(Request))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\Form\Form.php line 489

at Symfony\Component\Form\Form->handleRequest(object(Request))
in E:\code\symfony\phpstorm\src\AppBundle\Controller\MovieController.php line 21

at AppBundle\Controller\MovieController->newAction(object(Request))
in line

at call_user_func_array(array(object(MovieController), 'newAction'), array(object(Request)))
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\HttpKernel.php line 144

at Symfony\Component\HttpKernel\HttpKernel->handleRaw(object(Request), '1')
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\HttpKernel.php line 64

at Symfony\Component\HttpKernel\HttpKernel->handle(object(Request), '1', true)
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel.php line 69

at Symfony\Component\HttpKernel\DependencyInjection\ContainerAwareHttpKernel->handle(object(Request), '1', true)
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Component\HttpKernel\Kernel.php line 185

at Symfony\Component\HttpKernel\Kernel->handle(object(Request))
in E:\code\symfony\phpstorm\web\app_dev.php line 28

at require('E:\code\symfony\phpstorm\web\app_dev.php')
in E:\code\symfony\phpstorm\vendor\symfony\symfony\src\Symfony\Bundle\FrameworkBundle\Resources\config\router_dev.php line 40

Reply

Hey Rizky A.
Could you give more context please. What caused that error? What Symfony and PHP version are you using?

Cheers!

Reply
Rizky A. Avatar

if i submit form this error message show up "Warning: count(): Parameter must be an array or an object that implements Countable". How to make sure that form sending data?. I am using symfony 2.8

Reply

Hmm, I believe that error comes from an incompatible library with your current PHP version. Did you download the course code from this page?
Try updating the validator component composer up symfony/validator

Reply
Default user avatar
Default user avatar Jacek Gendera | posted 4 years ago

You missed the "Optimize imports" feature at 4:54 ;)

Reply

Hey Jacek,

Nice tip, thank you! ;) Yeah, you can use "Code" -> "Optimize Imports" (or Control + Alt + O on a Mac) to remove unused namespaces and reorder them alphabetically.

P.S. I added a note about it here: https://github.com/knpunive...

Cheers!

Reply
Default user avatar

Hi,

When i attempt ./app/console doctrine:schema:create I receive this error [Doctrine\Common\Annotations\AnnotationException]

[Syntax Error] Expected Doctrine\Common\Annotations\DocLexer::T_CLOSE_PARENTHESIS, got

'length' at position 26 in property AppBundle\Entity\Movie::$samsCharacterName.

doctrine:schema:create [--dump-sql] [--em [EM]]

Probably something simple but I can't seem to work it out

Reply

Hey Matt!

Yep, I know that error :). It means that there's a syntax error in the annotations above the samsCharacterName in your Movie entity - you might be missing a comma between your properties (e.g. @ORM\Column(name="sams_character_name", length=255).

Btw, I like your entity+property name :)

Cheers!

Reply

Hey Ryan! how's going ?

I've been doing some refactoring (of course, using phpstorm), and I found a problem that I just can't solve.
When I refactor a field name from a class, lets say "type" to "format", the getter and setter method get's refactored automatically, but my problem relys on my twig templates that make use of those methods, they don't get refactored, so I have to make those changes manually.
This is killing me D:

Any help is very appreciated.

Reply

Hey, Diego!

As for me, in this case I do "Replace in Path" (shift+command+R on any folder in project) for property name including dot in the beginning of it, i.e. .type => .format. For unique properties it helps a lot! But if property not unique, you could skip it during refactoring.

Cheers!

Reply

Yep, I do something similar - I use Refactor in PhpStorm... but I know it's not perfect (it never could be - there are some things that are just too dynamic for phpstorm to realize). I grep my code-base a lot to be completely sure when I make these changes. In this case, I would search for Twig instances by using:


git grep '\.type'

I wasn't familiar with the "Replace in Path" - that sounds very cool!

Cheers!

Reply

That's cool!
thank you both for your time :D

Reply
Cat in space

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

This is an older tutorial using an older version of PhpStorm. However, most of the tricks we show in PhpStorm still work beautifully.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.9, <7.3.0",
        "symfony/symfony": "2.8.*", // v2.8.15
        "doctrine/orm": "^2.4.8", // v2.4.8
        "doctrine/dbal": "<2.5", // v2.4.5
        "doctrine/doctrine-bundle": "~1.4", // 1.6.4
        "symfony/assetic-bundle": "~2.3", // v2.8.1
        "symfony/swiftmailer-bundle": "~2.3,>=2.3.10", // v2.4.2
        "symfony/monolog-bundle": "^3.0.2", // v3.0.2
        "sensio/distribution-bundle": "~5.0", // v5.0.17
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.18
        "incenteev/composer-parameter-handler": "~2.0" // v2.1.2
    },
    "require-dev": {
        "sensio/generator-bundle": "~3.0", // v3.1.2
        "symfony/phpunit-bridge": "~2.7" // v2.8.15
    }
}
userVoice