Use of methods in php object orientation

Asked

Viewed 221 times

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 the method. Example: I want to add a TV to 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 in some plugins, but I have no idea how to do it. Can you give me a hand? I hope I was able to explain more or less the idea...

obs.: I need add() to be a method because I will use several parameters.

2 answers

8


If you do not know the names of the categories before they are created, you can use the method __call to leave the dynamic creation.

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 create a class called categorias, which will basically be responsible for filling the class category vector produtos.

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

If you know which categories can be used, the second @Wallacemaxters option would work well: Sending existing categories in the class constructor produtos and adding a check inside the __call to check if the category exists before passing on the responsibility.

Translating to the way 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 class categorias takes the class instance as the second argument in its constructor produtos, because if you need to use some property of the product to perform the actions within the add, shall have the object available for.

  • 1

    We think almost the same thing, hehe. + 1

3

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

First option

I don’t know if it would be more appropriate for you to use a method just to return an object that does this, but perhaps it would be interesting to define a method for each action.

Behold:

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 it exactly the way you want it, you would have to have another object responsible for storing the data. In that case, I will name the classes in the most appropriate way to separate the responsibilities.

Behold:

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 method __call:

Other references:

  • 2

    I am not adept of 'lazy' methods (rs) for this case - I prefer the first option. As a guarantee I would use class Produtos implements Aparelhos.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.