Em um projeto PHP, quais padrões existem para armazenar, acessar e organizar objetos auxiliares? [fechadas]

114

Como você organiza e gerencia seus objetos auxiliares, como mecanismo de banco de dados, notificação de usuário, tratamento de erros e assim por diante, em um projeto orientado a objetos baseado em PHP?

Digamos que eu tenha um grande CMS PHP. O CMS está organizado em várias classes. Alguns exemplos:

  • o objeto de banco de dados
  • gerenciamento de usuários
  • uma API para criar / modificar / excluir itens
  • um objeto de mensagem para exibir mensagens ao usuário final
  • um manipulador de contexto que leva você para a página certa
  • uma classe de barra de navegação que mostra botões
  • um objeto de registro
  • possivelmente, tratamento de erros personalizado

etc.

Estou lidando com a questão eterna, como tornar melhor esses objetos acessíveis a cada parte do sistema que precisa deles.

minha primeira abordagem, há muitos anos, era ter um $ application global que contivesse instâncias inicializadas dessas classes.

global $application;
$application->messageHandler->addMessage("Item successfully inserted");

Em seguida, mudei para o padrão Singleton e uma função de fábrica:

$mh =&factory("messageHandler");
$mh->addMessage("Item successfully inserted");

mas também não estou feliz com isso. Testes de unidade e encapsulamento se tornam cada vez mais importantes para mim, e em meu entendimento a lógica por trás de globais / singletons destrói a ideia básica de OOP.

Então, é claro, há a possibilidade de dar a cada objeto um número de ponteiros para os objetos auxiliares de que ele precisa, provavelmente a maneira mais limpa, com economia de recursos e amigável para testes, mas tenho dúvidas sobre a sustentabilidade disso a longo prazo.

A maioria das estruturas de PHP que examinei usa o padrão singleton ou funções que acessam os objetos inicializados. Ambas as abordagens são boas, mas, como disse, não estou feliz com nenhuma delas.

Eu gostaria de ampliar meu horizonte sobre quais padrões comuns existem aqui. Estou à procura de exemplos, ideias adicionais e ponteiros para os recursos que discutem isso de um longo prazo , do mundo real perspectiva.

Além disso, estou interessado em ouvir sobre abordagens especializadas, de nicho ou totalmente estranhas para o problema.

Pekka 웃
fonte

Respostas:

68

Eu evitaria a abordagem Singleton sugerida por Flavius. Existem vários motivos para evitar essa abordagem. Isso viola os bons princípios OOP. O blog de teste do Google tem alguns bons artigos sobre o Singleton e como evitá-lo:

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

Alternativas

  1. um provedor de serviços

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. Injeção de dependência

    http://en.wikipedia.org/wiki/Dependency_injection

    e uma explicação php:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

Este é um bom artigo sobre essas alternativas:

http://martinfowler.com/articles/injection.html

Implementando injeção de dependência (DI):

  • Acredito que você deva perguntar o que é necessário no construtor para o objeto funcionar :new YourObject($dependencyA, $dependencyB);

  • Você pode fornecer os objetos necessários (dependências) manualmente ( $application = new Application(new MessageHandler()). Mas você também pode usar uma estrutura DI (a página da Wikipédia fornece links para estruturas PHP DI ).

    O importante é que você apenas passe o que você realmente usa (chame uma ação), NÃO o que você simplesmente passa para outros objetos porque eles precisam. Aqui está um post recente do 'tio Bob' (Robert Martin) discutindo DI manual vs usando o framework .

Mais algumas reflexões sobre a solução de Flavius. Não quero que este post seja um anti-post, mas acho importante ver por que a injeção de dependência é, pelo menos para mim, melhor do que os globais.

Mesmo que não seja uma implementação de Singleton 'verdadeira' , ainda acho que Flavius ​​se enganou. O estado global é ruim . Observe que essas soluções também usam métodos estáticos difíceis de testar .

Eu sei que muitas pessoas fazem, aprovam e usam. Mas lendo os artigos do blog de Misko Heverys ( uma especialista em testabilidade do Google ), relê-los e digerir lentamente o que ele diz que alterou muito a maneira como vejo o design.

Se você quiser testar seu aplicativo, precisará adotar uma abordagem diferente para projetá-lo. Ao fazer a programação de teste primeiro, você terá dificuldade com coisas como estas: 'em seguida, quero implementar o log neste pedaço de código; vamos escrever um teste primeiro que registra uma mensagem básica e, em seguida, apresentar um teste que força você a escrever e usar um registrador global que não pode ser substituído.

Ainda estou tendo dificuldades com todas as informações que obtive desse blog, e nem sempre é fácil de implementar, e tenho muitas dúvidas. Mas não há como voltar ao que fiz antes (sim, estado global e singletons (S grande)) depois de entender o que Misko Hevery estava dizendo :-)

koen
fonte
16
class Application {
    protected static $_singletonFoo=NULL;

    public static function foo() {
        if(NULL === self::$_singletonFoo) {
            self::$_singletonFoo = new Foo;
        }
        return self::$_singletonFoo;
    }

}

É assim que eu faria. Ele cria o objeto sob demanda:

Application::foo()->bar();

É a maneira que estou fazendo, respeita os princípios OOP, é menos código do que como você está fazendo agora, e o objeto é criado apenas quando o código precisa dele pela primeira vez.

Nota : o que apresentei não é nem mesmo um padrão singleton real. Um singleton permitiria apenas uma instância de si mesmo, definindo o construtor (Foo :: __ constructor ()) como privado. É apenas uma variável "global" disponível para todas as instâncias de "Aplicativo". É por isso que acho que seu uso é válido, pois NÃO desrespeita os bons princípios OOP. É claro que, como qualquer coisa no mundo, esse "padrão" também não deve ser usado em demasia!

Eu vi isso sendo usado em muitos frameworks PHP, Zend Framework e Yii entre eles. E você deve usar uma estrutura. Não vou te dizer qual.

Termo aditivo Para aqueles entre vocês que estão preocupados com o TDD , você ainda pode fazer algumas conexões para injetá-lo de forma dependente. Pode ser assim:

class Application {
        protected static $_singletonFoo=NULL;
        protected static $_helperName = 'Foo';

        public static function setDefaultHelperName($helperName='Foo') {
                if(is_string($helperName)) {
                        self::$_helperName = $helperName;
                }
                elseif(is_object($helperName)) {
                        self::$_singletonFoo = $helperName;
                }
                else {
                        return FALSE;
                }
                return TRUE;
        }
        public static function foo() {
                if(NULL === self::$_singletonFoo) {
                        self::$_singletonFoo = new self::$_helperName;
                }
                return self::$_singletonFoo;
        }
}

Há espaço suficiente para melhorias. É apenas um PoC, use sua imaginação.

Por que fazer assim? Bem, na maioria das vezes o aplicativo não será testado na unidade, ele realmente será executado, espero que em um ambiente de produção . A força do PHP é sua velocidade. PHP NÃO é e nunca será uma "linguagem OOP limpa", como Java.

Dentro de um aplicativo, há apenas uma classe de aplicativo e apenas uma instância de cada um de seus auxiliares, no máximo (conforme o carregamento lento acima). Certo, solteiros são ruins, mas, novamente, apenas se eles não aderem ao mundo real. No meu exemplo, eles fazem.

"Regras" estereotipadas como "solteiros são ruins" são a fonte do mal, são para pessoas preguiçosas que não querem pensar por si mesmas.

Sim, eu sei, o manifesto do PHP é RUIM, tecnicamente falando. Ainda assim, é uma linguagem de sucesso, em seu jeito hackeado.

Termo aditivo

Um estilo de função:

function app($class) {
    static $refs = array();

    //> Dependency injection in case of unit test
    if (is_object($class)) {
        $refs[get_class($class)] = $class;
        $class = get_class($class);
    }

    if (!isset($refs[$class]))
        $refs[$class] = new $class();

    return $refs[$class];
}

//> usage: app('Logger')->doWhatever();
Flavius
fonte
15

Eu gosto do conceito de injeção de dependência:

"A injeção de dependência é onde os componentes recebem suas dependências por meio de seus construtores, métodos ou diretamente nos campos. (Do site do Pico Container )"

Fabien Potencier escreveu uma série de artigos muito bons sobre injeção de dependência e a necessidade de usá-los. Ele também oferece um belo e pequeno recipiente de injeção de dependência chamado Pimple, que eu realmente gosto de usar (mais informações no github ).

Como afirmado acima, não gosto do uso de Singletons. Um bom resumo sobre por que os solteiros não são um bom design pode ser encontrado aqui no blog de Steve Yegge .

Thomas
fonte
9

A melhor abordagem é ter algum tipo de contêiner para esses recursos. Algumas das maneiras mais comuns de implementar este contêiner :

Singleton

Não recomendado porque é difícil de testar e implica um estado global. (Singletonite)

Registro

Elimina a singletonite, bug que eu não recomendaria o registro também, porque é uma espécie de singleton também. (Teste de unidade difícil)

Herança

Pena, não há herança múltipla no PHP, então isso limita tudo à cadeia.

Injeção de dependência

Esta é uma abordagem melhor, mas um tópico mais amplo.

Tradicional

A maneira mais simples de fazer isso é usando o construtor ou injeção de setter (passe o objeto de dependência usando setter ou no construtor de classe).

Frameworks

Você pode lançar seu próprio injetor de dependência ou usar alguns dos frameworks de injeção de dependência, por exemplo. Yadif

Recurso de aplicativo

Você pode inicializar cada um dos seus recursos no bootstrap do aplicativo (que atua como um contêiner) e acessá-los em qualquer lugar do aplicativo acessando o objeto bootstrap.

Esta é a abordagem implementada no Zend Framework 1.x

Carregador de recursos

Um tipo de objeto estático que carrega (cria) recursos necessários apenas quando necessário. Esta é uma abordagem muito inteligente. Você pode vê-lo em ação, por exemplo, implementando o componente Dependency Injection do Symfony

Injeção em camada específica

Os recursos nem sempre são necessários em qualquer lugar do aplicativo. Às vezes, você só precisa deles, por exemplo, nos controladores (MV C ). Então você pode injetar os recursos apenas lá.

A abordagem comum para isso é usar comentários docblock para adicionar metadados de injeção.

Veja minha abordagem para isso aqui:

Como usar injeção de dependência no Zend Framework? - Stack Overflow

No final, gostaria de acrescentar uma observação sobre uma coisa muito importante aqui - o cache.
Em geral, apesar da técnica escolhida, você deve pensar em como os recursos serão armazenados em cache. O cache será o próprio recurso.

Os aplicativos podem ser muito grandes e carregar todos os recursos em cada solicitação é muito caro. Existem muitas abordagens, incluindo este appserver-in-php - Hospedagem de Projetos no Google Code .

takehin
fonte
4

Os objetos em PHP ocupam uma boa quantidade de memória, como você provavelmente viu em seus testes de unidade. Portanto, é ideal destruir objetos desnecessários o mais rápido possível para economizar memória para outros processos. Com isso em mente, descobri que cada objeto se encaixa em um dos dois moldes.

1) O objeto pode ter muitos métodos úteis ou precisa ser chamado mais de uma vez, caso em que implemento um singleton / registro:

$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this

2) O objeto existe apenas durante a vida do método / função que o chama, caso em que uma criação simples é benéfica para evitar que referências de objeto persistentes mantenham os objetos vivos por muito tempo.

$object = new Class();

Armazenar objetos temporários EM QUALQUER LUGAR pode levar a vazamentos de memória porque as referências a eles podem ser esquecidas sobre como manter o objeto na memória pelo resto do script.

Xeoncross
fonte
3

Eu iria para a função que retorna objetos inicializados:

A('Users')->getCurrentUser();

No ambiente de teste, você pode defini-lo para retornar maquetes. Você pode até mesmo detectar dentro de quem chama a função usando debug_backtrace () e retornar objetos diferentes. Você pode registrar dentro dele quem deseja obter quais objetos para obter alguns insights sobre o que realmente está acontecendo dentro do seu programa.

Kamil Szot
fonte