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 SubscribePHP 7 added scalar type-hinting. But it also added - finally - return types. These didn't exist at all in PHP 5, not even for objects. This gives us the ability to say that this method returns a string and that method returns some object. I love return types.
Start in GenusController
: let's fix our code first. Say $genus->setName('Octopus')
. Now, dump $genus->getName()
.
declare(strict_types = 1); | |
... line 3 | |
namespace AppBundle\Controller; | |
... lines 5 - 15 | |
class GenusController extends Controller | |
{ | |
... lines 18 - 20 | |
public function typesExampleAction() | |
{ | |
$genus = new Genus(); | |
$genus->setName('Octopus'); | |
... line 25 | |
var_dump($genus->getName()); | |
... line 27 | |
} | |
... lines 29 - 154 | |
} |
We know that this should return a string... but technically... it could return anything. Like, what if we get crazy and just return 5
.
... lines 2 - 3 | |
namespace AppBundle\Entity; | |
... lines 5 - 17 | |
class Genus | |
{ | |
... lines 20 - 97 | |
public function getName() | |
{ | |
return 5; | |
... line 101 | |
} | |
... lines 103 - 223 | |
} |
What happens? Well, no surprise... it returns 5, an integer. Yep, we have a method that should return a string, but is instead returning an integer. Lame!
To fix this, add a return type. After the method name, add : string
. Of course, this could be any type: a class name, int
, bool
, float
, array
, callable
or even the new iterable
. More on that later.
declare(strict_types = 1); | |
... line 3 | |
namespace AppBundle\Entity; | |
... lines 5 - 17 | |
class Genus | |
{ | |
... lines 20 - 97 | |
public function getName(): string | |
{ | |
... lines 100 - 101 | |
} | |
... lines 103 - 223 | |
} |
As soon as we do this, PhpStorm is furious all over again! And so is PHP: refresh!
TypeError: Return value of getName() must be of the type string, integer returned
Yes! There is one really important thing happening behind the scenes: this throws an error only because this class is in strict mode.
What I mean is, if we changed Genus
back to weak mode, then instead of throwing an error, PHP would try to turn the integer 5 into a string. The strict or weak mode affects argument type-hints and return types.
But here's the tricky part: in this case, the strict_types
, that's important is the one in Genus
. If you remove the declare(strict_types=1)
on Genus
and then refresh the page... it works!
Wait, wait, wait. When we type-hinted the argument in setName()
, that caused an error when we put the controller in strict mode. But when we added the return type, suddenly it was important to use strict mode in the Genus
class.
Here's the real, full explanation. When you add strict_types=1
, it says:
I want "strict" type-checking to be applied to all function calls I make from this file and all return types in this file.
Or, to be even more brief:
I want "strict" type-checking to be applied to all values I control in this file.
The return value of getName()
is something that we control and calculate in Genus
. Thanks to the strict_types
in Genus
, PHP forces us to write good code and return the correct type.
But, with setName()
, the argument value is being created outside of this class in GenusController
. For that, the strict_types
needs to be added there.
Actually, scalar type hinting and return types are really easy... except for the strict_types
part. My advice is this: start adding a few strict_types
to your code and see how you like it. It's a great feature, but your code will work fine with or without it.
In Genus
, let's fix our code by returning $this->name
. Now, life is good.
... lines 2 - 17 | |
class Genus | |
{ | |
... lines 20 - 97 | |
public function getName(): string | |
{ | |
return $this->name; | |
} | |
... lines 102 - 222 | |
} |
But what if Genus
did not have a name yet? In that case, getGenus()
would return null
. Is that allowed? Nope! null is not a string... and this can be really annoying. Fortunately, PHP 7.1 gives us nullable types.
Hello! Vimeo, the service we use to host our videos, was having an outage issue earlier today. It now seems to be resolved and videos are playing again (yay!). Please let me know if you are continuing to have issues accessing content.
On declaring strict types: Instead of PhpStorm I used Netbeans 11.1 for an IDE, including the php-cs-fixer plugin. If I include the declare(strict_types=1); statement in, say, a controller class as above, I get a "hint" that doing so is a PSR-1 violation. I am not sufficiently fluent in PSR to know whether that hint is correct. Is it? [The first class I tried contains only a namespace, several use statements and a class with two methods, each returning a Response object.]
Hey GeoB,
Well, if it's just a tip - you can completely ignore it. If you want to deal with it deeper - I'd recommend you you look at the PSR-1 standard then: https://www.php-fig.org/psr... - PSR-1 is just a basic coding standard, so it might be so that you just do not have a required empty line etc, in other words it depends on your actual code, so difficult to say for sure for me because I do not use NetBeans lately.
I hope this helps!
Cheers!
there Thank you so much for yet another great post. In regards to type hinting in php 7, sometimes in our entity we don't want a variable to accept null - and when using the form component, it will trigger an error exactly like this:
https://stackoverflow.com/q...
The proposed answer suggests to always use a Data Transfer Object (DTO), and reading the sources mentioned, they suggest an entity should not be in invalid state. May I ask your opinion about this? Not sure what you think I don't think this is feasable as it would be very difficult to handle DTOs with OneToMany relationships for example, as one would have to map the entity and all its relations to the DTO - and for something like an update, I can't see how this would be realistically doable - isn't that just plainly impossible to use with Doctrine ORM and Symfony form component?
So do you guys at KNPU personally use DTO (which would solve the type hinting issue)? Using DTO seems impossible to update entities with OneToMany relationship for example, or am I missing something.. Thank you!
Hey Anthony R.!
GREAT question :). First, by design, the form component does put your objects into invalid state: that's just the nature of how it works, for better or worse. So, you're right about that :). Second, no, we rarely use DTO's on KnpU. And when we do, it's not for this reason: we use DTO when we have a form that is significantly different than any entities. In that case, using a DTO is just a matter of simplifying our code :). We're definitely more on the pragmatic side of things, and so, our entities usually do allow things to be null. And, I personally don't have a problem with that: if it's important for business for something to NOT be null, I make sure the field is setup as nullable=false in the database so that it IS caught when persisting.
Overall, the issue is that it's so common for our entities to need to be in an invalid state. For example, if you have a User class with 6 required properties, then the moment you say $user = new User()
it's invalid - the 6 "required" properties are null. The only true way to make your entities always valid is to put any required properties as constructor arguments so that they are never null ($user = new User('weaverryan', 'Ryan', 'Weaver', 'ryan@knpuniversity.com', true, 'ROLE_USER')
). I really don't like this :). So, that's why we "catch" any "should not be null" errors during persistence. Btw, if you DO want to create an entity that has required constructor arguments, that actually is possible with the form component - https://webmozart.io/blog/2015/09/09/value-objects-in-symfony-forms/ - but not worth it for us.
I know that's not a totally satisfying answer, but I hope it makes you feel better at least ;).
Cheers!
Ryan - thank you so much for this info. It's incredibly useful & I really appreciate you always taking the time to reply to your readers. Everything makes sense and I do love the idea of being more pragmatic. Thank you!
Hello!
Can I configure the strict_types=1 in all my project? I don't want to put strict_types=1 in each file.
Is it possible?
Thanks!
Hey JuanLuisGarciaBorrego!
Yea... this is *not* possible. The reason is that, as we learn about in this tutorial, it's up to the author of each file to decide if they want to have strict_types=1 for that file. Then, they write their code accordingly. If there *were* some way to do it globally, PHP would apply it to *all* files, including third-party vendor files, which weren't expecting that behavior. And since there's no way for PHP to know *your* files versus *their* files, it all just kinda doesn't work :). So yea... annoying. My best advice - which you probably already thought of - is to update your PhpStorm (or whatever editor) to automatically add that line when you create a new PHP file / class.
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.*"
}
}
Hello.
Please, check the current video. For me it doesn't play :(