We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Map record to target model

Does phalcon ORM supports mapping a record to target model depending on a field value from the record ? Example:

And I have two models: Physical and Downloadable both extends from the parent model Product. How can Phalcon ORM map first record to Physical model and the second record on Downloadable model when fetching records using Product::find() ?

So this would be something like a submodel. I think I have seen something like that in Doctrine but not sure.

If both those models point to the same table then you will have to do some work with extending the find and findFirest methods to add the specific conditional for each of your use cases. I am not sure though how this would work with relationships and related data from other tables.

edited Apr '19

Yes, it exists in doctrine and they call it Single Table Inheritance. I thought I will find a workaround in Phalcon to do this, but hopeless.

It would be great if Phalcon supports this in find() and findFirst() methods, depending on discriminator column.

So this would be something like a submodel. I think I have seen something like that in Doctrine but not sure.

If both those models point to the same table then you will have to do some work with extending the find and findFirest methods to add the specific conditional for each of your use cases. I am not sure though how this would work with relationships and related data from other tables.

I recall discussing about this years ago but we never did something solid to implement it. I believe it is the way the framework is structured, with Active Record but don't take my word for it - not 100% sure.

What you could do however is leverage relationships with parameters so that you can get what you need.

https://docs.phalcon.io/4.0/pt-br/db-models-relationships.html#relationships-with-parameters

Could be a solution for you.

I already opened a NFR for this, and hope that Phalcon team put it under spots to vote for it. https://github.com/phalcon/cphalcon/issues/14007

From my side, I vote for it :)

The only possible solution I can think of is to wrap ResultSet and your desire models.

In Product::find(), don't return the result of parent::find(). Instead, store it in a variable and pass it to a new object, along with the discriminator information:

$results = parent::find();
return new MultiTypeResultSet(
    $results, // Original result set
    'product_type', // Discriminator column
    [ // map of value to class
        'physical'     =>'Namespace\For\Product\Physical',
        'downloadable' =>'Namespace\For\Product\Downloadable'
    ]
);

You could probably even abstract that out a little more and put that type of info in initialize().

I believe it has to be done this way because Phalcon will return \Phalcon\Mvc\Model\Resultset and there's nothing we can do to change that so we can't properly extend, only wrap.

MultiTypeResultSet can then define magic __call() and __callStatic() methods that simply call the requested method on the ResultSet. The real magic is done by overriding current(). In that method, you call parent::current(), but then check the value of the passed discriminator column - product_type - and create a new model object based on the mapping that was passed. This will, unfortunately, require 2 queries - one to get the original Product model, another to get the Physical or Downloadable model. Alternatively, you could write code in Physical and Downloadable to import values from a passed Product object, into themselves.

It's a bit dirty, but I think the concept is functional at least.

Very thanks for your participation in this discussion.

My goal is to reduce number of queries and to get mapped results in the same time, I think Doctrine can do the job for now, but I will lose Phalcon ORM usability.

The only possible solution I can think of is to wrap ResultSet and your desire models.

In Product::find(), don't return the result of parent::find(). Instead, store it in a variable and pass it to a new object, along with the discriminator information:

$results = parent::find();
return new MultiTypeResultSet(
   $results, // Original result set
   'product_type', // Discriminator column
   [ // map of value to class
       'physical'     =>'Namespace\For\Product\Physical',
       'downloadable' =>'Namespace\For\Product\Downloadable'
   ]
);

You could probably even abstract that out a little more and put that type of info in initialize().

I believe it has to be done this way because Phalcon will return \Phalcon\Mvc\Model\Resultset and there's nothing we can do to change that so we can't properly extend, only wrap.

MultiTypeResultSet can then define magic __call() and __callStatic() methods that simply call the requested method on the ResultSet. The real magic is done by overriding current(). In that method, you call parent::current(), but then check the value of the passed discriminator column - product_type - and create a new model object based on the mapping that was passed. This will, unfortunately, require 2 queries - one to get the original Product model, another to get the Physical or Downloadable model. Alternatively, you could write code in Physical and Downloadable to import values from a passed Product object, into themselves.

It's a bit dirty, but I think the concept is functional at least.

To be honest, I believe it's highly irregular to have multiple models pointing to the same table. Why not just combine Physical and Downloadable into 1 model?

Each one has its own attributes, on the other hand, they are common in much attributes.

To be honest, I believe it's highly irregular to have multiple models pointing to the same table. Why not just combine Physical and Downloadable into 1 model?