Use of methods in php object orientation

6

Hello, I have the following question:

In this example:

class produtos
{
    public categorias = [];
}

Categories is a vector, as you can see. Its structure is as follows:

$categorias["tvs"][0] = "aqui o modelo da tv"; 
$categoria["pcs"][0] = "aqui o nome do pc";

I wanted to know how to create a method to feed this vector by identifying the category by method. Example: I want to add a TV on tvs like this:

$this->tvs()->add("nome da tv");

Already a pc would be as follows:

$this->pcs()->add("nome do pc");

I've seen this in practice on some plugins, but I have no idea how. Can you give me a hand? I hope I've been able to explain more or less the idea ...

Note: I need to add () a method because I'll use several parameters.

    
asked by anonymous 27.09.2016 / 18:01

2 answers

8

If you do not know the category names before they are created, you can use the __ call to make the creation dynamic.

An example:

class Categorias
{
    protected $name;
    private $prod;

    public function __construct($name, $prod)
    {
        $this->name = $name;
        $this->prod = $prod;
    }

    public function add($value)
    {
        $this->prod->categorias[$this->name][] = $value;
    }
}

class Produtos
{
    public $categorias = array();

    public function __call($name, $arguments)
    {
        return (new Categorias($name, $this));
    }
}

$p = new Produtos();
$p->tvs()->add("teste");
$p->pcs()->add("nome do pc");

var_dump($p->categorias);

In the code above, we created a class called categorias , which basically will be responsible for filling the category vector of class produtos .

The __call method is used when some non-existent method of the object is called. That is, when categories, which you do not know yet, are called, the __call method is invoked and then we pass the call to a new instance of the categorias class.

If you know which categories can be used, the second @WallaceMaxters option would work fine: Sending the existing categories in the constructor of the produtos class and adding a check within __call to check if the category exists before forwarding the responsibility.

Translating to form I wrote:

class Categorias
{
    protected $name;
    private $prod;

    public function __construct($name, $prod)
    {
        $this->name = $name;
        $this->prod = $prod;
    }

    public function add($value)
    {
        $this->prod->categorias[$this->name][] = $value;
    }
}

class Produtos
{
    public $categorias = array();

    public function __construct($categorias = [])
    {
        foreach ($categorias as $categoria) {
            $this->categorias[$categoria] = [];
        }       
    }

    public function __call($name, $arguments)
    {
        if (!array_key_exists($name, $this->categorias)) {
            // retorna erro.
        }
        return (new Categorias($name, $this));
    }
}

$p = new Produtos(["tvs", "pcs"]);
$p->tvs()->add("teste");
$p->pcs()->add("nome do pc");

var_dump($p->categorias);

The categorias class receives the instance of the produtos class as its second argument in its constructor, because if it needs to use some product property to execute the actions inside the add , it will have the object available to it. p>     

27.09.2016 / 18:53
3

To do such an action above, it would be necessary for tvs and pcs to return a objeto that would have the add method.

First choice

I do not know if it would be best for you to use a method only to return an object that does this, but perhaps it would be interesting to define a method for each action.

See:

class Produtos
{
    public categorias = [
        'tvs' => [],
        'pcs' => []
    ];

    public function addTv($tv)
    {

        $this->categorias['tvs'][] = $tv;
        return $this;
    }

    public function addPc($pc)
    {
        $this->categorias['pcs'][] = $pc;    
        return $this;
    }
}

$produtos = new Produtos;

$produtos->addPc('positivo');
$produtos->addTv('lg');

Second option

To do exactly the way you want, you would have to have another object responsible for storing the data. In this case, I will name the classes most appropriately to separate responsibilities.

See:

class Produtos
{
    protected $produtosCategorias = [];

    public function __construct(array $categorias = [])
    {
        foreach ($categorias as $categoria) {
            $this->produtosCategorias[$categoria] = new ProdutosCategoria($categoria);
        }
    }

    /**
     * Usamos o call para acessar os índices do array como se fosse método
     * 
     * */
    public function __call($nomeCategoria, $argumentos)
    {
        if (isset($this->produtosCategorias[$nomeCategoria])) {
            return $this->produtosCategorias[$nomeCategoria];
        }
        throw new \BadMethodCallException("Método {$nomeCategoria} não existe");
    }
}


class ProdutosCategoria
{

    protected $nomeCategoria;
    protected $produtos = [];

    public function __construct($nomeCategoria)
    {
        $this->nomeCategoria = $nomeCategoria;
    }

    public function add($produto)
    {
        if (in_array($produto, $this->produtos)) {
            throw new UnexpectedValueException('Produto já foi adicionado');
        }

        $this->produtos[] = $produto;
        return $this;
    }
}

$produtos = new Produtos(['tvs', 'pcs']);

$produtos->tvs()->add('LG');
$produtos->pcs()->add('Positivo');

Take a look here to learn more about the __call method:

Other references:

27.09.2016 / 18:42