#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?