Melhor maneira de iniciar uma classe em um plugin WP?


90

Eu criei um plugin e, claro, sendo eu, eu queria ter uma boa abordagem de OO. Agora, o que eu tenho feito é criar esta classe e, logo abaixo, criar uma instância dessa classe:

class ClassName {

    public function __construct(){

    }
}

$class_instance = new ClassName();  

Estou assumindo que existe uma maneira mais WP de iniciar essa classe, e então me deparei com pessoas dizendo que elas preferem ter uma init()função do que uma __construct(). Da mesma forma, encontrei algumas pessoas usando o seguinte gancho:

class ClassName {

    public function init(){

    }
}
add_action( 'load-plugins.php', array( 'ClassName', 'init' ) );

Qual é geralmente considerada a melhor maneira de criar uma instância de classe WP em carga e tê-la como uma variável acessível globalmente?

NOTA: Como um ponto interessante, notei que embora register_activation_hook()possa ser chamado de dentro do __construct, ele não pode ser chamado de dentro do init()usando o segundo exemplo. Talvez alguém possa me esclarecer sobre esse ponto.

Edit: Obrigado por todas as respostas, há claramente um bom debate sobre como lidar com a inicialização dentro da própria classe, mas acho que geralmente há um consenso bastante bom que add_action( 'plugins_loaded', ...);é a melhor maneira de realmente começar ...

Edit: Apenas para confundir as coisas, eu também já vi isso usado (embora eu não usasse esse método porque transformar uma classe OO agradavelmente em uma função parece derrotar o objetivo):

// Start up this plugin
add_action( 'init', 'ClassName' );
function ClassName() {
    global $class_name;
    $class_name = new ClassName();
}

1
Em relação à última edição, se estiver contida no mesmo arquivo de plug-in da classe, ela se tornará um pouco inútil. Você também pode instanciar a classe conforme o método que eu descrevo. Mesmo que esteja em um arquivo separado, ainda é inútil. O único caso de uso que posso ver é se você deseja criar uma função de invólucro que permita instanciar uma classe fora dos seus arquivos de plug-in, dentro de temas e assim por diante. Mesmo assim, eu teria que perguntar qual lógica estava por trás disso, porque o uso adequado de condicionais e ganchos deve permitir um controle fino da instanciação, permitindo que você se concentre no uso do plug-in.
Adam

Eu meio que concordo com isso, mas achei que valeria a pena colocar o que encontrei em alguns plugins WP.
kalpaitch

Respostas:


60

Boa pergunta, existem várias abordagens e isso depende do que você deseja alcançar.

Eu costumo fazer;

add_action( 'plugins_loaded', array( 'someClassy', 'init' ));

class someClassy {

    public static function init() {
        $class = __CLASS__;
        new $class;
    }

    public function __construct() {
           //construct what you see fit here...
    }

    //etc...
}

Um exemplo mais aprofundado e aprofundado, que surgiu como resultado de algumas discussões recentes sobre esse mesmo tópico dentro da sala de bate-papo, pode ser visto nesta síntese pelo membro da WPSE toscho .

A abordagem de construtor vazio.

Aqui está um trecho de vantagens / desvantagens retiradas da essência acima, que exemplifica a abordagem do construtor vazio na íntegra.

  • Vantagens:

    • Os testes de unidade podem criar novas instâncias sem ativar nenhum gancho automaticamente. Não Singleton.

    • Nenhuma variável global é necessária.

    • Quem quiser trabalhar com a instância do plug-in pode simplesmente chamar T5_Plugin_Class_Demo :: get_instance ().

    • Fácil de desativar.

    • OOP ainda real: nenhum método de trabalho é estático.

  • Desvantagem:

    • Talvez mais difícil de ler?

A desvantagem, na minha opinião, é fraca, e é por isso que teria que ser minha abordagem preferida, mas não a única que eu uso. De fato, vários outros pesos pesados ​​irão, sem dúvida, abordar esse tópico com sua opinião sobre o assunto em breve, porque há algumas boas opiniões em torno desse tópico que devem ser expressas.


note: Preciso encontrar o exemplo essencial da toscho que realizou 3 ou 4 comparações de como instanciar uma classe em um plug-in que analisava os prós e os contras de cada um, que o link acima era a maneira preferida de fazê-lo, mas os outros exemplos fornecem um bom contraste para este tópico. Espero que toscho ainda tenha isso registrado.

Nota: A WPSE Responde a este tópico com exemplos e comparações relevantes. Também a melhor solução, por exemplo, uma aula no WordPress.

add_shortcode( 'baztag', array( My_Plugin::get_instance(), 'foo' ) );
class My_Plugin {

    private $var = 'foo';

    protected static $instance = NULL;

    public static function get_instance() {

        // create an object
        NULL === self::$instance and self::$instance = new self;

        return self::$instance; // return the object
    }

    public function foo() {

        return $this->var; // never echo or print in a shortcode!
    }
}

qual seria a diferença entre add_action ('plugins_loaded', ...); e add_action ('load-plugins.php', ...); O exemplo que eu usei o último
kalpaitch 22/10/12

1
Pelo que entendi, o load-plugins.php, embora funcione, está associado ao update.phparquivo principal e não faz parte das ações padrão habituais que devem ser consideradas quando se refere à sequência de eventos que são acionados durante a inicialização e, por esse motivo, prefiro para usar os ganchos que se aplicam, neste caso plugins_loaded. É a isso que frequentemente me refiro como um instantâneo rápido do que acontece quando o Action Reference . Minha explicação não está completa na sua totalidade.
Adam

4
Eu gosto dessa abordagem singleton. No entanto, questiono usar plugins_loaded como seu gancho de ação de inicialização. Este gancho deve ser executado após o carregamento de todos os plugins. Ao conectar-se a ele, você está meio que seqüestrando esse gancho e pode se divertir com conflitos ou problemas na sequência de inicialização com outros plug-ins ou temas que se conectam a plugins_loaded. Eu não entraria em nenhuma ação para executar seu método de inicialização. A arquitetura do plug-in foi projetada para executar em linha, não em uma ação.
Tom Auger

2
Observe que, se você usar, register_activation_hook()precisará chamar essa função antes que a plugins_loadedação seja acionada.
Geert

1
Como informações adicionais, consulte este post de @mikeschinkel e o dicuss nos comentários. hardcorewp.com/2012/...
bueltge

78

Chegando aqui exatamente 2 anos depois que a pergunta original foi feita, há algumas coisas que quero destacar. (Não me peça para apontar muitas coisas , sempre).

Gancho adequado

Para instanciar uma classe de plug-in, o gancho adequado deve ser usado. Não existe uma regra geral para isso, pois depende do que a classe faz.

Usar um gancho muito cedo, como não "plugins_loaded"costuma fazer sentido, porque um gancho desse tipo é acionado para solicitações de administrador, frontend e AJAX, mas muitas vezes um gancho posterior é muito melhor porque permite instanciar classes de plug-in apenas quando necessário.

Por exemplo, uma classe que faz coisas para modelos pode ser instanciada "template_redirect".

De um modo geral, é muito raro que uma classe precise ser instanciada antes de "wp_loaded"ser demitida.

Classe sem Deus

A maioria de todas as classes usadas como exemplos em respostas mais antigas usa uma classe chamada like "Prefix_Example_Plugin"ou "My_Plugin"... Isso indica que provavelmente existe uma classe principal para o plugin.

Bem, a menos que um plug-in seja feito por uma única classe (nesse caso, nomeá-lo após o nome do plug-in é absolutamente razoável), crie uma classe que gerencie todo o plug-in (por exemplo, adicionando todos os ganchos de que um plug-in precisa ou instanciando todas as outras classes de plug-in ) pode ser considerada uma má prática, como um exemplo de um objeto divino .

Na programação orientada a objetos, o código deve tender a ser SOLID, onde "S" significa "princípio de responsabilidade única" .

Isso significa que toda classe deve fazer uma única coisa. No desenvolvimento de plug-ins do WordPress, significa que os desenvolvedores devem evitar usar um único gancho para instanciar uma classe principal de plug - ins, mas diferentes ganchos devem ser usados ​​para instanciar classes diferentes, de acordo com a responsabilidade da classe.

Evite ganchos no construtor

Esse argumento foi introduzido em outras respostas aqui, no entanto, quero comentar esse conceito e vincular essa outra resposta, onde foi amplamente explicado no âmbito do teste de unidade.

Quase 2015: o PHP 5.2 é para zumbis

Desde 14 de agosto de 2014, o PHP 5.3 atingiu seu fim de vida . Definitivamente está morto. O PHP 5.4 será suportado por todo o ano de 2015, ou seja, por mais um ano no momento em que estou escrevendo.

No entanto, o WordPress ainda suporta PHP 5.2, mas ninguém deve escrever uma única linha de código que suporte essa versão, especialmente se o código for OOP.

Existem diferentes razões:

  • PHP 5.2 morto há muito tempo, nenhuma correção de segurança foi lançada, o que significa que não é seguro
  • PHP 5.3 adicionou um monte de recursos para PHP, funções anônimas e namespaces alles über
  • versões mais recentes do PHP são muito mais rápidas . PHP é grátis. A atualização é gratuita. Por que usar uma versão mais lenta e insegura se você pode usar uma versão mais rápida e segura de graça?

Se você não quiser usar o código PHP 5.4+, use pelo menos 5.3+

Exemplo

Neste ponto, é hora de revisar as respostas mais antigas com base no que eu disse até aqui.

Uma vez que não precisamos mais nos preocupar com o 5.2, podemos e devemos usar namespaces.

Para melhor explicar o princípio da responsabilidade única, meu exemplo usará 3 classes, uma que faz algo no front-end, uma no back-end e uma terceira usada nos dois casos.

Classe de administrador:

namespace GM\WPSE\Example;

class AdminStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}

Classe de front-end:

namespace GM\WPSE\Example;

class FrontStuff {

   private $tools;

   function __construct( ToolsInterface $tools ) {
     $this->tools = $tools;
   }

   function setup() {
      // setup class, maybe add hooks
   }

}

Interface de ferramentas:

namespace GM\WPSE\Example;

interface ToolsInterface {

   function doSomething();

}

E uma classe Tools, usada pelos outros dois:

namespace GM\WPSE\Example;

class Tools implements ToolsInterface {

   function doSomething() {
      return 'done';
   }

}

Tendo essas aulas, posso instancia-las usando ganchos adequados. Algo como:

require_once plugin_dir_path( __FILE__ ) . 'src/ToolsInterface.php';
require_once plugin_dir_path( __FILE__ ) . 'src/Tools.php';

add_action( 'admin_init', function() {

   require_once plugin_dir_path( __FILE__ ) . 'src/AdminStuff.php';
   $tools = new GM\WPSE\Example\Tools;
   global $admin_stuff; // this is not ideal, reason is explained below
   $admin_stuff = new GM\WPSE\Example\AdminStuff( $tools ); 
} );

add_action( 'template_redirect', function() {

   require_once plugin_dir_path( __FILE__ ) . 'src/FrontStuff.php';
   $tools = new GM\WPSE\Example\Tools;
   global $front_stuff; // this is not ideal, reason is explained below
   $front_stuff = new GM\WPSE\Example\FrontStuff( $tools );    
} );

Inversão de Dependência e Injeção de Dependência

No exemplo acima, usei namespaces e funções anônimas para instanciar classes diferentes em ganchos diferentes, colocando em prática o que eu disse acima.

Observe como os namespaces permitem criar classes nomeadas sem nenhum prefixo.

Eu apliquei outro conceito que foi indiretamente mencionado acima: Injeção de Dependência , é um método para aplicar o Princípio de Inversão de Dependência , o "D" na sigla SOLID.

A Toolsclasse é "injetada" nas outras duas classes quando são instanciadas, portanto é possível separar a responsabilidade.

Além disso, AdminStuffe FrontStuffclasses usam dicas de tipo para declarar que precisam de uma classe que implementa ToolsInterface.

Dessa maneira, nós mesmos ou usuários que usamos nosso código podemos usar implementações diferentes da mesma interface, tornando nosso código não acoplado a uma classe concreta, mas a uma abstração: é exatamente disso que se trata o Princípio da Inversão de Dependência.

No entanto, o exemplo acima pode ser melhorado. Vamos ver como.

Carregador automático

Uma boa maneira de escrever um código OOP mais legível é não misturar a definição de tipos (interfaces, classes) com outro código e colocar todos os tipos em seu próprio arquivo.

Esta regra também é um dos padrões de codificação PSR-1 1 .

No entanto, para fazer isso, antes de poder usar uma classe, é necessário exigir o arquivo que a contém.

Isso pode ser esmagador, mas o PHP fornece funções utilitárias para carregar automaticamente uma classe quando necessário, usando um retorno de chamada que carrega um arquivo com base em seu nome.

Usando espaços para nome, fica muito fácil, porque agora é possível combinar a estrutura da pasta com a estrutura do espaço para nome.

Isso não é apenas possível, mas também é outro padrão PSR (ou melhor, 2: PSR-0 agora obsoleto e PSR-4 ).

Seguindo esses padrões, é possível fazer uso de diferentes ferramentas que lidam com o carregamento automático, sem a necessidade de codificar um carregador automático personalizado.

Devo dizer que os padrões de codificação do WordPress têm regras diferentes para nomear arquivos.

Portanto, ao escrever o código para o núcleo do WordPress, os desenvolvedores precisam seguir as regras do WP, mas ao escrever o código personalizado, é uma opção do desenvolvedor, mas o uso do padrão PSR é mais fácil de usar as ferramentas já escritas 2 .

Padrões de acesso global, registro e localizador de serviço.

Um dos maiores problemas ao instanciar classes de plug-in no WordPress é como acessá-las em várias partes do código.

O próprio WordPress usa a abordagem global : as variáveis ​​são salvas no escopo global, tornando-as acessíveis em qualquer lugar. Todo desenvolvedor de WP digita a palavra globalmilhares de vezes em sua carreira.

Essa também é a abordagem que usei para o exemplo acima, mas é ruim .

Essa resposta já é longa demais para permitir que eu explique melhor o porquê, mas ler os primeiros resultados no SERP para "variáveis ​​globais más" é um bom ponto de partida.

Mas como é possível evitar variáveis ​​globais?

Existem maneiras diferentes.

Algumas das respostas mais antigas aqui usam a abordagem de instância estática .

public static function instance() {

  if ( is_null( self::$instance ) ) {
    self::$instance = new self;
  }

  return self::$instance;
}

É fácil e muito bom, mas obriga a implementar o padrão para todas as classes que queremos acessar.

Além disso, muitas vezes essa abordagem coloca o caminho para cair no problema da classe god, porque os desenvolvedores tornam acessível uma classe principal usando esse método e depois o usam para acessar todas as outras classes.

Eu já expliquei o quão ruim é uma classe god, portanto, a abordagem de instância estática é um bom caminho a percorrer quando um plug-in precisa apenas tornar acessível uma ou duas classes.

Isso não significa que ele possa ser usado apenas para plugins com apenas algumas classes; de fato, quando o princípio de injeção de dependência é usado corretamente, é possível criar aplicativos bastante complexos sem a necessidade de tornar globalmente acessível um grande número de objetos.

No entanto, algumas vezes os plugins precisam tornar acessíveis algumas classes e, nesse caso, a abordagem de instância estática é esmagadora.

Outra abordagem possível é usar o padrão de registro .

Esta é uma implementação muito simples:

namespace GM\WPSE\Example;

class Registry {

   private $storage = array();

   function add( $id, $class ) {
     $this->storage[$id] = $class;
   }

   function get( $id ) {
      return array_key_exists( $id, $this->storage ) ? $this->storage[$id] : NULL;
   }

}

Usando essa classe, é possível armazenar objetos no objeto de registro por um ID, portanto, tendo acesso a um registro, é possível ter acesso a todos os objetos. Obviamente, quando um objeto é criado pela primeira vez, ele precisa ser adicionado ao registro.

Exemplo:

global $registry;

if ( is_null( $registry->get( 'tools' ) ) ) {
  $tools = new GM\WPSE\Example\Tools;
  $registry->add( 'tools', $tools );
}

if ( is_null( $registry->get( 'front' ) ) ) {
  $front_stuff = new GM\WPSE\Example\FrontStuff( $registry->get( 'tools' ) );    
  $registry->add( 'front', front_stuff );
}

add_action( 'wp', array( $registry->get( 'front' ), 'wp' ) );

O exemplo acima deixa claro que, para ser útil, o registro precisa estar acessível globalmente. Uma variável global para o único registro não é muito ruim; no entanto, para puristas não globais, é possível implementar a abordagem de instância estática para um registro ou talvez uma função com uma variável estática:

function gm_wpse_example_registry() {
  static $registry = NULL;
  if ( is_null( $registry ) ) {
    $registry = new GM\WPSE\Example\Registry;
  }
  return $registry;
}

A primeira vez que a função é chamada, ela instancia o registro e, nas chamadas subsequentes, apenas o devolve.

Outro método específico do WordPress para tornar uma classe acessível globalmente está retornando uma instância de objeto de um filtro. Algo assim:

$registry = new GM\WPSE\Example\Registry;

add_filter( 'gm_wpse_example_registry', function() use( $registry ) {
  return $registry;
} );

Depois disso, em qualquer lugar, o registro é necessário:

$registry = apply_filters( 'gm_wpse_example_registry', NULL );

Outro padrão que pode ser usado é o padrão do localizador de serviço . É semelhante ao padrão do registro, mas os localizadores de serviço são passados ​​para várias classes usando injeção de dependência.

O principal problema desse padrão é que ele oculta as dependências de classes, dificultando a manutenção e a leitura do código.

DI Containers

Independentemente do método usado para tornar o localizador de registro ou serviço globalmente acessível, os objetos precisam ser armazenados lá e, antes de serem armazenados, precisam ser instanciados.

Em aplicações complexas, onde existem muitas classes e muitas delas possuem várias dependências, instanciar classes requer muito código; portanto, a possibilidade de erros aumenta: o código que não existe não pode ter erros.

Nos últimos anos, surgiram algumas bibliotecas PHP que ajudam os desenvolvedores a instanciar e armazenar facilmente instâncias de objetos, resolvendo automaticamente suas dependências.

Essas bibliotecas são conhecidas como contêineres de injeção de dependência porque são capazes de instanciar classes resolvendo dependências e também para armazenar objetos e devolvê-los quando necessário, agindo de maneira semelhante a um objeto de registro.

Geralmente, ao usar contêineres DI, os desenvolvedores precisam configurar as dependências para cada classe do aplicativo e, na primeira vez em que uma classe é necessária no código, ela é instanciada com dependências apropriadas e a mesma instância é retornada repetidamente em solicitações subsequentes. .

Alguns contêineres DI também são capazes de descobrir automaticamente dependências sem configuração, mas usando a reflexão do PHP .

Alguns recipientes DI bem conhecidos são:

e muitos outros.

Quero ressaltar que, para plugins simples, que envolvem apenas poucas classes e classes não possuem muitas dependências, provavelmente não vale a pena usar contêineres DI: o método de instância estática ou um registro acessível global são boas soluções, mas para plugins complexos o benefício de um contêiner DI se torna evidente.

Obviamente, mesmo objetos de contêineres DI precisam estar acessíveis para serem usados ​​no aplicativo e, para esse propósito, é possível usar um dos métodos vistos acima, variável global, variável estática de instância, objeto retornado via filtro e assim por diante.

Compositor

Usar o contêiner DI geralmente significa usar código de terceiros. Atualmente, no PHP, quando precisamos usar uma biblioteca externa (não apenas os contêineres DI, mas qualquer código que não faça parte do aplicativo), basta fazer o download e colocá-lo na pasta do aplicativo não é considerado uma boa prática. Mesmo se formos os autores desse outro pedaço de código.

A dissociação de um código de aplicativo de dependências externas é sinal de melhor organização, melhor confiabilidade e melhor sanidade do código.

Compositor , é o padrão de fato na comunidade PHP para gerenciar dependências do PHP. Longe de ser popular também na comunidade WP, é uma ferramenta que todo desenvolvedor de PHP e WordPress deve conhecer, se não usar.

Essa resposta já é do tamanho de um livro para permitir uma discussão mais aprofundada, e também discutir o Composer aqui provavelmente está fora de tópico, foi mencionado apenas por uma questão de completude.

Para mais informações, visite o site do Composer e também vale a pena ler este minisite com curadoria de @Rarst .


1 PSR são regras de padrões PHP lançadas pelo PHP Framework Interop Group

2 O Composer (uma biblioteca que será mencionada nesta resposta), entre outras coisas, também contém um utilitário de carregamento automático.


1
Uma observação adicional, que o PHP 5.3 também é o fim da vida útil. Um host responsável oferecerá pelo menos 5,4 como opção, se não for o padrão
Tom J Nowell

2
"Desde 14 de agosto de 2014, o PHP 5.3 chegou ao fim de sua vida útil. Está definitivamente morto." É a primeira linha em "PHP 5.2 é para zumbis" @TomJNowell
gmazzap

3
Apenas imaginando, como você apontaria "muitas coisas" ? ;-)
MikeSchinkel

Nos esforços para evitar variáveis ​​globais, gosto da ideia do padrão do Registro com função e variável estática. A primeira vez que a função é chamada, ela instancia o registro e, nas chamadas subsequentes, apenas o devolve.
Michael Ecklund

10

Eu uso a seguinte estrutura:

Prefix_Example_Plugin::on_load();

/**
 * Example of initial class-based plugin load.
 */
class Prefix_Example_Plugin {

    /**
     * Hooks init (nothing else) and calls things that need to run right away.
     */
    static function on_load() {

        // if needed kill switch goes here (if disable constant defined then return)

        add_action( 'init', array( __CLASS__, 'init' ) );
    }

    /**
     * Further hooks setup, loading files, etc.
     *
     * Note that for hooked methods name equals hook (when possible).
     */
    static function init(  ) {


    }
}

Notas:

  • definiu um local para coisas que precisam ser executadas imediatamente
  • desativar / substituir os ajustes é fácil (solte um initmétodo)
  • Eu acho que nunca usei / precisei o objeto da classe de plugins - requer acompanhar, etc; este é realmente um namespacing falso por objetivo, não OOP (na maioria das vezes)

Isenção de responsabilidade Ainda não uso testes de unidade ( muitas coisas no myplate ) e ouvi dizer que a estática pode ser menos preferível para eles. Faça sua pesquisa sobre isso, se você precisar fazer o teste unitário.


3
Sei que pessoas grandes em testes de unidade realmente não gostam de soluções estáticas / singleton. Acho que se você entende completamente o que está tentando alcançar usando a estática e pelo menos está ciente das implicações de fazê-lo, é perfeitamente bom implementar esses métodos. Bons tópicos em torno disso no Stack Overflow
Adam

Isso me fez realmente pensar. Então, por que usar Classes então e não apenas voltar para funções prefixadas simples. Fazemos isso apenas para ter nomes de funções / métodos mais limpos? Quero dizer tê-los aninhados com um "estático" b4 eles é muito mais legível? A chance de ter um conflito de nome é quase a mesma de um nome de classe, se você usar prefixos de propriedade ou estiver faltando alguma coisa?
James Mitch

1
@JamesMitch sim, os métodos totalmente estáticos são principalmente apenas funções com espaço para nome falso, conforme usado no WP. No entanto, as classes têm algumas vantagens sobre funções puras, mesmo nesse caso, como carregamento automático e herança. Ultimamente, tenho me mudado de métodos estáticos para objetos instanciados reais organizados por contêiner de injeção de dependência.
Rarst

3

Tudo depende da funcionalidade.

Certa vez, criei um plug-in que registrava scripts quando o construtor era chamado, então tive que conectá-lo ao wp_enqueue_scriptsgancho.

Se você quiser chamá-lo quando seu functions.phparquivo for carregado, você também poderá criar uma instância $class_instance = new ClassName();como mencionou.

Você pode considerar a velocidade e o uso da memória. Não conheço nenhum, mas posso imaginar que há ganchos desnecessários em alguns casos. Ao criar sua instância nesse gancho, você poderá economizar alguns recursos do servidor.


Legal obrigado por isso, suponho que há dois pontos para a pergunta acima também. O outro é se __construct é adequado ou se init () é a melhor maneira de inicializar a classe.
kalpaitch

1
Bem, eu usaria um init()método estático para que a instância da classe seja chamada no escopo da classe, em vez de outro escopo em que você possa sobrescrever as variáveis ​​existentes.
Tim S.

0

Eu sei que isso tem alguns anos, mas enquanto isso o php 5.3 suporta métodos anônimos , então eu vim com isso:

add_action( 'plugins_loaded', function() { new My_Plugin(); } );

e de alguma forma eu gosto mais. Eu posso usar construtores regulares e não preciso definir nenhum método "init" ou "on_load" que atrapalhe minhas estruturas OOP.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.