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.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur 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(int $size = null): string | |
{ | |
$url = 'https://robohash.org/'.$this->getEmail(); | |
if ($size) { | |
$url .= sprintf('?size=%dx%d', $size, $size); | |
} | |
return $url; | |
} | |
// ... lines 157 - 158 |
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.
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.