Aggregation of CodeIgniter 3 objects

I am having a question related to object aggregation using CodeIgniter 3.1.9.

I have the following method in model :

public function get_tickets($where = array())
{
    $this->db->select('*');
    $this->db->from('tickets');
    $this->db->where($where);

    return $this->db->get()->result('Ticket');
}

Which makes a simple query in the database and returns a array with the instantiated objects of a class located within /libraries/ called Ticket that is already loaded in memory. I followed this example.

However, objects of Class Ticket have another object of another aggregate class, in the case, the Class Solicitante.

+-----------------------------+               +-------------------+
| Ticket                      |               | Solicitante       |
+-----------------------------+               +-------------------+
| - id          : integer     |               | - id    : integer |
| - solicitante : Solicitante |  0..*         | - nome  : string  |
| - titulo      : string      |<>-------------| - email : string  |
| - descricao   : string      |               | - senha : string  |
| - prazo       : DateTime    |               | - ativo : string  |
| - criado_em   : DateTime    |               +-------------------+
+-----------------------------+

The Class Ticket already has the attribute private $solicitante and the getter and setter for this attribute implemented:

public function set_solicitante(Solicitante $solicitante)
{
    $this->solicitante = $solicitante;
}

public function get_solicitante()
{
    return $this->solicitante;
}

The tables in the database look like this:

+----+------------------------------+    +----+------------------------------+
|    | tb_tickets                   |    |    | tb_solicitantes              |
+----+------------------------------+    +----+------------------------------+
| PK | id INT (11) AUTO_INCREMENT   |    | PK | id INT (11) AUTO_INCREMENT   |
| FK | id_solicitante INT (11)      |    |    | nome VARCHAR (50) NOT NULL   |
|    | titulo VARCHAR (50) NOT NULL |    |    | email VARCHAR (50) NOT NULL  |
|    | descricao TEXT NOT NULL      |    |    | senha VARCHAR (100) NOT NULL |
|    | prazo DATETIME NOT NULL      |    |    | ativo CHAR (1) DEFAULT 'S'   |
|    | criado_em DATETIME NOT NULL  |    +-----------------------------------+
+----+------------------------------+

I thought of implementing a constructor in class Ticket and calling a method from another model that will instantiate the object $solicitante, for example. But I think this ends up being unworkable in terms of performance. For to each ticket, another query would have to be made.

Maybe using INNER JOIN in the get_tickets() method is the best solution, but I don't know if it is possible to already instantiate the Ticket and the Solicitante that aggregate at once.

Can anyone help me? I appreciate it now!

Author: novic, 2019-01-10

2 answers

First, you would create two models that represent each table, in your specific case Tickets and Solicitantes as follows:

Inside the folder application creates another folder with the name entities (the name can be of your preference), and a base file with the name Base.php with the following code:

<?php

abstract class Base
{
    public function __get($name)
    {
        return $this->$name;
    }

    public function __set($name, $value)
    {
        $this->$name = $value;
    }
}

With this file of base the two classes:

<?php

class Solicitante extends Base
{
    protected $id;
    protected $nome;
    protected $email;
    protected $senha;
    protected $ativo;

    public function getId()
    {
        return $this->id;
    }
    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }
    public function getNome()
    {
        return $this->nome;
    }
    public function setNome($nome)
    {
        $this->nome = $nome;
        return $this;
    }
    public function getEmail()
    {
        return $this->email;
    }
    public function setEmail($email)
    {
        $this->email = $email;
        return $this;
    }
    public function getSenha()
    {
        return $this->senha;
    }
    public function setSenha($senha)
    {
        $this->senha = $senha;
        return $this;
    }
    public function getAtivo()
    {
        return $this->ativo;
    }
    public function setAtivo($ativo)
    {
        $this->ativo = $ativo;
        return $this;
    }

}

And

<?php

class Ticket extends  Base
{
    protected $id;
    protected $id_solicitante;
    protected $titulo;
    protected $descricao;
    protected $prazo;
    protected $criado_em;
    protected $solicitante;

    public function getId()
    {
        return $this->id;
    }
    public function setId($id)
    {
        $this->id = $id;
        return $this;
    }
    public function getIdSolicitante()
    {
        return $this->id_solicitante;
    }
    public function setIdSolicitante($id_solicitante)
    {
        $this->id_solicitante = $id_solicitante;
        return $this;
    }
    public function getTitulo()
    {
        return $this->titulo;
    }
    public function setTitulo($titulo)
    {
        $this->titulo = $titulo;
        return $this;
    }
    public function getDescricao()
    {
        return $this->descricao;
    }
    public function setDescricao($descricao)
    {
        $this->descricao = $descricao;
        return $this;
    }
    public function getPrazo()
    {
        return $this->prazo;
    }
    public function setPrazo($prazo)
    {
        $this->prazo = $prazo;
        return $this;
    }
    public function getCriadoEm()
    {
        return $this->criado_em;
    }
    public function setCriadoEm($criado_em)
    {
        $this->criado_em = $criado_em;
        return $this;
    }

    public function getSolicitante()
    {
        return $this->solicitante;
    }
    public function setSolicitante($solicitante)
    {
        $this->solicitante = $solicitante;
        return $this;
    }
}

To upload these classes you have to configure the folders / file vendor/autoload.php (inside the config.php in the $config['composer_autoload'] = './vendor/autoload.php'; key) for loading, open the composer.json file that is in the root of your project and configure the psr-4:

"autoload": {
    "psr-4": {
        "": "application/entities/"
    }
}

Give the command php composer.phar dump to upload these settings and consequently the classes that were created and the new ones that you can later create.

Preparing the models: after this time create the two files responsible for fetching information from their tables within the folder application/models:

<?php

class Ticket_model extends CI_Model
{
    public function find($id, $load_relationship = false)
    {
        $this->db->select('*');
        $this->db->from('tb_tickets');
        $result = $this->db->get()->row(0, 'Ticket');
        if ($load_relationship)
        {
            $this->getLoadRelationshipSolicitanteOne($result);
        }
        return $result;
    }

    public function all($load_relationship = false)
    {
        $this->db->select('*');
        $this->db->from('tb_tickets');
        $result = $this->db->get()->result('Ticket');
        if ($load_relationship)
        {
            $this->getLoadRelationshipSolicitanteAll($result);
        }
        return $result;
    }

    protected function getLoadRelationshipSolicitanteAll($result)
    {
        if (!$this->load->is_loaded('Solicitante_model'))
        {
            $this->load->model('Solicitante_model');
        }
        $values = array_map(function ($o) {
            return (int)$o->id;
        }, $result);

        $solicitantes = $this->Solicitante_model->getAllSolicitantes($values);

        return array_map(function ($c) use ($solicitantes) {
            $res = array_values(array_filter($solicitantes, function ($s) use ($c) {
                return $c->id_solicitante == $s->id;
            }));
            if ($res && count($res) == 1){
                $c->solicitantes = $res[0];
            }
            return $c;
        }, $result);

        return $result;
    }

    protected function getLoadRelationshipSolicitanteOne($result)
    {
        if (!$this->load->is_loaded('Solicitante_model'))
        {
            $this->load->model('Solicitante_model');
        }
        $result->solicitante = $this->Solicitante_model->find($result->id_solicitante);
        return $result;
    }
}

And

<?php

class Solicitante_model extends CI_Model
{
    public function find($id)
    {
        $this->db->select('*');
        $this->db->from('tb_solicitantes');
        $result = $this->db->get()->row(0, 'Solicitante');
        return $result;
    }

    public function getAllSolicitantes(array $values = array())
    {
        $this->db->select('*');
        $this->db->from('tb_solicitantes');
        $this->db->where_in('id', $values);
        $result = $this->db->get()->result();
        return $result;
    }
}

In that part he was in charge that each model loads its information, even if within the model Ticket_model have to be loaded the model Solicitante_model and it is good that it is done like this, because any change the others models will receive it clearly.

how to use:

1 Ticket

$this->load->model('Ticket_model');
$ticket = $this->Ticket_model->find(1, true);

result:

Ticket Object
(
    [id:protected] => 1
    [id_solicitante:protected] => 1
    [titulo:protected] => Title 1
    [descricao:protected] => Desc 1
    [prazo:protected] => 2019-01-01 00:00:00
    [criado_em:protected] => 2019-01-01 00:00:00
    [solicitante:protected] => Solicitante Object
        (
            [id:protected] => 1
            [nome:protected] => Stack
            [email:protected] => [email protected]
            [senha:protected] => 102030
            [ativo:protected] => 1
        )

)

Todos Ticket

$this->load->model('Ticket_model');
$it = $this->Ticket_model->all(true);

Result:

Array
(
    [0] => Ticket Object
        (
            [id:protected] => 1
            [id_solicitante:protected] => 1
            [titulo:protected] => Title 1
            [descricao:protected] => Desc 1
            [prazo:protected] => 2019-01-01 00:00:00
            [criado_em:protected] => 2019-01-01 00:00:00
            [solicitante:protected] => stdClass Object
                (
                    [id] => 1
                    [nome] => Stack
                    [email] => [email protected]
                    [senha] => 102030
                    [ativo] => 1
                )

        )

    [1] => Ticket Object
        (
            [id:protected] => 2
            [id_solicitante:protected] => 2
            [titulo:protected] => Title 2
            [descricao:protected] => Desc 2
            [prazo:protected] => 2019-01-02 00:00:00
            [criado_em:protected] => 2019-01-02 00:00:00
            [solicitante:protected] => stdClass Object
                (
                    [id] => 2
                    [nome] => Web
                    [email] => [email protected]
                    [senha] => 102030
                    [ativo] => 1
                )

        )

)

observation: it can only simplify you don't create the entities and only work with arrays as responses from all your models, but, this example can be done clearly by taking out the first part.

 2
Author: novic, 2019-01-11 18:59:02

You can insert the following structure inside the ticket class constructor:

public function __construct($paramsSolicitante) {

    if (isset($paramsSolicitante)) {
        $solicitante = $this->load->library('Solicitante', $this->$paramsSolicitante); 
    }

}
 0
Author: Vinicius Gabriel, 2019-01-10 17:26:06