sábado, 13 de outubro de 2012

Autoload de classes em PHP 5


Breve explicação deste post:

Estava aqui escrevendo um post de jQuery + php, um CRUD de clientes,  e achei que o exemplo estava meio pobre em termos de php, então pensei em melhorar um pouco o exemplo.
OBS: os clientes são armazenados em $_Session[] e arrays, pois já tem muito CRUD de php-mysql por aí, e o objetivo é mostrar o jQuery.
As telas abrem em divs, os forms não tem action , pois o jQuery lê os campos do form, posta e fecha o div. as action são invocadas a partir do nome dos elementos.
Então antes de organizar  este projeto que já funciona, estou criando este post p/ compartilhar a experiência que tive com o autoload até chegar nos namepaces. veja abaixo uma foto da app.

Figura 01- Propósito final do proximo post
obs: Arquivos dessa App. :

surfer@surfer-OptiPlex-360:/var/sites/test.lpanic.intra$ ls -R
.:
Cliente.class.php  ClienteHandler.php  custom.css  custom.js  images  index.php  jquery-1.8.2.min.js  jqueryFormExample.js  jqueryFormExample.php  Utils.class.php

./images:
bin_empty.png  book_add.png  page_refresh.png  table_refresh.png


Esse Post

Eu não usava esse recurso de autoload e tenho projetos antigos que tem mais de 10 requires em cada página. Na época era como eu sabia fazer (php 3 e 4).


Esse recurso de autoload de classes que pode ser usado a partir do php5, economiza algumas linhas. Procurei em vários lugares na web, mas não tinha entendido como usar, já que consegui usar, vou dividir. :)


Já tenho alguma experiência com JAVA e hoje separo meus objetos, interfaces e classes em pacotes.
O php agora tem o recurso de namespaces que tenta ser parecido com o conceito de package do java com algumas modificações no uso. E utiliza um esquema de autoload com uma implementação melhorada do __autoload (spl_autoload_register), porém precisa de métodos estáticos, então mostrarei isso na 3ª parte do post, depois de mostrar o autoload, pois teremos que modificar a função criada na 2ª implementação


Imaginemos uma aplicação onde temos clientes e a estrutura p/ esses clientes vou apenas mostrar o básico, pois a idéia é mostrar o recurso de autoload:
-- M -> Cliente.php, ClienteDao.php
-- V  -> index.php
-- C -> ClienteController

1- Jeito Antigo:


PHP (tudo num arquivo só p/ explicar o exemplo)
<?

//----arquivo cliente.php model
class Cliente {

    private $id;
    private $nome;
    private $email;

    public function Cliente() {
        
    }

//construtor
//gets e sets
}

//----arquivo clienteDAO.php dao
require_once('cliente.php');

class ClienteDAO {

    private $cliente;
    private $listaCliente;

    public function ClienteDAO() {
        
    }

//construtor
//gets e sets

    public function add(Cliente $cliente) {
        $sql = "Insert into ..";
//conexao .. recorset .. execute $sql
    }

    public function remove(Cliente $cliente) {
        
    }

    public function saveOrUpdate(Cliente $cliente) {
        
    }

    public function getListaCliente() {
        $sql = "SELECT * FROM cliente";
//conexao .. recorset .. execute $sql 
        return $this->listaCliente;
    }

//..etc
}

//----arquivo clienteController.php controller
require_once('cliente.php');
require_once('clienteDAO.php');

class ClienteController {

    private $clienteDao;

    public function ClienteController() {
        $this->clienteDao = new ClienteDAO();
    }

//construtor
//gets e sets

    public function add(Cliente $cliente) {
        $this->clienteDao->add(cliente);
    }

    public function remove(Cliente $cliente) {
        $this->clienteDao->remove(cliente);
    }

    public function saveOrUpdate(Cliente $cliente) {
        $this->clienteDao->saveOrUpdate(cliente);
    }

    public function getListaCliente() {
        return $this->clienteDao->getListaCliente();
    }

}

//..etc
?>
e no index.php ou outro arquivo do site vc teria
//..etc e no index.php por exemplo 
require_once('cliente.php');
require_once('clienteController.php');

$clienteController = new ClienteController();
$arrClientes= $clienteController->getListaCliente(); 
print_r($arrClientes);

Observe os require.

2- Utilizando o __autoload

P/ cada classe que temos temos que fazer um require ou include da classe desejada.
Um jeito de não precisar fazer isso em todas as páginas é criar um arquivo com a lista de classes,  que faz esses includes ao criar objetos,  e incluir esse arquivo no inicio de cada página.:
arquivo autoload.php
<?
function __autoload($classe) {

    $classes = array(
        'Cliente' => 'Cliente.php',
        'ClienteDAO' => 'ClienteDAO.php',
        'ClienteController' => 'ClienteController.php',
    );
    require_once $classes[$classe];
}?>

e o index

<?php
require 'autoload.php';
$cliente = new Cliente();
$clienteDao = new ClienteDAO();
echo "<pre>";
print_r($cliente);
print_r($clienteDao);
echo "</pre>";
?>


o resultado deve ser :
Figura 02 - Teste simples


3- Utilizando o spl_autoload_register

Porém nas versões mais novas do PHP, já tem recursos mais modernos. existe uma função spl_autoload_register que recebe como param um array (nome_classe,metodo_que_inicializa).
Porém p/ usar essa função o método deve ser static. Então vamos modificar as classes e organizar os arquivos :

Figura 03 - Arquivos em projeto do NetBeans
Adicionamos o atributo static nas funções init para poder registrá-las com o spl


ClienteController.php
class ClienteController {

    private $dao;
    
    public function __construct() {
        $this->dao = new ClienteDAO(); 
    }

    public static function init() {
       __construct();
        
    }
    
}



ClienteDAO.php
<? final class ClienteDAO {

    private $cliente;
    private $listaCliente;
    private $arrObjCliente;

    public function __construct() {
        $this->cliente = new Cliente();
        $this->listaCliente = array(new Cliente());
        $this->arrObjCliente = new ArrayObject($this->listaCliente);
    }
    public static function init() {
        __construct();

    }
}?>

Cliente.php
<?php

final class Cliente {

    private $id;
    private $nome;
    private $email;

    function __construct() {
        $this->id = 0;
        $this->nome = null;
        $this->email = null;
    }

    public static function init() {
        __construct();
    }

    public function getId() {
        return $this->id;
    }

    public function setId($id) {
        $this->id = $id;
    }

    public function getNome() {
        return $this->nome;
    }

    public function setNome($nome) {
        $this->nome = $nome;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setEmail($email) {
        $this->email = $email;
    }

}

?>


Autoload.php
<?php
//autoload.php
function __autoload($classe) {

    $classes = array(
        'Cliente' => 'app/model/Cliente.php',
        'ClienteDAO' => 'app/dao/ClienteDAO.php',
        'ClienteController' => 'app/controller/ClienteController.php',
        'Controller' => 'app/controller/Controller.php',
        'Utils' => 'app/lib/Utils.class.php'
    );
    echo "including $classes[$classe]<br>" ;
    require_once $classes[$classe];
    spl_autoload_register(array($classe, 'init'));
}?>


index.php
<?php
require 'autoload.php';
$cliente = new Cliente();
$clienteDao = new ClienteDAO();
$clienteController = new ClienteController();
echo "<pre>";
print_r($cliente);
echo "<hr>";
print_r($clienteDao);
echo "<hr>";
print_r($clienteController);
echo "</pre>";
?>

e o resultado deve ser esse:
Figura 04 - resultado com o autoload

p/ mostrar esses objetos de um jeito um pouco melhor vou chamar uma função mostraObjeto de uma nova classe Utils. Até aqui não tem problema criar uma classe com esse nome. mas se vc for usar algum framework, cuidado pois vários tem seu próprio Utils. Isso se resolve utilizando namespaces que vou mostrar mais pra frente. Vou tirar a linha do echo "including $classes[$classe]<br>" ;  para não ficar mostrando isso ao carregar as classes. 

Utils.class.php
<?php
define('CHARSET', 'UTF-8');
define('REPOSITORIO',$_SERVER['DOCUMENT_ROOT'] . '/files/');

class Utils {
      private $styleBox;  

      public static function init(){
          __construct() ;
      }
    public function __construct() {
        $this->styleBox="
        font-family: Tahoma, Geneva, sans-serif;
 background-color: #FFC;
 font-size: 9px;
 border: thin solid #03C;
 cursor: text;
 filter: Xray;
 font-weight: bold;";
    }

    public function mostraObjeto($object) {
//        var_dump($object); 
        echo "<pre style=\"".$this->styleBox."\">";
        print_r($object);
        echo "</pre>";
        echo "<hr size=\"1\" width=\"100%\">";
    }


    function includeFile($file) {
        try {
            $fileX = $_SERVER['DOCUMENT_ROOT'] . $file;
            //die($fileX);
            if (file_exists($fileX)) {
                require_once $fileX;
            } else {
                //throw new Exception("Arquivo '$file' n&atilde;o encontrado");
                throw new Exception("A operação deste modulo '$file' ainda  n&atilde;o foi implementada.");
            }
        } catch (Exception $ex) {
            echo $ex->getMessage();
        }
    }

    function destaca($x, $y, $upper = false) {
        if ((strlen($x) > 0) && (strlen($y) > 0)) {
            $y = ($upper) ? strtoupper($y) : $y;
            $saida = str_replace($y, "<font color=red>" . $y . "</font>", $x);
        } else {
            $saida = $x;
        }

        return $saida;
    }

    function mostraDinheiro($val) {
        if ($val) {
            $valFormatado = 'R$ ' . number_format($val, 2, ',', '.');
        } else {
            $valFormatado = "";
        }

        return $valFormatado;
    }

    function arrumaStr($str, $ucase = 'lower') {
        $str = trim(str_replace(" ", "", $str));
        for ($i = 0; $i < strlen($str); $i++) {
            $Astr[$i] = $str{$i};
        }

        $sem = array("c", "C", "a", "e", "i", "o", "u", "A", "E", "I", "O", "U", "A", "O", "a", "o", "a", "e", "i", "o", "u", "A", "E", "I", "O", "U", "A", "O");
        $com = array("ç", "Ç", "á", "é", "í", "ó", "ú", "Á", "É", "Í", "Ó", "Ú", "Ã", "Õ", "ã", "õ", "`a", "`e", "`i", "`o", "`u", "`A", "`E", "`I", "`O", "`U", "â", "ô", "Â", "Ô");
        $strFinal = implode("", str_replace($com, $sem, $Astr));
        if ($ucase == 'lower') {
            return strtolower($strFinal);
        } else {
            return strtoupper($strFinal);
        }
    }

    public function mostraSQL($sqlQuery) {
        echo "<pre style=\"border:solid 1px #EA9C5C; padding:3px; color:#000000; background-color:#ededed \">";
        echo $sqlQuery;
        echo "</pre>";
        echo "<hr size=\"1\" width=\"100%\">";
    }
    public function getHumanReadableSize($param) {

        if ($param < 1024) {
            return $param . ' B';
        } elseif ($param < 1048576) {
            return round($param / 1024, 2) . ' KiB';
        } elseif ($param < 1073741824) {
            return round($param / 1048576, 2) . ' MiB';
        } elseif ($param < 1099511627776) {
            return round($param / 1073741824, 2) . ' GiB';
        } elseif ($param < 1125899906842624) {
            return round($param / 1099511627776, 2) . ' TiB';
        } elseif ($param < 1152921504606846976) {
            return round($param / 1125899906842624, 2) . ' PiB';
        } elseif ($param < 1180591620717411303424) {
            return round($param / 1152921504606846976, 2) . ' EiB';
        } elseif ($param < 1208925819614629174706176) {
            return round($param / 1180591620717411303424, 2) . ' ZiB';
        } else {
            return round($param / 1208925819614629174706176, 2) . ' YiB';
        }
    }

}

?>
e o index alterado
<?php
require 'autoload.php';
// nem criando o construtor assim nao funciona
//$cliente = new Cliente(33,'Mario Cezzare','mcezzare@gmail.com');
$cliente = new Cliente();
$cliente->setId(33);
$cliente->setNome('Mario Cezzare');
$cliente->setEmail('mcezzare@gmail.com');

$clienteDao = new ClienteDAO();
$clienteController = new ClienteController();

$util = new Utils();
$util->mostraObjeto($cliente);
$util->mostraObjeto($clienteDao);
$util->mostraObjeto($clienteController);?>

e o resultado :
Figura 05 - mostraObjeto customizado 

Agora só p/ mostrar o objeto (array ou array de objetos) de um jeito mais compacto vou criar uma função mostraObjetoArr
Utils.class.php

<?     public function mostraObjetoArr($object) {
        echo "<pre style=\"border:solid 1px #EA9C5C; padding:3px; color:#000000; background-color:#ededed \">";
        echo "Object: <br>";  
        if (is_array($object)) {
            foreach ($object as $key => $value) {
                echo "$key:<b>$value</b> &nbsp;";
                if (is_array($value)) {
                    foreach ($value as $val) {
                        echo "$value:<b>$val</b> &nbsp;|&nbsp;";
                    }
                }
                echo "<br>";
            }
        }
        echo "</pre>";
        echo "<hr size=\"1\" width=\"100%\">";
    }?>

e p/ isso poder funcionar precisamos adicionar o metodo __toString em nosso objeto Cliente

Cliente.class.php
<?
//....
public function __toString() {
        return "id:".$this->getId()." nome:".$this->getNome()." email:".$this->getEmail();
    }?>

e no index agora :


<?php
require 'autoload.php';
// nem criando o construtor assim nao funciona
//$cliente = new Cliente(33,'Mario Cezzare','mcezzare@gmail.com');
$cliente = new Cliente();
$cliente->setId(33);
$cliente->setNome('Mario Cezzare');
$cliente->setEmail('mcezzare@gmail.com');


$util = new Utils();
//$util->mostraObjeto($cliente);
//$util->mostraObjeto($clienteDao);
//$util->mostraObjeto($clienteController);

$cliente2 = new Cliente();
$cliente2->setId(26);
$cliente2->setNome('Joao Test');
$cliente2->setEmail('jtest@gmail.com');

$arrClientes = array();
array_push($arrClientes, $cliente);
array_push($arrClientes, $cliente2);
$util->mostraObjetoArr($arrClientes);

//jeito mais moderno de armazenar arrays
$arrObjectNative = new ArrayObject($arrClientes);
$util->mostraObjeto($arrObjectNative);?>

e o resultado : 
Figura 06 - resultado após o metodo __toString e mostrando o objeto ArrayObject
so coloquei o $arrObjectNative p/ mostrar um jeito mais eficiente de armazenar arrays de tipos de objetos, o objetivo que era mostrar o autoload dentro de um MVC foi atendido.
Arquivos deste Post
AutoLoad_ExemploPHP.zip

Agora vou ter que fazer alguns acertos p/ poder utilizar os namespaces e mostro a app pronta no próximo post dela. Se quiser ver como utilizar os namespaces nesse exemplo siga para esse post :




Dúvidas, Contribuições, Sugestões? Poste..
Grato,
Até a próxima.


Nenhum comentário: