Jump to content

PHP-8's constructor property promotion


NotionCommotion
 Share

Recommended Posts

The following PHP-7 code

class CustomerDTO
{
    public string $name;

    public string $email;

    public DateTimeImmutable $birth_date;

    public function __construct(
        string $name, 
        string $email, 
        DateTimeImmutable $birth_date
    ) {
        $this->name = $name;
        $this->email = $email;
        $this->birth_date = $birth_date;
    }
}


can be written this way by using PHP-8's constructor property promotion:

class CustomerDTO
{
    public function __construct(
        public string $name, 
        public string $email, 
        public DateTimeImmutable $birth_date,
    ) {}
}

How should the following PHP-7 code be modified to utilize PHP-8's constructor property promotion? Specifically:

  1. How should the Column and Groups attributes be applied to $createAt?
  2. How is $createAt's default value set to new DateTimeImmutable()?
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping\Column;
use Symfony\Component\Serializer\Annotation\Groups;
use DateTimeImmutable;

class SomeClass
{
    /**
     * @var class-string<DateTimeImmutable>|mixed|null
     */
    #[Column(type: 'datetime_immutable')]
    #[Groups(['version:read'])]
    private $createAt;

    public function __construct()
    {
        $this->createAt = new DateTimeImmutable;
    }
}

 

Link to comment
Share on other sites

Also, creating the DateTimeImmutable object within the constructor couples the SomeClass with DateTimeImmutable. If you decide at a later date to switch from plain PHP to Carbon (for instance), you've got a ton of code to change.

That's pretty much antithetical to dependency injection which - as I understand it - is the entire point of property promotion.

Link to comment
Share on other sites

13 hours ago, maxxd said:

Also, creating the DateTimeImmutable object within the constructor couples the SomeClass with DateTimeImmutable. If you decide at a later date to switch from plain PHP to Carbon (for instance), you've got a ton of code to change.

That's pretty much antithetical to dependency injection which - as I understand it - is the entire point of property promotion.

I was going to say that Symony's entity maker adds it but just realized I have been adding it since I read somewhere that I should.   That being said, I see your point and agree and will no longer do so.  Thanks!

 

15 hours ago, requinix said:

Promoted properties are also arguments to the constructor. Your constructor has no arguments. The rest isn't important.

Okay, what about the following:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping\Column;
use Symfony\Component\Serializer\Annotation\Groups;
use DateTimeImmutable;

class SomeClass
{
    /**
     * @var class-string<DateTimeImmutable>|mixed|null
     */
    #[Column(type: 'datetime_immutable')]
    #[Groups(['version:read'])]
    private $createAt;

    public function __construct(DateTimeImmutable $createAt)
    {
        $this->createAt = $createAt;
    }
}

I don't have the issue regarding setting $createAt's default value as maxxd corrected me that I shouldn't be doing so in the first place, but still have the issue regarding needing to apply attributes and not being able to declare the property twice.

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping\Column;
use Symfony\Component\Serializer\Annotation\Groups;
use DateTimeImmutable;

class SomeClass
{
    /**
     * @var class-string<DateTimeImmutable>|mixed|null
     */
    #[Column(type: 'datetime_immutable')]
    #[Groups(['version:read'])]
    private $createAt;

    public function __construct(
        private DateTimeImmutable $createAt,
    ) {}
}

Actually, I just stumbled upon the solution.  I don't believe I've seen it documented but it works.

class SomeClass
{

    public function __construct(
        #[Column(type: 'datetime_immutable')]
        #[Groups(['version:read'])]
        private DateTimeImmutable $createAt,
    ) {}
}

 

Link to comment
Share on other sites

3 hours ago, NotionCommotion said:

Actually, I just stumbled upon the solution.  I don't believe I've seen it documented but it works.

class SomeClass
{

    public function __construct(
        #[Column(type: 'datetime_immutable')]
        #[Groups(['version:read'])]
        private DateTimeImmutable $createAt,
    ) {}
}

 

It's in there.

Link to comment
Share on other sites

Oh. Also, when it comes to new features, if you're not sure where in the manual you have to look to find more information about them, try the RFC list.

https://wiki.php.net/rfc

Constructor Property Promotion's mentions the different ways it could handle attributes, and that it opts for making attributes on arguments apply both to the arguments and the promoted properties.

Link to comment
Share on other sites

  • 1 month later...
On 9/19/2021 at 3:54 PM, maxxd said:

Also, creating the DateTimeImmutable object within the constructor couples the SomeClass with DateTimeImmutable. If you decide at a later date to switch from plain PHP to Carbon (for instance), you've got a ton of code to change.

That's pretty much antithetical to dependency injection which - as I understand it - is the entire point of property promotion.

Hello again maxxd,  While I agree instantiating objects in a constructor goes against the concept of dependency injection, I am now thinking it might be a necessary evil under some scenarios.  The alternative when using Symfony is to create a listener which gets triggered when the entity is persisted and have it call setCreateAt(new \DateTimeImmutable).  As for collections, Symfony/Doctrine handles them by creating a new ArrayCollection in the constructor which is basically the same thing.  Seems like more trouble than its worth.  Am I missing something?  Thanks

Link to comment
Share on other sites

I'm not familiar with Symfony, but yeah - rules are made to be broken, right? There are situations where a tighter coupling is not entirely unavoidable, but honestly is just easier and in the long run is not a terrible idea. Instantiating SPL objects in a class constructor is really not that awful IMO as SPL objects are unlikely to change massively without a good amount of forewarning. It can be annoying when a library comes out that does something similar to the SPL class easier, but by then you've already got the SPL code working so ... so?

Link to comment
Share on other sites

Gotcha!  And didn't think you were being lazy 🙂 No response necessary unless you think I am way off base but I am also going to add non-core objects and classes (i.e. Doctrine's ArrayCollection) to that list IF used within some framework and IF that framework is responsible to keep them up to date.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.