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 SubscribeThere's one last feature with return types: void return types. setFunFact()
is a function... but it doesn't return anything. You can advertise that with : void
. This literally means that the method returns... nothing. And not surprisingly, when we refresh, it works great... because we are in fact not returning anything.
declare(strict_types = 1); | |
... line 3 | |
namespace AppBundle\Entity; | |
... lines 5 - 17 | |
class Genus | |
{ | |
... lines 20 - 135 | |
public function setFunFact(?string $funFact): void | |
{ | |
... lines 138 - 140 | |
} | |
... lines 142 - 224 | |
} |
But now, try to return null
. This will not work! When your return type is void
, it literally means you do not return anything. Even null is not allowed.
... lines 2 - 17 | |
class Genus | |
{ | |
... lines 20 - 135 | |
public function setFunFact(?string $funFact): void | |
{ | |
$this->funFact = $funFact; | |
return null; | |
} | |
... lines 142 - 224 | |
} |
The void return type isn't that important, but it's useful because (A), it documents that this method does not return anything and (B) it guarantees that we don't get crazy and accidentally return something.
Oh, but you can use the return
statement - as long as it it's just return
with nothing after.
... lines 2 - 17 | |
class Genus | |
{ | |
... lines 20 - 135 | |
public function setFunFact(?string $funFact): void | |
{ | |
$this->funFact = $funFact; | |
return; | |
} | |
... lines 142 - 224 | |
} |
Great news! We now know everything about scalar type hints and return types. And we can use our new super powers to update everything in Genus
.
Let's do it! Start with getId()
, this will return an int
, but it should be nullable: there is no id
at first. For getName()
, the same thing, ?string
. For setName()
, it's up to you: this accepts a string, but I am going to allow it to be null. But if you never want that to happen, don't allow it! And of course, the method should return void
.
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 93 | |
public function getId(): ?int | |
... lines 95 - 98 | |
public function getName(): ?string | |
... lines 100 - 103 | |
public function setName(?string $name): void | |
... lines 105 - 222 | |
} |
For getSubFamily()
, this is easy: it will return a SubFamily
object or null. The cool part is that we don't need the PHP doc anymore! We have a return type! Amazing!
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 108 | |
public function getSubFamily(): ?SubFamily | |
... lines 110 - 222 | |
} |
For setSubFamily()
, mark this to return void
. Notice that the argument is SubFamily $subFamily = null
. If we want that argument to be required, we could change that to ?SubFamily
. Your call!
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 113 | |
public function setSubFamily(SubFamily $subFamily = null): void | |
... lines 115 - 222 | |
} |
Let's keep going! getSpeciesCount()
returns a nullable int
, though we could give that a default value of 0 if we want, and remove the question mark. setSpeciesCount()
accepts a nullable int
and returns void
. Again, the nullable part is up to you.
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 118 | |
public function getSpeciesCount(): ?int | |
... lines 120 - 123 | |
public function setSpeciesCount(?int $speciesCount): void | |
... lines 125 - 222 | |
} |
For getUpdatedAt()
, set its return type to a nullable DateTimeInterface
, because this starts as null
. But notice... I'm returning a DateTime
object... so why make the return-type DateTimeInterface
? Well, it's up to you. With this type-hint, I could update my code later to return a DateTimeImmutable
object. But more importantly, the return type is what you're "advertising" to outsiders. Choose whatever makes the most sense.
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 140 | |
public function getUpdatedAt(): \DateTimeInterface | |
... lines 142 - 222 | |
} |
Ok, let's get this done! setIsPublished
takes a bool
that is not nullable, and it returns void
. getIsPublished
will definitely return a bool
- we initialized it to a bool
when we defined the property.
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 145 | |
public function setIsPublished(bool $isPublished): void | |
... lines 147 - 150 | |
public function getIsPublished(): bool | |
... lines 152 - 222 | |
} |
For getNotes()
, return a Collection
and then update the PHPDoc to match. There are two interesting things happening. First, ArrayCollection
implements this Collection
interface, so using the interface is a bit more flexible. Normally, that's just a choice you can make: set your return type to the class you know you're returning... or use the more flexible interface. But actually, for Doctrine collections, you must use Collection
. Depending on the situation, this property might be an ArrayCollection
or a PersistentCollection
... both of which implement the Collection
interface. In other words, the only guarantee we can make is that this returns the Collection
interface.
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 155 | |
/** | |
* @return Collection|GenusNote[] | |
*/ | |
public function getNotes(): Collection | |
... lines 160 - 222 | |
} |
Second, this returns a collection of GenusNote
objects. In other words, if you call getNotes()
and then loop over the results, each item will be a GenusNote
. But there's no way to denote that with return types. That's why we're keeping the |GenusNote[]
. That helps my editor when looping.
getFirstDiscoveredAt()
returns a nullable DateTimeInterface
and setFirstDiscoveredAt()
returns void
. getSlug()
will be a nullable string, setSlug()
will accept a nullable string argument and return void
. addGenusScientists()
will return void and removeGenusScientist()
the same. For getGenusScientists()
, like before, I'll set the return type to Collection
and update the PHP doc. Do the same for getExpertScientists()
: return Collection
. This PHPDoc is already correct... but I'll shorten it.
... lines 2 - 18 | |
class Genus | |
{ | |
... lines 21 - 163 | |
public function getFirstDiscoveredAt(): ?\DateTimeInterface | |
... lines 165 - 168 | |
public function setFirstDiscoveredAt(\DateTime $firstDiscoveredAt = null): void | |
... lines 170 - 173 | |
public function getSlug(): ?string | |
... lines 175 - 178 | |
public function setSlug(?string $slug): void | |
... lines 180 - 183 | |
public function addGenusScientist(GenusScientist $genusScientist): void | |
... lines 185 - 194 | |
public function removeGenusScientist(GenusScientist $genusScientist): void | |
... lines 196 - 205 | |
/** | |
* @return Collection|GenusScientist[] | |
*/ | |
public function getGenusScientists(): Collection | |
... lines 210 - 213 | |
/** | |
* @return \Doctrine\Common\Collections\Collection|GenusScientist[] | |
*/ | |
public function getExpertScientists(): Collection | |
... lines 218 - 222 | |
} |
Phew! That makes our class a lot tighter: it's now more readable and more difficult to make mistakes. But it also took some work! The cool thing is that you have the power to add return types and type-hint arguments wherever you want. But you don't need to do it everywhere.
After all those changes... did we break anything? Find your browser and go to /genus/new
. This is a "dummy" URL that creates and saves a Genus
behind the scenes. So apparently, that still works! Click the Genus to go to its show page. Then, login using weaverryan+1@gmail.com
and password iliketurtles
. Once you do that, click to edit the genus.
Let's see... change the species to 5000, keep the fun fact empty and change the name. Hit enter!
Yay! Everything still works! And our Genus
class is awesome!
Hey CISVHenriksen
I know, is not so easy to code along with the video without pausing, but maybe an extra monitor could help you get faster :)
About the login issue. I believe Ryan had in his session a redirection to the admin area after a successfully login. Symfony does it by default when you try to access a secured are when you are anonymous.
Cheers!
Yo MolloKhan!
I'll jump in here too... since I'm to blame ;). Yes, we often use that auto-complete PHP Storm trick. It's difficult because if we explain it each time, it will feel laborious after the first time... but if you haven't seen it before, it's surprising! I try to explain it when it makes sense, but it's quite imperfect :/.
About the speed, it may not always help (because when we're just talking, you may want full speed), but the video player also has a speed option. Oh, and one more thing! The code blocks are below the videos, so you can reference the code there too if it helps :).
Cheers!
Hi ! Thank you again for your great course. After years of PHP5, it's nice to have a nice way to keep updated !
I have one issue while loading the genus/new route.
Here is the SF error I got :
An exception occurred while executing 'INSERT INTO genus (name,
slug, species_count, fun_fact, is_published, first_discovered_at,
sub_family_id) VALUES (?, ?, ?, ?, ?, ?, ?)' with params ["Octopus5954",
"octopus5954", 30496, null, 1, "2068-03-23", null]:
Apparently, the SubFamily isn't correctly retrieve on line 44 in the GenusController. I also have a PhpStorm warning on this line, for the findAny() method, a method not found warning. In the SubFamilyRepository, I have aPhpStorm warning in the findAny() method as an unhandled UniqueResultException. At the end, I still don't get any result for a SubFamily in the GenusController, thus, I have this error.
Hey Virginie,
I think it's not a complete error you see, howeverI suppose you see it because sub_family_id value is null, but from the code I see nullable=false for Genus::$subFamily . So you need to find out why it is null. Is it intentionally? I think if you fixed the problem why it's null - the error will be gone.
Cheers!
I don't think it's intentional as it goes smoothly in the video, without any mention about any fix I should make for the code to run. It isn't blocking for the learning process, as I already knew SF, I could figure out why there was this issue, however, if someone who only wants to know about PHP7, without any prior knowledge on SF, has the same issue, I think he/she would be lost.
Hey Virginie,
Hm, let me see again... as I understand, you're mostly talking about this:
Apparently, the SubFamily isn't correctly retrieve on line 44 in the GenusController.
Why do you think it's retrieved incorrectly? I see:
$subFamily = $em->getRepository(SubFamily::class)
->findAny('AppBundle:SubFamily');
This is totally valid as for me.
> I also have a PhpStorm warning on this line, for the findAny() method, a method not found warning
Make sense of course, because getRepository() method has "@return \Doctrine\Common\Persistence\ObjectRepository" where there're no any findAny() method, this method is from custom SubFamilyRepository. Well, If you enable and configure Symfony Plugin for PhpStorm - you won't see this warning. Or, if you don't want to use this plugin - you can manually help PhpStorm to understand better what getRepository() method returns. For example, just add an annotation manually and you won't see this warning:
/** @var \AppBundle\Repository\SubFamilyRepository $subFamilyRepo */
$subFamilyRepo = $em->getRepository(SubFamily::class);
$subFamilyRepo->findAny('AppBundle:SubFamily');
> In the SubFamilyRepository, I have aPhpStorm warning in the findAny() method as an unhandled UniqueResultException.
Yes, this is a new feature for PhpStorm. This exception will be thrown if your query returns more than one row in getOneOrNullResult(), and we really do not want to catch and handle this error because this should never happen since we call setMaxResults(1).
> At the end, I still don't get any result for a SubFamily in the GenusController, thus, I have this error.
This one is interesting... Do you think we missed something in this screencast or this is just your error, i.e. you just missed something when following our tutorials? Do you have an idea how we can improve this? Because for me it looks like you just forgot to load fixtures that's why findAny() returns null for you, and it seems like a user error.
Would be glad to make a quick fix if there's really a missing part in our process.
Cheers!
// composer.json
{
"require": {
"php": ">=5.5.9",
"symfony/symfony": "3.3.*", // v3.3.18
"doctrine/orm": "^2.5", // v2.7.2
"doctrine/doctrine-bundle": "^1.6", // 1.10.3
"doctrine/doctrine-cache-bundle": "^1.2", // 1.3.2
"symfony/swiftmailer-bundle": "^2.3", // v2.5.4
"symfony/monolog-bundle": "^2.8", // v2.12.1
"symfony/polyfill-apcu": "^1.0", // v1.3.0
"sensio/distribution-bundle": "^5.0", // v5.0.19
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.25
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"knplabs/knp-markdown-bundle": "^1.4", // 1.5.1
"doctrine/doctrine-migrations-bundle": "^1.1", // v1.2.1
"stof/doctrine-extensions-bundle": "^1.2", // v1.2.2
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.4
"symfony/phpunit-bridge": "^3.0", // v3.2.8
"nelmio/alice": "^2.1", // v2.3.1
"doctrine/doctrine-fixtures-bundle": "^2.3", // v2.4.1
"symfony/web-server-bundle": "3.3.*"
}
}
@ 4:00 when you set Collection, it went WAY too fast, that you actually used phpStorm Auto-complete to add use statement for Doctrine\Common\Collection.
Am I the only one, that thinks these tutoials sometimes write too fast? trying to keep up, I have to keep pausing video
+ Last step for Login, does not bring you to genus overview page.