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

Model Composition - How to join when one model is part of another

I have an Office model and an Address model. The Office model has an Address model. Using a single query with a join, how can I retrieve the Office model that contains the Address information?

Address Model

<?php
namespace Domain\Model;

use Library\Domain\AbstractModel; // extends Phalcon\Mvc\Model

/**
 * Address
 *
 * @package Domain\Model
 */
class Address extends AbstractModel
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var integer
     */
    public $officeId;

    /**
     * @var string
     */
    public $streetAddress;

    /**
     * @var string
     */
    public $streetAddress2;

    /**
     * @var string
     */
    public $poBox;

    /**
     * @var string
     */
    public $city;

    /**
     * @var string
     */
    public $state;

    /**
     * @var integer
     */
    public $zip;

    /**
     * @var integer
     */
    public $zipPlus4;

    /**
     * Get the source table
     *
     * @return string
     */
    public function getSource()
    {
        return 'addresses';
    }
}

Office Model

<?php
namespace Domain\Model;

use Library\Domain\AbstractModel; // extends Phalcon\Mvc\Model

/**
 * Office
 *
 * @package Domain\Model
 */
class Office extends AbstractModel
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var integer
     */
    public $companyId;

    /**
     * @var integer
     */
    public $regionId;

    /**
     * @var string
     */
    public $fax;

    /**
     * @var string
     */
    public $phone;

    /**
     * Setup once per application load
     */
    public function initialize()
    {
        $this->hasOne('id', '\Domain\Model\Address', 'officeId', array(
            'alias' => 'address'
        ));
    }

    /**
     * Get the source table
     *
     * @return string
     */
    public function getSource()
    {
        return 'offices';
    }
}

Office Mapper

<?php
namespace Domain\Mapper;

use Library\Domain\AbstractMapper;
use Library\Domain\Model\Collection;
use Exception;

/**
 * OfficeMapper
 *
 * Retrieves and persists office models
 *
 * @package Domain\Mapper
 */
class OfficeMapper extends AbstractMapper
{

    /**
     * Finds a office by ID
     *
     * @param id
     * @return \Domain\Model\Office
     */
    public function findOneById($id)
    {
        $office = $this->getModelManager() // Returns instance of \Phalcon\Mvc\Model\Manager
            ->createBuilder()
            ->columns('[\Domain\Model\Office].*')
            ->from('\Domain\Model\Office')
            ->join('\Domain\Model\Address', '[\Domain\Model\Office].id = [\Domain\Model\Address].officeId')
            ->where('[\Domain\Model\Office].id = :id:', array('id' => $id))
            ->getQuery()
            ->getSingleResult();

        var_dump($office); // I would like this to include $office->address information
        var_dump($office->address->toArray()); // This displays the address information, but also creates another query
        exit;

        return $office;
    }
}

Ideally, I'd like the Office model to have address as a member field, and not just accessible via a magic getter. Am I trying to force something that wasn't meant to be with the Phalcon ORM? Below is what I would like my Office model to look like.

class Office extends AbstractModel
{
    /**
     * @var integer
     */
    public $id;

    /**
     * @var integer
     */
    public $companyId;

    /**
     * @var integer
     */
    public $regionId;

    /**
     * @var \Domain\Model\Address
     */
    public $address; // <- I'd like this to be part of the model's definition

    /**
     * @var string
     */
    public $fax;

    /**
     * @var string
     */
    public $phone;

     /**
     * Setup once per application load
     */
    public function initialize()
    {
        $this->hasOne('id', '\Domain\Model\Address', 'officeId', array(
            'alias' => 'address'
        ));
    }

    /**
     * Get the source table
     *
     * @return string
     */
    public function getSource()
    {
        return 'offices';
    }
}

Just select the other model too:

->columns('[Domain\Model\Office].*', '[Domain\Model\Address].*')
->from('Domain\Model\Office')
->join('Domain\Model\Address', '[\Domain\Model\Office].id = [\Domain\Model\Address].officeId')


3.6k

Thanks for your reply. This returns a Phalcon\Mvc\Model\Row composed of both a Domain\Model\Office and Domain\Model\Address as separate members of the Phalcon\Mvc\Model\Row.

Although this does return the data for everything I need, I would like Domain\Model\Office to be returned with an Domain\Model\Address as a member field, since this object will be returned as a response to an API resource and needs to matchup with the API specs.

It seems like the best way I can do this, is to create a business object for the Office which contains an Address as a member field, and can be populated from an Phalcon\Mvc\Model\Row object. Any suggestions are welcome.