Custom User Method

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our fancy new account page is complete! Oh, except for that missing Twitter username part - aliens freakin' love Twitter. The problem is that we don't have this field in our User class yet. No problem, find your terminal and run:

php bin/console make:entity

to update the User entity. Add twitterUsername... and make this nullable in the database: this is an optional field:

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 39
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $twitterUsername;
... lines 44 - 134
public function getTwitterUsername(): ?string
{
return $this->twitterUsername;
}
public function setTwitterUsername(?string $twitterUsername): self
{
$this->twitterUsername = $twitterUsername;
return $this;
}
}

Cool! Now run:

php bin/console make:migration

Let's go check that out: look in the Migrations/ directory and open the new file:

... lines 1 - 2
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20180831195803 extends AbstractMigration
{
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE user ADD twitter_username VARCHAR(255) DEFAULT NULL');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE user DROP twitter_username');
}
}

And... yep! It looks perfect. Move back to your terminal one more time and run:

php bin/console doctrine:migrations:migrate

Excellent! Now that we have the new field, let's set it on our dummy users in the database. Open UserFixture. Inside the first set of users, add if $this->faker->boolean, then $user->setTwitterUsername($this->faker->userName):

... lines 1 - 8
class UserFixture extends BaseFixture
{
... lines 11 - 17
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) {
... lines 21 - 24
if ($this->faker->boolean) {
$user->setTwitterUsername($this->faker->userName);
}
... lines 28 - 34
});
... lines 36 - 51
}
}

The $faker->boolean is cool: it will return true or false randomly. So, about half of our users will have a twitter username.

Go reload! Run:

php bin/console doctrine:fixtures:load

Finally! Let's get to work in account/index.html.twig. Replace the ? marks with app.user.twitterUsername:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
... lines 16 - 20
<div class="col-md-10 no-pad">
<div class="user-pad">
... lines 23 - 24
<h4 class="white"><i class="fa fa-twitter"></i> {{ app.user.twitterUsername }}</h4>
... lines 26 - 29
</div>
</div>
</div>
... lines 33 - 46
</div>
</div>
</div>
{% endblock %}

Hmm, but we probably don't want to show this block if they don't have a twitterUsername. No problem: surround this with an if statement:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
... lines 16 - 20
<div class="col-md-10 no-pad">
<div class="user-pad">
... line 23
{% if app.user.twitterUsername %}
<h4 class="white"><i class="fa fa-twitter"></i> {{ app.user.twitterUsername }}</h4>
{% endif %}
... lines 27 - 29
</div>
</div>
</div>
... lines 33 - 46
</div>
</div>
</div>
{% endblock %}

Perfect! Ok, let's go find a user that has their twitterUsername set! Run:

php bin/console doctrine:query:sql "SELECT * FROM user"

Scroll up and... cool: spacebar1@example.com. Move back to your browser and refresh. Oh! We got logged out! That's because the id of the user that we were logged in as was removed from the database when we reloaded the fixtures.

Login as spacebar1@example.com, password engage. Click sign in and... got it!

Custom User Method for RoboHash

Oh, and there's one other thing that we can finally update! See the user avatar on the drop-down? That's totally hardcoded. Let's roboticize that! Yea, roboticize apparently is a real word.

Copy the src for the RoboHash:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
<div class="col-md-2 no-pad">
<div class="user-image">
<img src="https://robohash.org/{{ app.user.email }}" class="img-responsive thumbnail">
</div>
</div>
... lines 21 - 31
</div>
... lines 33 - 46
</div>
</div>
</div>
{% endblock %}

Then, open up base.html.twig and, instead of pointing to the astronaut's profile image, paste it!

... line 1
<html lang="en">
... lines 3 - 15
<body>
<nav class="navbar navbar-expand-lg navbar-dark navbar-bg mb-5">
... lines 18 - 21
<div class="collapse navbar-collapse" id="navbarNavDropdown">
... lines 23 - 34
<ul class="navbar-nav ml-auto">
{% if is_granted('ROLE_USER') %}
<li class="nav-item dropdown" style="margin-right: 75px;">
<a class="nav-link dropdown-toggle" href="http://example.com" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="nav-profile-img rounded-circle" src="https://robohash.org/{{ app.user.email }}">
</a>
... lines 41 - 47
</li>
... lines 49 - 52
{% endif %}
</ul>
</div>
</nav>
... lines 57 - 74
</body>
</html>

Try it! Move over and... refresh!

Nice! But, hmm... there is one small thing that I don't like. Right click on the image, copy the image address and paste in a new tab. Oh. That's a pretty big image: 300x300. It's not a huge deal, but our users are downloading a pretty big image, just to display this teenie-tiny thumbnail.

Fortunately, the fine people who created RoboHash added a feature to help us! By adding ?size=100x100, we can get a smaller image. Let's do that on the menu.

But, wait! Instead of just putting ?size= right here... let's get organized! I don't like duplicating the RoboHash link everywhere. Open your User class. Let's add a new custom function called public function getAvatarUrl().

We don't actually have an avatarUrl property... but that's ok! Give this an int argument that's optional and the method will return a string:

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 146
public function getAvatarUrl(string $size = null): string
{
... lines 149 - 154
}
}

Inside, set $url = and paste the RoboHash link. Remove the email but add $this->getEmail():

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 146
public function getAvatarUrl(string $size = null): string
{
$url = 'https://robohash.org/'.$this->getEmail();
... lines 150 - 154
}
}

Easy enough! For the size part, if a $size is passed in, use $url .= to add sprintf('?size=%dx%d'), passing $size for both of these wildcards. At the bottom, return $url:

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 146
public function getAvatarUrl(string $size = null): string
{
$url = 'https://robohash.org/'.$this->getEmail();
if ($size)
$url .= sprintf('?size=%dx%d', $size, $size);
return $url;
}
}

Now that we're done with our fancy new function, go into index.html.twig, remove the long string, and just print app.user.avatarUrl:

... lines 1 - 10
{% block body %}
<div class="container">
<div class="row user-menu-container square">
<div class="col-md-12 user-details">
<div class="row spacepurplebg white">
<div class="col-md-2 no-pad">
<div class="user-image">
<img src="{{ app.user.avatarUrl }}" class="img-responsive thumbnail">
</div>
</div>
... lines 21 - 31
</div>
... lines 33 - 46
</div>
</div>
</div>
{% endblock %}

We can reference avatarUrl like a property, but behind the scenes, we know that Twig is smart enough to call the getAvatarUrl() method.

Copy that, go back into base.html.twig and paste. But this time, call it like a function: pass 100:

... line 1
<html lang="en">
... lines 3 - 15
<body>
<nav class="navbar navbar-expand-lg navbar-dark navbar-bg mb-5">
... lines 18 - 21
<div class="collapse navbar-collapse" id="navbarNavDropdown">
... lines 23 - 34
<ul class="navbar-nav ml-auto">
{% if is_granted('ROLE_USER') %}
<li class="nav-item dropdown" style="margin-right: 75px;">
<a class="nav-link dropdown-toggle" href="http://example.com" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="nav-profile-img rounded-circle" src="{{ app.user.avatarUrl(100) }}">
</a>
... lines 41 - 47
</li>
... lines 49 - 52
{% endif %}
</ul>
</div>
</nav>
... lines 57 - 74
</body>
</html>

Let's see if it works! Close a tab then, refresh! Yep! And if we copy the image address again and load it... nice! A little bit smaller.

Next, let's find out how to fetch the user object from the one spot we haven't talked about yet: services.

Leave a comment!

  • 2019-10-08 Diego Aguiar

    Ok, so what you can do is to check both configurations and look if something relevant is different. If you run php -i in your terminal you will see all PHP settings

  • 2019-10-08 Nicolas Caballero Rau

    Hi Diego, thanks for your reply, and yes you´re right with macOS Catalina we get PHP 7.3.9 in the Terminal (console), but MAMP PRO only has PHP version 7.3.8 available at this time.

  • 2019-10-08 Diego Aguiar

    Hey Nicolas Caballero Rau

    Does it work when you run your application through the browser? If you only got that error via a console command it's very likely that it's running a different php executable.

    Cheers!

  • 2019-10-08 Nicolas Caballero Rau

    Hello Folks, be aware to install macOS Catalina using MAMP PRO as your mySQL-DB. First it looks fine, but when you try to create or update an Entity PHP CLASS and continue to the Terminal with: ./bin/console make:migration ... then you will get the following error messages in your Terminal: In AbstractMySQLDriver.php line 93: An exception occurred in driver: SQLSTATE[HY000] [2002] No such file or dir
    ector
    . In case that you have a solutions for that, please let me know, thanks.

  • 2019-08-15 Duilio Palacios

    Thanks, you're right :) I'll remember to add a test for that or to use app.user instead.

  • 2019-08-14 Diego Aguiar

    Hey Duilio Palacios

    The only downside I see to your approach is that you could pass a different User object to your template and it would look like as the logged in user. About autocompletion, I have the same problem, I believe the Symfony plugin has a bug on it.

    Cheers!

  • 2019-08-14 Duilio Palacios

    I don't know if this is a good idea or not, but I ended up passing the user to the view, in this way I've got both autocompletion in PHPStorm and a shorter way to get the user data:


    return $this->render('account/index.html.twig', [
    'user' => $this->getUser()
    ]);

    In the view:

    {{ user.firstName }}

  • 2019-07-15 Diego Aguiar

    It behaves exactly the same for me. I believe there is a way to tell PhpStorm the instance of which class is that varaible but I'm not sure how. Probably you can talk to the developers of the Symfony plugin about this.

    Cheers!

  • 2019-07-13 Mike

    Well PHPStorm does autocomplete the app.user variable inside of twig, but it doesn't do it any further (it can't autocomplete/doesnt show anything beyond app.user.) is that normal?

  • 2019-05-20 weaverryan

    Hey AbelardoLG!

    You're right! Well, we're using FontAwesome 4 in this tutorial - that's why we have the fa tag :). But if you're using Font Awesome 5, then you're totally right: fab fa-twitter.

    Thanks!

  • 2019-05-18 AbelardoLG

    By starting > v5, FontAwesome starting tag is "fab" but not "fa". :)
    Please, fix it inside the html.twig files :)

    https://fontawesome.com/ico...

    Regards.

  • 2019-03-11 weaverryan

    Hey Cryptoblob!

    No particular reason. In general, I tend to like to *always* call my getters, in the (rare) event that I add some sort of logic in that method that changes the return value in some way I want, or maybe throws an exception if something isn't right. It just gives me that flexibility if I need it, which is honestly rare in these very simple entity classes. For me, both are fine :).

    Cheers!

  • 2019-03-11 Cryptoblob

    In the getAvatarUrl method, why do you call `$this->getEmail()` instead of `$this->email`?

  • 2018-11-05 Diego Aguiar

    Thanks for sharing it!

  • 2018-11-02 Jeroen van den Nieuwenhuisen

    Instead of $uri .= sprintf('?size=%dx%d', $size, $size); you can do $uri .= sprintf('?size=%1$dx%1$d', $size);. This removes the duplication of the $size arguments.