Jump to content

Modelling two entities who's intersection identifies a third entity


NotionCommotion

Recommended Posts

I am using PHP and Doctrine to store and manipulate Highchart objects such as the following:

{
    "chart": {
        "type": "bar"
    },
    "xAxis": {
        "categories": ["Africa", "America", "Asia", "Europe", "Oceania"]
        }
    },
    "series": [{
        "name": "Year 1800",
        "data": [107, 31, 635, 203, 2]
    }, {
        "name": "Year 1900",
        "data": [133, 156, 947, 408, 6]
    }, {
        "name": "Year 2000",
        "data": [814, 841, 3714, 727, 31]
    }, {
        "name": "Year 2016",
        "data": [1216, 1001, 4436, 738, 40]
    }]
}

My DB schema is:

BarChart
-id (PK)
-name (string)

BarChartSerie
-id (PK)
-barChartId (FK)
-name (string)
-position (int)

BarChartCategory
-id (PK)
-barChartId (FK)
-name (string)
-position (int)

BarChartNode
-barChartSerie (FK)
-barChartCategory (FK)
-value (decimal)

The chart entity has a collection of serie and category, and  the serie and category entities both contain a reference to the shared node.

I wish to clone the chart entity and thus use __clone() to set the chart, category, and serie entity's' PK to NULL, however, am running into issues as the node entity still has the original entitiy's PKs.

I think maybe I should be creating the entities differently.

Any suggestions how these objects should be created?  Maybe instead have my series contain a collection of nodes and have each of these nodes contain a reference to the applicable category?

Thanks

Link to comment
Share on other sites

On 9/10/2019 at 6:05 PM, NotionCommotion said:

I wish to clone the chart entity and thus use __clone() to set the chart, category, and serie entity's' PK to NULL, however, am running into issues as the node entity still has the original entitiy's PKs.

Then your cloning is faulty.

1. If a chart "has a" set of series and categories, then a (deep) clone of the chart should also clone those sets.
2. A clone of a database entity should reset the object to a "new" state - ie, unset the key, mark the object as "new", and/or affect whatever else your abstraction library uses.

Link to comment
Share on other sites

17 hours ago, requinix said:

Then your cloning is faulty.

1. If a chart "has a" set of series and categories, then a (deep) clone of the chart should also clone those sets.
2. A clone of a database entity should reset the object to a "new" state - ie, unset the key, mark the object as "new", and/or affect whatever else your abstraction library uses.

Yes, probably.

What do you mean by a "(deep)" clone?

This is for a BarChart.  The parent object Chart as well as the Serie and Category objects use __clone() to set their PK to NULL.

Where I am having problems is with the Node objects which are in a collection in both Serie and Category.  I can clone each node in a series, but then I still have old nodes in the collection and I can't just clone those nodes in the collection because they need to be the same instances as in the series.  Same is true if I do the categories first and then the series.  So, instead I tried to replace the series and categories in each node with the newly cloned series and categories, but that doesn't change the node entities so no good.

Any advise?  Thank yuo

 

    public function __clone()
    {
        if ($this->getId()) {
            parent::__clone();

            $seriesArray = new ArrayCollection();
            foreach($this->series as $serie) {
                $serieClone = clone $serie;
                //$serieClone->setChart($this);
                //$nodesArray = new ArrayCollection();
                foreach($serieClone->getNodes() as $node) {
                    //$nodeClone = clone $node;
                    //$nodesArray->add($nodeClone);
                    $node->replaceSerie($serieClone);
                }
                $seriesArray->add($serieClone);
            }
            $this->series=$seriesArray;

            $categoriesArray = new ArrayCollection();
            foreach($this->categories as $category) {
                $categoryClone = clone $category;
                //$categoryClone->setChart($this);
                //$nodesArray = new ArrayCollection();
                foreach($categoryClone->getNodes() as $node) {
                    //$nodeClone = clone $node;
                    //$nodesArray->add($nodeClone);
                    $node->replaceCategory($categoryClone);
                }
                $categoriesArray->add($categoryClone);
            }
            $this->categories=$categoriesArray;
        }
    }

 

Link to comment
Share on other sites

18 minutes ago, NotionCommotion said:

What do you mean by a "(deep)" clone?

A deep copy/clone is when you clone an object and all the other objects "in" it. What you are doing now, where your __clone also clones the series and categories in it.

A shallow copy is when you don't do that.

18 minutes ago, NotionCommotion said:

Where I am having problems is with the Node objects which are in a collection in both Serie and Category.  I can clone each node in a series, but then I still have old nodes in the collection and I can't just clone those nodes in the collection because they need to be the same instances as in the series.  Same is true if I do the categories first and then the series.  So, instead I tried to replace the series and categories in each node with the newly cloned series and categories, but that doesn't change the node entities so no good.

You must clone nodes. You have to. Can't avoid that. So anything you come up with that doesn't clone the nodes will not work correctly.

Cloning the nodes according to the series won't let you update the category. Cloning the nodes according to the categories won't let you update the series. So come up with another method: one that lets you discover all the nodes and update their series and category correctly. Hint: as you clone the series and categories, keep track of what which objects are taking the place of which old objects...

Link to comment
Share on other sites

The following works as desired.  Thank you for keeping me from chasing unicorns.

This what you had in mind?  One thing I might rather do different is instead of cloning the nodes in the chart entity, clone them in the chart's serie entity and set the newly cloned notes in the category entity.  The problem, however, is using SplObjectStorage in the chart as I have done, the category does not have access to these newly cloned nodes.  Should I abandon trying to do so?

    public function __clone()
    {
        parent::__clone();

        $s = new \SplObjectStorage();
        $seriesArray = new ArrayCollection();
        foreach($this->series as $serie) {
            $serieClone = clone $serie;
            $serieClone->setChart($this);
            $nodesArray = new ArrayCollection();
            foreach($serieClone->getNodes() as $node) {
                $nodeClone = clone $node;
                $nodeClone->setSerie($serieClone);
                $s[$node]=$nodeClone;
                $nodesArray->add($nodeClone);
            }
            $seriesArray->add($serieClone);
        }
        $this->series=$seriesArray;

        $categoriesArray = new ArrayCollection();
        foreach($this->categories as $category) {
            $categoryClone = clone $category;
            $categoryClone->setChart($this);
            $nodesArray = new ArrayCollection();
            foreach($categoryClone->getNodes() as $node) {
                $nodeClone = $s[$node];
                $nodeClone->setCategory($categoryClone);
                $nodesArray->add($nodeClone);
            }
            $categoriesArray->add($categoryClone);
        }
        $this->categories=$categoriesArray;
    }

When setting the parent entity which belongs in a collection, I am also adding to the collection as shown, and do the same for the CategoryChartSerie's and CategoryChartCategory's.  Is doing so good practice?  Is it best to set the reciprocating properties always in the child entity as I have done or instead do it in the parent entity?

<?php
class CategoryChartNode
{
    private $serie;
    private $category;
    private $point;

    public function setSerie(CategoryChartSerie $serie)
    {
        $serie->addNode($this);
        $this->serie = $serie;
        return $this;
    }

    //...

}

When cloning, I often see the following check before cloning.  Recommended?

    public function __clone()
    {
        if ($this->id) {
            $this->id=null;
        }
    }
 

Lastly, you stated that the nodes must be cloned.  While I do not disagree, is the reason this is necessary that Doctrine must be internally using the node's hash to keep track of them?

Thanks again for your help.

Edited by NotionCommotion
Link to comment
Share on other sites

2 hours ago, NotionCommotion said:

This what you had in mind?

Yup. Personally I would also be tracking the nodes as members of the chart itself, but that's besides the point.

2 hours ago, NotionCommotion said:

One thing I might rather do different is instead of cloning the nodes in the chart entity, clone them in the chart's serie entity and set the newly cloned notes in the category entity.  The problem, however, is using SplObjectStorage in the chart as I have done, the category does not have access to these newly cloned nodes.  Should I abandon trying to do so?

Sounds too complicated.

Speaking of complicated, you could create a NodeCollection object that offers iteration by category and series. The series and category objects then use that collection instead of managing their nodes manually. I'm leaning towards this.

2 hours ago, NotionCommotion said:

When setting the parent entity which belongs in a collection, I am also adding to the collection as shown, and do the same for the CategoryChartSerie's and CategoryChartCategory's.  Is doing so good practice?  Is it best to set the reciprocating properties always in the child entity as I have done or instead do it in the parent entity?

Having links on both ends makes it harder to move things around, as you have to remember to update both links, but it's not too unreasonable to do so.

2 hours ago, NotionCommotion said:

When cloning, I often see the following check before cloning.  Recommended?

    public function __clone()
    {
        if ($this->id) {
            $this->id=null;
        }
    }

Meh. If the id isn't set then there's no operations to clear it, but instead it adds operations to check the value. More lines of code for no net benefit.

2 hours ago, NotionCommotion said:

Lastly, you stated that the nodes must be cloned.  While I do not disagree, is the reason this is necessary that Doctrine must be internally using the node's hash to keep track of them?

The nodes must be cloned because they can/should only belong to one chart. No cloning means you are reusing them in two places. Same reason you have to clone the series and categories.

  • Like 1
Link to comment
Share on other sites

20 minutes ago, requinix said:

Speaking of complicated, you could create a NodeCollection object that offers iteration by category and series. The series and category objects then use that collection instead of managing their nodes manually. I'm leaning towards this.

Sounds like a good idea.  These two interdependent collections have brought complication and it would be nice to have it encapsulated elsewhere.  Do you envision this NodeCollection object as being an entity with two collection properties?  Or maybe it is just a collection of series, but it has a getCategories() method which iterates over the series and creates an array of categories?

I was implementing this __clone() method for a pie chart, and accidentally forgot to replace the chart's series with the new collection of clones, and was surprised that it worked.  I then commented out doing so for the bar chart I previously showed, and it too worked without errors.  While it works, I would still like to understand why it works.  I also included my chart setter for series and category is the same.  Any ideas?
 

    public function __clone()
    {
        parent::__clone();

        $s = new \SplObjectStorage();
        $seriesArray = new ArrayCollection();
        foreach($this->series as $serie) {
            $serieClone = clone $serie;
            $serieClone->setChart($this);
            $nodesArray = new ArrayCollection();
            foreach($serieClone->getNodes() as $node) {
                $nodeClone = clone $node;
                $nodeClone->setSerie($serieClone);
                $s[$node]=$nodeClone;
                $nodesArray->add($nodeClone);
            }
            $seriesArray->add($serieClone);
        }
        //$this->series=$seriesArray;

        $categoriesArray = new ArrayCollection();
        foreach($this->categories as $category) {
            $categoryClone = clone $category;
            $categoryClone->setChart($this);
            $nodesArray = new ArrayCollection();
            foreach($categoryClone->getNodes() as $node) {
                $nodeClone = $s[$node];
                $nodeClone->setCategory($categoryClone);
                $nodesArray->add($nodeClone);
            }
            $categoriesArray->add($categoryClone);
        }
        //$this->categories=$categoriesArray;
    }
<?php

class CategoryChartSerie
{
    public function setChart(CategoryChart $chart)
    {
        $chart->addSeries($this);
        $this->chart = $chart;
        return $this;
    }
    //...
}

 

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.