Jump to content

Creating new objects with sub-objects when using an ORM


NotionCommotion

Recommended Posts

I have the following three tables (and also delta_point and hist_point which are purposely left out and just mentioned for context).  Before going down the Doctrine ORM long curvy road, aggrType was just used to restrict possible values so that I didn't need to use MySQL's ENUM, but now I am thinking there may be value to making it an entity and include some methods (and a second reason because I still can't figure out how to make Doctrine create a FK constraint yet not populate the parent entity with a AggrType object).

point
-id (int PK)
-data
-dType /* descriminator.  Options are AggregatePoint, DeltaPoint, and HistoricPoint */

aggr_point
-id (int PK, FK to point)
-aggrType (char FK to aggrType)
-data

aggrType
-type (char PK) /* there are about five types */

I have a service to create a new point based on the provided desired type and user data.  I don't like the switch statement to determine which type of point to create, but don't know a better way.  Also my entity is responsible to provide the appropriate validation rules for the given point type which might not be considered proper but it seems to work for me.  Any constructive criticism is welcomed.

<?php
namespace NotionCommotion\PointMapper\Api\Point;
use NotionCommotion\PointMapper\Domain\Entity\Point;
class PointService
{
    public function create(string $type, array $params):integer {
        $class=[
            'aggr'=>'AggregatePoint',
            'delta'=>'DeltaPoint',
            'hist'=>'HistoricPoint',
        ][$type];
        //$class='Point\\'.$class;  //This doesn't seem to be possible and I needed to use the fully qualifed name without the short cut used namespace.
        $class='\\Greenbean\Datalogger\Domain\Entity\Point\\'.$class;
        $point=new $class();
        $validator=$this->getValidator($point);
        $params=$validator->sanitize($params);
        $validator->validate($params);
        $point->setAccount($this->account);
        $point->populate($params, $this->em); //Will be addressed later
        $this->em->persist($point);
        $this->em->flush();
        $point->setIdPublic($this->em->getRepository(Point\Point::class)->getPublicId($point->getId()));
        return $point->getIdPublic();
    }
}

Now, this is the part I am struggling with the most.  I only have one service to create any type of point, so I do wish to put a bunch of if/thens in it and instead move logic to the individual point type being created.  The AggrPoint in question requires a sub-point and I don't think it is appropriate to inject the entity manager into a point entity and as an alternative solution find an existing point which is associated with the account which resides in the new AggrPoint.  Maybe not appropriate, but it works.  This type of point also has a sub-object AggrType as discussed at the very beginning of this post, so I first attempt to create a new one from scratch, but that doesn't work and I need to set $aggrType with an existing one.  As an alternative, I passed the entity the entity manager, but as stated I think this is a bad idea.

Any recommendations where to go from here?  Thank you

namespace NotionCommotion\PointMapper\Domain\Entity\Point;
class AggregatePoint extends Point
{
    public function populate(array $params, \Doctrine\ORM\EntityManager $em):\NotionCommotion\PointMapper\Domain\Entity\Entity{
        $criteria = \Doctrine\Common\Collections\Criteria::create()
        ->where(\Doctrine\Common\Collections\Criteria::expr()->eq("id_public", $params['pointId']));
        if(!$subpoint = $this->getAccount()->getPoints()->matching($criteria)->current()){
            throw new \Exception("Invalid subpoint ID $params[pointId]");
        }
        $this->setSubpoint($subpoint);

        //This won't work since I must use an existing AggregateType and not create a new one
        $aggrType=new AggregateType();
        $aggrType->setType($params['aggrType']);

        //This just seems wrong and I shouldn't be passing the entity manager to an entity
        $aggrType=$em->getRepository(AggregateType::class)->findOneBy(['type'=>$params['aggrType']]);

        $this->setAggregateType($aggrType);

        unset($params['pointId'], $params['aggrType']);
        // parent::populate($params) not shown, but it basically calles appropriate setters based on the property name
        return parent::populate($params);
    }

    public function getValidatorRules():array
    {
        return $this->filesToArr(['/point/base.json', '/point/aggr.json']);
    }

}

 

Link to comment
Share on other sites

Another example of the same issue is TimeUnit.  I have a table which contains only eight records which correspond to seconds, minutes, hours, days, weeks, months, quarters, and years.  This table has two purposes: 1) Create the list of options to present to the user. 2) Provide referential integrity.

I can make AggrPoint#timeUnit either a primitive type (integer or string) or an object.  Both approaches seem to have merit.  What would you do?  If a string, how can Doctrine be used to generate the foreign key constraint?  If an object, how should an instance of one of these eight TimeUnit objects be obtained when creating a new AggrPoint?

Link to comment
Share on other sites

I think I answered half of my questions.  If AggrPoint#aggrType should be an object (which I believe it should be), then I should use a factory pattern and have each factory retrieve the correct instance of AggrType and then either inject it in AggrPoint or use AggrPoint's setAggrType() method.

I am not really sure whether TimeUnit should be an object, and still need to figure out how to prevent Doctrine from generating an object but still keep the foreign key constant.

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.