Jump to content

Can inheritance be used with PHP 8 attributes?


NotionCommotion

Recommended Posts

Context.

Class AbstractTenantEntity's purpose is to restrict access to data to the tenant (i.e. account, owner, etc) that owns the data.  Any entity which extends it will have the TenantId added when created and the TenantId in the WHERE clause for all other requests.

Tenant typically does not have collections of the various entities which extend AbstractTenantEntity, but does for a few of them do.  When using annotation, I handled it by applying Doctrine's AssociationOverride annotation to the extended classes which should have a collection in Tenant.

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */
class Tenant
{
    /**
     * @ORM\OneToMany(targetEntity=Asset::class, mappedBy="tenant")
     */
    private $assets;

    // Other properties and typical getters and setters
}

 

use Doctrine\ORM\Mapping as ORM;

abstract class AbstractTenantEntity implements TenantInterface
{
    /**
     * inversedBy performed in child where required
     * @ORM\ManyToOne(targetEntity=Tenant::class)
     * @ORM\JoinColumn(nullable=false)
     */
    protected ?Tenant $tenant = null;

    // Typical getters and setters
}

 

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\AssociationOverrides({
 *     @ORM\AssociationOverride(name="tenant", inversedBy="assets")
 * })
 */
class Asset extends PublicIdTenantEntity
{
    // Various properties and typical getters and setters
}

 

So, now I am changing from annotations to attributes, and modified AbstractTenantEntity as follows:

use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\JoinColumn;

abstract class AbstractTenantEntity implements TenantInterface
{
    /**
     * inversedBy performed in child where required
     */
    #[ManyToOne(targetEntity: Tenant::class)]
    #[JoinColumn(nullable: false)]
    protected ?Tenant $tenant = null;

    // Typical getters and setters
}

Next I went to modify Asset and realized that there didn't appear to be an attribute for AssociationOverride.  I found a little discussion on the topic on github but couldn't find any resolution.

Question

Can inheritance be used with PHP 8 attributes where the property is defined in the parent class and the attribute is applied in the child class?  Googling php 8 attributes inheritance didn't provide anything and thus I expect the answer is no but thought I'd ask.

Thanks

 

EDIT.  Maybe Doctrine does have an attribute version: https://github.com/doctrine/orm/blob/2.9.x/lib/Doctrine/ORM/Mapping/AttributeOverride.php

EDIT2.  Actually don't think so :(

Edited by NotionCommotion
Link to comment
Share on other sites

16 minutes ago, requinix said:

https://3v4l.org/nEML3#v8.0.9

When you redefine a property you redefine the property.

Was just hoping that one could redefine a property without really redefining the property...  Yeah, agree it doesn't make sense but was just hoping.  EDIT.  On a whim, just looked for a parent::addAttribute() method but no such luck.

After I better understand attributes, would it be that difficult to make my own?  I believe this commit implemented most of the attributes used by Doctrine.  Doesn't look like too overwhelming but that is always what I initially think...

Edited by NotionCommotion
Link to comment
Share on other sites

3 hours ago, requinix said:

Personally, I would just copy/paste the annotations from the parent class.

My plan was to copy (or maybe extend it but probably not) the existing annotation class and put it in my space app/Attribute/BlaAossociation.pho and edit it similar to the others.  This reflects your thoughts.  Thanks 

Link to comment
Share on other sites

18 hours ago, requinix said:

Personally, I would just copy/paste the annotations from the parent class.

Maybe I don't understand what you are suggesting.  Copy attributes and not annotations since it appears that both can be used at the same time, right?  And just don't extend the few classes which will have a collection in Tenant and duplicate the properties and methods?

PS  My expectation to simply create a new attribute class might have been a little naïve.

<?php

/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/

namespace App\Mapping;

use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\ORM\Mapping\Annotation;

/**
 * This annotation is used to override association mapping of property for an entity relationship.
 *
 * @Annotation
 * @NamedArgumentConstructor()
 * @Target("ANNOTATION")
 */
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class AssociationOverride implements Annotation
{
    /**
     * The name of the relationship property whose mapping is being overridden.
     *
     * @var string
     */
    public $name;

    /**
     * The join column that is being mapped to the persistent attribute.
     *
     * @var array<\Doctrine\ORM\Mapping\JoinColumn>
     */
    public $joinColumns;

    /**
     * The join table that maps the relationship.
     *
     * @var \Doctrine\ORM\Mapping\JoinTable
     */
    public $joinTable;

    /**
     * The name of the association-field on the inverse-side.
     *
     * @var string
     */
    public $inversedBy;

    /**
     * The fetching strategy to use for the association.
     *
     * @var string
     * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
     */
    public $fetch;

    public function __construct(
        ?string $name = null,
        ?array  $joinColumns = null,
        ?string $joinTable = null,
        ?string $inversedBy = null,
        ?string $fetch = null
    ) {
        $this->name    = $name;
        $this->joinColumns = $joinColumns;
        $this->joinTable = $joinTable;
        $this->inversedBy = $inversedBy;
        $this->fetch = $fetch;
        //$this->debug('__construct',);
    }

    private function debug(string $message, string $file='test.json', ?int $options = null)
    {
        $content = file_exists($file)?json_decode(file_get_contents($file), true):[];
        $content[] = ['message'=>$message, 'object_vars'=>get_object_vars($this), 'debug_backtrace'=>debug_backtrace($options)];
        file_put_contents($file, json_encode($content, JSON_PRETTY_PRINT));
    }
}

 

Link to comment
Share on other sites

Creating your own attribute class will only work if Doctrine supports scanning custom attribute classes. I wouldn't really expect it to.

What I mean by copying is, if you've got something in a parent class with attributes and you want to extend it in a child class, copy it with its attributes and then add whatever else you want to it.

class ParentClass {
	#[FooAttribute]
	protected $foo;
}

class ChildClass extends ParentClass {
	#[FooAttribute]
	#[BarAttribute]
	protected $foo;
}

 

Link to comment
Share on other sites

17 hours ago, requinix said:

Creating your own attribute class will only work if Doctrine supports scanning custom attribute classes. I wouldn't really expect it to.

What I mean by copying is, if you've got something in a parent class with attributes and you want to extend it in a child class, copy it with its attributes and then add whatever else you want to it.

Thanks requinix,

Yes, that was my backup plan and it works as desired.

I incorrectly thought that the annotation class would magically work with attributes if modified appropriately, but now I see other code must be able to apply the appropriate logic based on the attributes.

A little off topic, but one annoyance of using attributes instead of annotations is I don't get an error if I failed to import the attribute class. For instance, this would have thrown a class not found error:

<?php
declare(strict_types=1);
namespace App\Entity;
//use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */
class MyEntity{}

But this will not:

<?php
declare(strict_types=1);
namespace App\Entity;
//use Doctrine\ORM\Mapping\Entity;

#[Entity]
class MyEntity{}

I suspect this is the case because an actual class is not needed per the docs, and when an actual class isn't used, maybe a standard class is used which obviously will not provide the desired results but unfortunately doesn't provide any warning.

Quote

While not strictly required it is recommended to create an actual class for every attribute. In the most simple case only an empty class is needed with the #[Attribute] attribute declared that can be imported from the global namespace with a use statement.

 

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

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.

×
×
  • 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.