#SouDev, focado em #FrontEnd “atualmente”.

Já trabalhei com backend também, desde a modelagem das Entidades, até as interfaces e controllers com ajax.

Uma das minhas maiores dificuldades enquanto estava estudando sobre Orientação a Objetos, era entender de fato o que é um objeto.

E o que a palavra orientação tem a ver com eles. Afinal, o que é Orientação a Objetos?

Não basta sair criando classes, instanciando coisas, ou colocando todo o nosso código estruturado dentro de métodos public function foo()!

Isso não é Orientação a Objetos.

Precisamos ter em mente diversos conceitos de programação, e entender que a granulidade de nossos objetos(SRP), nos guiará a um código melhor, enxuto e menos custoso.

Exemplo Prático

Precisei recentemente, ler diversos feeds de redes sociais, e mostrar os 4~5~7 últimas atividades da timeline/posts/vídeos/fotos…

Eu já tinha cada código isolado mais ou menos pronto:

-> Ler rss do WordPress com php

-> Timeline Twitter com php

..

Ainda precisava ler do Flickr, do Facebook, e do Youtube.

Simplesmente, pegar cada rotina dessa e jogar na aplicação, me levaria a ter um código macarrônico/redundante:

Ok, a classe acima “faz” oque eu precisava, mas está incorreta. De uma forma visual e bem simples, note as áreas que circulei.

Uma “god class”, precisa ser urgentemente refatorada!

E ainda faltam o Facebook, o Flickr, o Youtube…

Refatorando

As repetições e inconsistências estão concentradas no loop que itera o objeto, e nas rotinas de testar se já existe ou criar um novo arquivo de cache.

<?php

/**
 * @file View.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
class View
{


  public function draw( iSource $source, $limit=5 )
  {
    $data = $source->getData();

    $i = 0;
    $html = '';
    foreach( $data AS $item ){
      if( $i==$limit ) break;


      if( $source->criteria( $item ) ){
        $html .= $source->html( $item );
        $i++;
      }
    }
    return $html;
  }

}//View

E o cache, que também não deveria ser responsabilidade de cada “método” twitter(), wordpress()..

<?php

/**
 * @file Cache.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
class Cache
{
  private $path = '../cache/';
  private $view;

  public function setPath( $path )
  {
    $this->path = $path;
  }

  public function __construct( iSource $source, View $view )
  {
    $this->source = $source;
    $this->view = $view;
  }

  public function isCached( $url, $file, $limit=5 )
  {
    $this->source->setURL( $url );
    $cached_file = $this->path.$file;


    if( is_file( $cached_file ) )
      return file_get_contents( $cached_file );

    $contents = $this->view->draw( $this->source, $limit );

    file_put_contents( $cached_file, trim( $contents ) );
    return $contents;
  }


}//Cache

Note que para “dar certo” e fazer sentido, eu criei um contrato: A interface iSource:

<?php

/**
 * @file iSource.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
interface iSource
{
  public function setURL( $url );
  public function html( $data );

  public function getData();
  public function criteria( $item );
}

E então agora, cada “objeto”, afinal Twitter é uma coisa, Facebook é uma outra, e Flickr é outra ainda completamente diferente. Logo, objetos diferentes, podem implementar esse contrato, e garantir compatibilidade com as classes Cache e View.

Timeline do Twitter

<?php

include_once 'iSource.class.php';
/**
 * @file Twitter.class.php
 * @date 2012-05-04
 * @author William Moraes
 * @usage

  $twitter = new Twitter( $reader );
  $twitter->setURL( 'http://twitter.com/statuses/user_timeline/locaweb.json?count=5' );
  echo $view->draw( $twitter );

 */
class Twitter implements iSource
{

  private $reader;
  private $url;

  public function __construct( Reader $reader )
  {
    $this->reader = $reader;
  }

  public function setURL( $url )
  {
    $this->url = $url;
  }


  /**
   * @function html
   */
  public function html( $item )
  {
    $html = "\t".'<li><img src="'.$item->user->profile_image_url.'" alt="'.$item->user->screen_name.'" title="'.$item->user->screen_name.'" /> ';

    $html .= $this->makeLinks( $item->text );
    $html .= '</li>'.PHP_EOL;


    return $html;
  }


  /**
   * @function getData
   */
  public function getData()
  {
    return $this->reader->getJSON( $this->url );
  }


  /**
   * @function criteria
   */
  public function criteria( $item )
  {
    return true;
  }


  /**
   * @function makeLinks
   */
  private function makeLinks( $text )
  {
    return preg_replace(
      Array(
        '/(http:\/\/[\w\.\/]+)/',
        '/[^\w]@([\w]+)/',
        '/(#[\w]+)/'
      ),
      Array(
        '<a href="$1" title="$1" rel="external">$1</a>',
        '<a href="http://twitter.com/#!/$1" title="$1" rel="external">@$1</a>',
        '<a href="http://twitter.com/#!/search/$1" title="$1" rel="external">$1</a>'
      ),
      $text
    );
  }


}//Twitter

Note a simplicidade do método Twitter:html();

Diferente do método LeitorRss:twitter(), agora só oque “realmente” interessa está ali. Afinal, oque mudava entre: LeitorRss:twitter() e LeitorRss:wordpress(), era a forma com que eu montava cada LI, e não o loop em si.

Timeline do Facebook

É simples também agora, plugar a class Facebook para ler a timeline:

<?php

include_once 'iSource.class.php';
/**
 * @file Facebook.class.php
 * @date 2012-05-04
 * @author William Moraes
 * @usage

   $facebook = new Facebook( $reader );
  $facebook->setURL( 'https://graph.facebook.com/locaweb/posts&access_token=SEU_ACCESS_TOKEN&method=get' );
  echo $view->draw( $facebook );

 */
class Facebook implements iSource
{

  private $reader;
  private $url;

  public function __construct( Reader $reader )
  {
    $this->reader = $reader;
  }

  public function setURL( $url )
  {
    $this->url = $url;
  }


  /**
   * @function html
   */
  public function html( $item )
  {

    $ts = strtotime( $item->created_time );
    $date = date( 'd/m/Y à\s H:m', $ts );
    $pieces = explode('_', $item->id );



    return '<li>'.substr( $item->message, 0, 140 ).'..
      postado dia <a href="https://www.facebook.com/locaweb/posts/'.$pieces[1].'" rel="external">'.$date.'</a></li>';
  }


  /**
   * @function getData
   */
  public function getData()
  {
    $data = $this->reader->getJSON( $this->url );
    return $data->data;
  }


  /**
   * @function criteria
   */
  public function criteria( $item )
  {
    return isset( $item->message );
  }

}//Facebook

Usando:

<?php
  header ('Content-type: text/html; charset=utf-8');

  date_default_timezone_set('America/Sao_Paulo');

  include 'class/Reader.class.php';

  include 'class/Twitter.class.php';
  include 'class/Facebook.class.php';
  include 'class/Wordpress.class.php';
  include 'class/Statusblog.class.php';
  include 'class/Flickr.class.php';
  include 'class/View.class.php';
  include 'class/Cache.class.php';


  $reader = new Reader();
  $view = new View();


  $twitter = new Twitter( $reader );
  $cache = new Cache( $twitter, $view );
  echo $cache->isCached( 'http://twitter.com/statuses/user_timeline/locaweb.json?count=10', 'twitter-timeline.txt' );


  $facebook = new Facebook( $reader );
  $cache = new Cache( $facebook, $view );
  echo $cache->isCached( 'https://graph.facebook.com/locaweb/posts&access_token=XXX&method=get', 'facebook-timeline.txt' );


  $wordpress = new WordPress( $reader );
  $cache = new Cache( $wordpress, $view );
  echo $cache->isCached( 'http://feeds.feedburner.com/bloglocaweb', 'rss_blog.txt' );


  /* cache status blog */
  $statusblog = new Statusblog( $reader );
  $cache = new Cache( $statusblog, $view );
  echo $cache->isCached( 'http://statusblog.locaweb.com.br/feed', 'rss_statusblog.txt', 7 );

E enfim, o último participante:

Class Reader

Eu não vi sentido em colocar o cURL, o json_decode(), o file_get_contents()… dentro de cada uma das classes que representam as Mídias Sociais.

Por esse motivo, criei um outro objeto, especializado em “ler”, seja da forma que for, e retornar dados em forma de StandardClass.

<?php

/**
 * @file Reader.class.php
 * @date 2012-05-04
 * @author William Moraes
 */
class Reader
{


  public function getXML( $url )
  {
    $file = $this->curlFile( $url );
    return simplexml_load_string( $file, 'SimpleXMLElement', LIBXML_NOCDATA );
  }
  public function getJSON( $url )
  {
    $result = $this->curlFile( $url );
    return json_decode( $result );
  }

  private function getContents( $url )
  {
    return file_get_contents( $url );
  }

  private function curlFile($url, $timeout=0)
  {
    $ch = curl_init();
    curl_setopt( $ch, CURLOPT_URL, $url );
    //curl_setopt ($ch, CURLOPT_HEADER, 1);
    curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, $timeout );
    $content = curl_exec( $ch );
    curl_close( $ch );

    return $content;
  }
}//Reader

Eu disse não à herança

Geralmente, temos uma certa tendência a querer colocar esses métodos em uma class Abstrata, e então Twitter, Facebook, WordPress, extenderem essa class.

Porém, do ponto de vista da Orientação a Objetos, isso não é bacana. Afinal, nossos objetos usam pq precisam os métodos curlFile, getJSON, mas não são isso.

O objeto Twitter não deve ter a responsabilidade de ir buscar o jSON, nem de fazer o cache, nem de interar um loop.

Apenas deve saber oque fazer com os dados já mastigados, e assim por diante.

Vantagem

Uma vantagem, é que essa class Reader pode ser utilizada em outro projeto, sozinha. Desacoplei o código dela do restante da aplicação.

Extends pode e deve ser usado, mas na hora correta

Dentro deste mesmo projeto, eu precisava retornar dados, de mais de um blog WordPress. E para cada blog, havia um html diferente. Digamos que uma “especialização” do que a class WordPress fazia. Logo, aqui sim, coube usar herança:

<?php

include_once 'iSource.class.php';
/**
 * @file Statusblog.class.php
 * @date 2012-05-04
 * @author William Moraes
 * @usage

   $Statusblog = new Statusblog( $reader );
  $Statusblog->setURL( 'http://feeds.feedburner.com/BlogLocaweb14elwSalvador' );
  echo $view->draw( $Statusblog );


 */
class Statusblog extends WordPress
{

  /**
   * @overwrite parent::html()
   * @function html
   */
  public function html( $item )
  {
    return '<li>
      <a href="'.$item->link.'">'.$item->title.'</a>
      <div class="category">Categoria: '.$item->category.'</div>
      </li>'.PHP_EOL;
  }

}//Facebook

Concluindo

Agora sim, eu tenho um projeto Orientado a Objetos. Um objeto conversando com outro, poucas dependências sólidas e bem resolvidas.

É isso. O que acham ?

criticas? sugestões? dúvidas?