Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

felipe_zmm

Iniciante em OO

Recommended Posts

Só complementando o que o Evandro falou sobre herança, esse é o LSP (Liskov substitution principle), terceiro princípio dos princípios S.O.L.I.D.

 

Para entender isso facilmente basta ver o exemplo estúpido abaixo:

 

<?php

abstract class Fruta
{
    abstract public function nomeDaFruta();
}

class Maca extends Fruta
{
    public function nomeDaFruta()
    {
        return 'Maçã';
    }
}

class Manga extends Fruta
{
    public function nomeDaFruta()
    {
        return 'Manga';
    }
}

class AK47 extends Fruta
{
    public function nomeDaFruta()
    {
        return 'AK 47';
    }
}

class ComedorDeFrutas
{
    public function comer(Fruta $fruta)
    {
        echo "Uma {$fruta->nomeDaFruta()} está sendo comida.\n";
    }
}

$comedorDeFrutas = new ComedorDeFrutas();

$comedorDeFrutas->comer(new Maca()); // Uma maçã está sendo comida.
$comedorDeFrutas->comer(new Manga()); // Uma manga está sendo comida.
$comedorDeFrutas->comer(new AK47()); // Uma AK 47 está sendo comida.

 

Maçã e Manga são frutas, logo podem estender da classe fruta que a semântica não será quebrada. Mas uma AK 47 não é uma fruta, veja no código como podemos "quebrar" a semântica e fazer o nosso comedor de frutas comer uma AK 47.

Compartilhar este post


Link para o post
Compartilhar em outros sites

 

Péssimo uso de herança!! Encare herança como, herança. Pai pra filho. Uma linhagem familiar. O filho do sr José da Silva será Joãozinho da Silva.

Usuário não é um [inline]PDO[/inline]. Usuário usa um [inline]PDO[/inline]. Precisa de um [inline]PDO[/inline].

 

Eu entendo essa questão da herança, mas UsuarioMapper não é um usuário, é um Mapper para a classe Usuario. Eu mesmo disse que achei que não estava correta, mas foi porque imaginei ter uma solução melhor para o caso.

 

 

 

Não tente facilitar a implementação. Não é esse o objetivo da Orientação a Objetos. Se você está preocupado em se repetir, provavelmente há algo errado na modelagem. Se houver reuso de componentes, esse reuso deve ser externado em um novo componente.

Não fazer algo por ser "custoso" é desculpa de preguiçoso. Não quer trabalhar, vá ser deputado.

 

Não estou tentando facilitar, o que tentei fazer foi criar as classes sem repetir código, já que de outra forma eu teria que repetir a propriedade $pdo em todos os Mappers. Mas talvez isso seja um exagero.

Compartilhar este post


Link para o post
Compartilhar em outros sites

É um exagero dos mais exagerados, definir uma propriedade em mais de uma classe não pode ser considerada uma duplicação de código, a não ser que tenha validação, formatação, etc. Mas só colocar uma instância... é uma questão de custo vs benefício.

 

Já que você está procurando por técnicas de melhorar o código, eu recomendo você ler o Clean Code, ele aborda muitos pontos de código bom incluindo duplicação, OOP, etc.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Hoje estive olhando com mais calma e tentando implementar algumas das dicas que me passaram aqui e surgiram algumas dúvidas sobre a implementação do Enrico.

 

interface UserStorage
{
    public function insert(User $user);
    public function update(User $user);
    public function delete($id);
    public function getAll();
    public function findById($id);
}

O problema que eu vejo nessa interface é que ela fica amarrada a um objeto do tipo User. Não seria mais fácil que eu tivesse uma interface pra todos os objetos possíveis ? Eu criaria uma classe abstrata os objetos como Usuario herdariam dela. Senão eu teria que criar interface pra Produto, Fornecedor e etc. Mas ao mesmo tempo não vejo qual poderia ser a estrutura dessa classe abstrata, já que o único atributo comum entre suas subclasses seria a propriedade id além de getter e setter dessa propriedade. Se puderem me dar uma sugestão eu agradeço.

 

Outra coisa:

public function findById($id)
{
    $query = "SELECT nome, sobrenome, email, login
    FROM {$this->table}
    WHERE id = :id";

    $statement = $this->pdo->query($query);
    $statement->setFetchMode(PDO::FETCH_CLASS, 'stdClass');
    $statement->bindParam(':id', $id);

    if (! $statement->execute()) {
        throw new RuntimeException();
    }
        
    $user = $statement->fetch();
      
    $statement->closeCursor();

    return $user;
}

Reparei que ele trocou o retorno de um objeto Usuario que era instanciado pelo próprio método pelo resultado do método fetch do objeto PDOStatement. Mas não seria realmente melhor instanciar e retornar um objeto Usuario ou passar o fetch_style PDO::FETCH_OBJ que daria praticamente o mesmo efeito ?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Não é comum usar uma entidade em SELECT's, DELETE's, etc. Eles são usados geralmente apenas em INSERT's e UPDATE's.

 

Você poderia ver uma implementação de um ORM na vida real. Um exemplo seria o Zend\Db: https://github.com/zendframework/zf2/tree/master/library/Zend/Db

 

Vou olhar sim. Mas e quanto à interface ? Você não acha um pouco redundante ficar criando interface UserStorage, ProdutoStorage e etc ? Elas seriam a mesma coisa porém esperando um tipo de objeto diferente. Tem alguma idéia de como eu poderia padronizar apenas uma interface para todos esses tipos de objeto ?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Pessoal, criei uma espécie de FrontController para meu mini sistema de estudos, gostaria que se possível dessem uma olhada e fizessem críticas que possam me ajudar.

 

<?php
    class FrontController
    {
        private $diretorioBase;
        private $url;
        private $segmentoAcao;
        private $queryString = false;
        private $argsArray;
        
        public function __construct($diretorioBase, $url, $segmentoAcao)
        {
            $this->setDiretorioBase($diretorioBase);
            $this->setSegmentoAcao($segmentoAcao);
            $this->url = $url;
        }
        
        public function getDiretorioBase()
        {
            return $this->diretorioBase;
        }
        private function setDiretorioBase($diretorioBase)
        {
            if (!is_dir($diretorioBase))
            {
                throw new Exception("O sistema não pôde encontrar o diretório {$diretorioBase}.");
            }
            
            $this->diretorioBase = $diretorioBase;
        }
        
        public function getSegmentoAcao()
        {
            return $this->segmentoAcao;
        }
        public function setSegmentoAcao($segmentoAcao)
        {
            if (!is_int($segmentoAcao))
            {
                throw new Exception('Segmento de URL inválido!');
            }
            
            $this->segmentoAcao = $segmentoAcao;
        }
        
        public function getQueryString()
        {
            return $this->queryString;
        }
        public function setQueryString($habilitado)
        {
            if (!is_bool($habilitado))
            {
                throw new Exception('Argumento inválido!');
            }
            
            $this->queryString = $habilitado;
        }
        
        public function executar()
        {
            $pagina = $this->tratarURL();
            
            if (file_exists($pagina))
            {
                include($pagina);
            }
            else
            {
                throw new Exception('Página não encontrada!');
            }
        }
        
        private function tratarURL()
        {
            $urlArray = array_filter(explode('/', $this->url));
            $acao     = $urlArray[$this->segmentoAcao];
            
            if ($this->queryString == false)
            {
                for ($i = $this->segmentoAcao + 1; $i <= count($urlArray); $i++)
                {
                    $this->argsArray[] = $urlArray[$i];   
                }
            }
            
            return $this->diretorioBase . "\\{$acao}.php";
        }
    }
?>

 

Um problema que eu estou tendo com essa classe é que como include é feito através do método executar(), as instâncias de PDO e Smarty que eu crio na index não são encontradas nas páginas de include. Eu consegui resolver este problema utilizando global (Enrico cai da cadeira), mas gostaria de saber se alguém tem uma sugestão melhor de como resolver isso, se é que existe.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu acho que o Front Controller deveria ser uma coisa "magra". Você poderia separar a parte de roteamento em uma classe separada.

 

 

Um problema que eu estou tendo com essa classe é que como include é feito através do método executar(), as instâncias de PDO e Smarty que eu crio na index não são encontradas nas páginas de include. Eu consegui resolver este problema utilizando global (Enrico cai da cadeira), mas gostaria de saber se alguém tem uma sugestão melhor de como resolver isso, se é que existe.

 

Você poderia ter uma classe de bootstrap, que controla um Dependency Injection Container e que define o que será passado para os controllers, os quais seriam classes.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Eu acho que o Front Controller deveria ser uma coisa "magra". Você poderia separar a parte de roteamento em uma classe separada.

 

Hum. Pode ser uma boa mesmo separar isso. No mais está ok ?

 

Você poderia ter uma classe de bootstrap, que controla um Dependency Injection Container e que define o que será passado para os controllers, os quais seriam classes.

 

Pesquisarei sobre. Obrigado.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Hum. Pode ser uma boa mesmo separar isso. No mais está ok ?

 

Não vejo nada de mais. E você poderia "enxugar" um pouquinho também a parte de arquivos e tornar o set/get de queryString em um método mais inteligente, veja:

 

<?php

class Router
{
    private $url;
    private $segment;
    private $queryString = false;
    private $argsArray;

    public function __construct($url, $segment = 0)
    {
        $this->url = $url;
        $this->segment = $segment;
    }

    public function enableQueryString()
    {
        $this->queryString = true;
    }

    public function disableQueryString()
    {
        $this->queryString = false;
    }
    
    public function isQueryStringEnabled()
    {
        return $this->queryString === true;
    }

    public function getAction()
    {
        $urlArray = array_filter(explode('/', $this->url));
        $action = $urlArray[$this->segmentoAcao];
        
        if ($this->isQueryStringEnabled())
        {
            $segment = $this->segment + 1;
            $urlArraySize = count($urlArray);

            for ($segment; $i <= $urlArraySize; $segment++)
            {
                $this->argsArray[] = $urlArray[$segment];   
            }
        }
        
        return $action;
    }

    public function getSegment($segment)
    {
        if (! isset($this->argsArray[$segment])) {
            throw new Exception('Segmento não encontrado.');
        }

        return $this->argsArray[$segment];
    }
}

class Filesystem
{
    private $directory;

    public function __construct($directory)
    {
        if (! is_dir($directory))
        {
            throw new Exception(sprintf(
                'O sistema não pôde encontrar o diretório %s.', $directory
            ));
        }

        $this->directory = $directory;
    }

    public function requirePage($page)
    {
        $file = sprintf('%s/%s.php', $this->directory, $page);

        if (! file_exists($file))
        {
            throw new Exception('Página não encontrada!');
        }

        include $file;
    }
}

class FrontController
{
    private $router;
    private $filesystem;
    
    public function __construct(Router $router, Filesystem $filesystem)
    {
        $this->router = $router;
        $this->filesystem = $filesystem;
    }
    
    public function run()
    {
        $this->filesystem->requirePage($this->router->getAction());
    }
}

 

PS: Eu não entendi muito bem o esquema de segmento, mas acho que a ideia é pegar um segmento da URL.

 

Outra dica: não saia criando getters e setters como se não houvesse amanhã. Pense bem antes de criar um.

Compartilhar este post


Link para o post
Compartilhar em outros sites

PS: Eu não entendi muito bem o esquema de segmento, mas acho que a ideia é pegar um segmento da URL.

 

A idéia é essa mesmo, setar em qual segmento da URL vai estar a página a ser incluída.

 

Vou olhar com calma sua implementação. Valeu.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Já conversamos sobre muita coisa citada aqui. Mas achei por bem deixar documentado

Eu entendo essa questão da herança, mas UsuarioMapper não é um usuário, é um Mapper para a classe Usuario. Eu mesmo disse que achei que não estava correta, mas foi porque imaginei ter uma solução melhor para o caso.


Assim como nomear variáveis como [inline]$a, $shdslhd, $var1[/inline] não é uma boa prática, falhar ao escolher o nome dos seus componentes é uma falha em escala maior ainda.

$usuario = new UsuarioMapper();


Beleza, campeão. Um mapeador de usuário para o quê? Pense em código semântico e auto-descritivo.

Não estou tentando facilitar, o que tentei fazer foi criar as classes sem repetir código, já que de outra forma eu teria que repetir a propriedade $pdo em todos os Mappers. Mas talvez isso seja um exagero.


em todos os [inline]*PdoMapper[/inline]. O problema aqui é querer se antecipar à implementação no âmbito da abstração. A estrutura e design de interfaces remete à abstração. Assumindo que é uma boa prática manter as propriedades privadas, Você - enquanto implementador de interfaces - não deve e não precisa saber quais os nomes foram utilizados para as propriedades de um objeto.

Você se antecipa à implementação, cria uma propriedade chamada [inline]$pdo[/inline]. Eu estendo sua classe abstrata, sobrescrevo o construtor e faço

function __construct(PDO $pdo)
{
    $this->databaseConnectionInstance = $pdo;
}


Uso tudo em função de [inline]$databaseConnectionInstance[/inline] respeitando a interface. O sistema continua funcionando perfeitamente, como deve, e você teve trabalho à toa.

 

Hoje estive olhando com mais calma e tentando implementar algumas das dicas que me passaram aqui e surgiram algumas dúvidas sobre a implementação do Enrico.

interface UserStorage
{
    public function insert(User $user);
    public function update(User $user);
    public function delete($id);
    public function getAll();
    public function findById($id);
}
O problema que eu vejo nessa interface é que ela fica amarrada a um objeto do tipo User.

Vai ver é por isso que o @Enrico Pereira decidiu nomeá-la como [inline]UserStorage[/inline].

Não seria mais fácil que eu tivesse uma interface pra todos os objetos possíveis ? Eu criaria uma classe abstrata os objetos como Usuario herdariam dela. Senão eu teria que criar interface pra Produto, Fornecedor e etc. Mas ao mesmo tempo não vejo qual poderia ser a estrutura dessa classe abstrata, já que o único atributo comum entre suas subclasses seria a propriedade id além de getter e setter dessa propriedade. Se puderem me dar uma sugestão eu agradeço.

Vou olhar sim. Mas e quanto à interface ? Você não acha um pouco redundante ficar criando interface UserStorage, ProdutoStorage e etc ? Elas seriam a mesma coisa porém esperando um tipo de objeto diferente. Tem alguma idéia de como eu poderia padronizar apenas uma interface para todos esses tipos de objeto ?


Não sei como está o seu raciocínio agora. Mas, até o momento deste quote, você ainda não tinha sacado do que se trata uma interface. Interface por vezes também é chamada de tipo. A interface define o tipo da instância. A interface não é apenas o elemento que nós descrevemos no código sob a palavra chave [inline]interface[/inline]. Interface é o resultado final de todas as estruturas que compõem uma instância.

Assim, assumindo

interface Foo
{
    public function foo();
}
 
interface Bar
{
    public function bar()/
}
 
class Baz implements Foo, Bar
{
    public function foo()
    {
        echo 'foo';
    }
 
    public function bar();
    {
        echo 'bar';
    }
}


A interface de [inline]Baz[/inline] é............. [inline]Baz[/inline]! Isso mesmo! Interface é a mesma coisa que tipo. A interface é o tipo do objeto. A variável [inline]$nomeDaVariável[/inline] contém uma instância do tipo [inline]nomeDaInstancia[/inline]. O ponto crucial aqui é a palavrinha chave [inline]implements[/inline]. Isso quer dizer que objetos do tipo [inline]Baz[/inline] implementam [inline]Foo[/inline] e [inline]Bar[/inline]. Pra resumir, no final das contas, a interface é o resultado final da salada toda.

Poder simplificar um elemento de interface, você pode:

interface EntityMapper
{
    public function setId($id);
 
    public function getId();
}


A questão é até onde uma Interface específica é viável e até onde uma interface genérica é viável. Confiar numa interface assegura que todos os métodos necessários estarão disponíveis para uso. Se confiarmos na interface genérica...

public function persistAge(EntityMapper $entity)
{
    $this->storage->saveInto('user')->field('age')->value($entity->getAge());
}


... corremos o risco de receber produtos, fornecedores, etc. No caso de comparar produtos, fornecedores, com a idade do usuário, podemos não ter problemas muito graves, assumindo que a possibilidade de se interessar pela idade de um fornecedor é mínima e a de um produto então, menor ainda.

Agora, imagine que temos um sistema para vendas de bebidas. Obviamente, precisamos saber a idade do cliente para não vendermos para menores de 18 anos. Agora, e se, no lugar do cliente, eu passasse um objeto do tipo [inline]Whisky[/inline]. Entendeu o problema de se ter uma interface genérica?

 

Outra coisa:

public function findById($id)
{
    $query = "SELECT nome, sobrenome, email, login
    FROM {$this->table}
    WHERE id = :id";

    $statement = $this->pdo->query($query);
    $statement->setFetchMode(PDO::FETCH_CLASS, 'stdClass');
    $statement->bindParam(':id', $id);

    if (! $statement->execute()) {
        throw new RuntimeException();
    }
        
    $user = $statement->fetch();
      
    $statement->closeCursor();

    return $user;
}
Reparei que ele trocou o retorno de um objeto Usuario que era instanciado pelo próprio método pelo resultado do método fetch do objeto PDOStatement. Mas não seria realmente melhor instanciar e retornar um objeto Usuario ou passar o fetch_style PDO::FETCH_OBJ que daria praticamente o mesmo efeito ?


No caso ele retornou um objeto [inline]StdClass[/inline]. Sim, o ideal era usar [inline]PDO::FETCH_CLASS[/inline] com [inline]UserMapper[/inline] como argumento.

 

Pessoal, criei uma espécie de FrontController para meu mini sistema de estudos, gostaria que se possível dessem uma olhada e fizessem críticas que possam me ajudar.

<?php
    class FrontController
    {
        private $diretorioBase;
        private $url;
        private $segmentoAcao;
        private $queryString = false;
        private $argsArray;
        
        public function __construct($diretorioBase, $url, $segmentoAcao)
        {
            $this->setDiretorioBase($diretorioBase);
            $this->setSegmentoAcao($segmentoAcao);
            $this->url = $url;
        }
        
        public function getDiretorioBase()
        {
            return $this->diretorioBase;
        }
        private function setDiretorioBase($diretorioBase)
        {
            if (!is_dir($diretorioBase))
            {
                throw new Exception("O sistema não pôde encontrar o diretório {$diretorioBase}.");
            }
            
            $this->diretorioBase = $diretorioBase;
        }
        
        public function getSegmentoAcao()
        {
            return $this->segmentoAcao;
        }
        public function setSegmentoAcao($segmentoAcao)
        {
            if (!is_int($segmentoAcao))
            {
                throw new Exception('Segmento de URL inválido!');
            }
            
            $this->segmentoAcao = $segmentoAcao;
        }
        
        public function getQueryString()
        {
            return $this->queryString;
        }
        public function setQueryString($habilitado)
        {
            if (!is_bool($habilitado))
            {
                throw new Exception('Argumento inválido!');
            }
            
            $this->queryString = $habilitado;
        }
        
        public function executar()
        {
            $pagina = $this->tratarURL();
            
            if (file_exists($pagina))
            {
                include($pagina);
            }
            else
            {
                throw new Exception('Página não encontrada!');
            }
        }
        
        private function tratarURL()
        {
            $urlArray = array_filter(explode('/', $this->url));
            $acao     = $urlArray[$this->segmentoAcao];
            
            if ($this->queryString == false)
            {
                for ($i = $this->segmentoAcao + 1; $i <= count($urlArray); $i++)
                {
                    $this->argsArray[] = $urlArray[$i];   
                }
            }
            
            return $this->diretorioBase . "\\{$acao}.php";
        }
    }
?>

Um problema que eu estou tendo com essa classe é que como include é feito através do método executar(), as instâncias de PDO e Smarty que eu crio na index não são encontradas nas páginas de include. Eu consegui resolver este problema utilizando global (Enrico cai da cadeira), mas gostaria de saber se alguém tem uma sugestão melhor de como resolver isso, se é que existe.

 

Seu Front Controller tem muitas responsabilidades. Este é o maior problema, e também o causador da necessidade do uso de [inline]global[/inline].

 

A função de um Front Controller é delegar a tarefa de atender uma requisição para um "back" controller que seja capaz de atendê-la.
Depois de detectado o controller, há duas possibilidades possíveis: O Front Controller invoca, no controller, um método - que é chamado de action. Esta é a abordagem mais comum. A outra, menos usual, é repassar a requisição como argumento construtor do controller, para que este decida o destino a ser tomado

 

 

$controller = new NotFrontController($request);

 

Seu Front Controller não deve se importar e nem manipular argumentos da URL como queryString, por exemplo. Isso deve ser feito por um Router.

Seu Front Controller não deve ser um invocador de arquivos. Front Controllers delegam tarefas aos Controllers. Para instanciar dinamicamente qualquer controller, você precisa de um Autoloader.

Compartilhar este post


Link para o post
Compartilhar em outros sites

ControlerInterface.php

interface ControlerInterface{

public function hasRequest();

public function initThis();

}

AddController.php

class AddController{

private $controller = array();

public function addController(Controllers $control) {
$this->controller[] = $control;
return $this;
}

public function handler() {
$found = false;
foreach ($this->controller as $control) {
if ($control->hasRequest()) {
$found = true;
return $control->initThis();
}
}
if (!$found) {
throw new \InvalidArgumentException("Controlador não encontrado!");
}
}
}

runControllers.php

final class runControllers{

public function run(){
$controllers = new controllers\AddController();
$controllers->addController(
new controllers\Home());

// $controllers->addController(new controllers\Single())
// ->addController(new controllers\otherController);

return $controllers->handler();
}
}

home.php

class Home implements ControlerInterface{

public function hasRequest() {
return isset($_GET['controller']) && $_GET['controller'] === 'home' || $_GET['controller'] === '';
}

public function initThis() {
var_dump($this);
}
}

index.php

try{
$_GET['controller'] = '';
$run = new runControllers();
$run->run();
}catch(\Exception $e){
echo $e->getMessage();
}

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.