Usamos cookies para medir audiência e melhorar sua experiência. Você pode aceitar ou recusar a qualquer momento. Veja sobre o iMasters.
Bom, amigos, pra quem conhece a teoria dos grafos ficará mais fácil entender a modelagem do problema.
Tenho uma classe chamada Grafo. Outra classe chamada Vértice. Uma outra classe chama-se CaixeiroViajante e uma outra ainda chama-se VizinhoMaisProximo. Estou estudando heurísticas do CaixeiroViajante.
Bom, eu leio um arquivo texto e crio para cada linha neste arquivo um objeto vértice Vértice com nome (chave associativa para a posição do vértice 1, 2, 3...), coordenadas X e Y, e um Booleano dizendo se este vértice foi visitado ou não. uma e armazeno cada um dos vértices numa estrutura de dados chamado ArrayVertices que fica na classe Grafo.
Então o Grafo possui as referências para estes objetos vértices num array. Ai eu passo o objeto Grafo pra classe CaixeiroViajante que vai realizando as operações. O problema é que, se eu tentar fazer mais de uma operação seguida (exemplo, se eu quiser calcular o caminho pelo Vizinho Mais Proximo mais de uma vez ou N vezes dentro de um LOOP), eu tenho um problema com o array de Vértices (alias, com os objetos dentro dele). O problema é que eu só faço os cálculos se os vértices não tiverem sido visitados. Porém, quando eu faço a primeira operação sobre os vértices, eu mudo a variável booleana de false pra true. Portanto, a partir da 2º vez ele acha que tudo já foi visitado e pára de calcular.
Para resolver isso, eu poderia criar cópias deste array de vértices. Mas se eu clonar o objeto Grafo, não dá certo pois o array de vértices dentro dele é um array de referencias, portanto ele mantém os ponteiros para aqueles objetos Vértices (que estarão todos visitados depois da primeira passagem por eles).
Algum de vocês sabe como resolver este problema? Conseguir copiar pelo menos uma vez esse Array de objetos? Pois se eu fizer isto, eu consigo guardar tudo numa variável aleatória e ficar sobrescrevendo sempre que eu tiver que calcular novamente.
Victor Ferreira,
A solução para o seu problema chama-se Memento, mas poste o código seu código todo aqui, é sempre divertido brincar com Grafo.
:D
Olá, amigos, desculpem a demora para responder
Bom, o código é um pouco grande, mas está assim:
index.php, o que eu acesso quando quero calcular as heurísticas
<?php
require_once 'Vertice.class';
require_once 'Grafo.class';
$grafo = new Grafo();
$arquivo = fopen('grafo.txt' ,'r');
while(($linha = fgets($arquivo)) !== false){
$partesVertice = explode(' ', $linha);
$vertice = new Vertice($partesVertice[0], $partesVertice[1], $partesVertice[2]);
$grafo->adicionarVertice(clone $vertice);
}
fclose($arquivo);
error_reporting(E_ALL);
ini_set('display_errors','On');
require_once 'CaixeiroViajante.class';
$caixeiroViajante = new CaixeiroViajante($grafo);
//$caixeiroViajante->calcularVizinhoMaisProximo();
$caixeiroViajante->TodosVizinhoMaisProximo();
?>
Vertice.class, aqui está o 'problema'.
<?php
class Vertice{
public $x;
public $y;
public $nome;
public $visitado;
public function __construct($nome, $x, $y) {
$this->nome = $nome;
$this->x = $x;
$this->y = $y;
$this->visitado = false;
}
}
?>
Grafo.class, que armazena o arrayVertices e tem uns métodos de debugg
<?php
require_once 'Vertice.class';
class Grafo {
public $arrayVertices;
public $arrayVerticesCopia;
public $arrayVerticesNaoVisitados;
public function __construct() {
$this->arrayVertices = array();
$this->tabelaAdjacencias = array();
$this->caminho = array();
}
public function adicionarVertice($vertice){
$this->arrayVerticesNaoVisitados[$vertice->nome] = true;
$this->arrayVertices[$vertice->nome] = $vertice;
}
public function printarNomeVertices(){
foreach($this->arrayVertices as $chave => $valor) echo $chave.'<BR/>';
}
public function retornarQuantidadeVertices(){
return count($this->arrayVertices);
}
public function printarPosXY($pos){
echo $this->arrayVertices[$pos]->x.'<BR/>';
echo $this->arrayVertices[$pos]->y.'<BR/>';
}
public function calcularDistancia($vertice1, $vertice2){
return sqrt( pow($vertice2->x - $vertice1->x, 2) + pow($vertice2->y - $vertice1->y, 2) );
}
}
?>
CaixeiroViajante.class que é a classe que chama as heuristicas, calcula tempo, printa na tela o resultado.
<?php
require_once 'Grafo.class';
class CaixeiroViajante {
public $grafo;
public $caminho;
public $distanciaCaminho;
public $verticeOrigem;
public $tempo;
public function __construct($grafo) {
$this->grafo = $grafo;
$this->caminho = array();
$this->distanciaCaminho = 0;
}
public function TodosVizinhoMaisProximo(){
$ultimoqtdVertices = $this->grafo->retornarQuantidadeVertices() +1;
$menorTodasDistancias = INF;
$menorTodosCaminhos = array();
$grafo = $this->grafo;
$tempoInicial = $this->retornarTempoSegundos();
require_once 'VizinhoMaisProximo.class';
for($i = 1; $i<$ultimoqtdVertices; $i++){
$caminho = array();
$vizinhoMaisProximo = new VizinhoMaisProximo(clone $this->grafo, $i);
$vizinhoMaisProximo->calcular();
if($vizinhoMaisProximo->distanciaCaminho < $menorTodasDistancias){
$menorTodasDistancias = $vizinhoMaisProximo->distanciaCaminho;// armazena sempre a menor distancia já calculada
$menorTodosCaminhos = $vizinhoMaisProximo->caminho;
}
}
$tempoFinal = $this->retornarTempoSegundos();
$this->tempo = $tempoFinal - $tempoInicial;
$objeto = new stdClass();
$objeto->caminho = $menorTodosCaminhos;
$objeto->distanciaCaminho = $menorTodasDistancias;
$this->imprimirResultado($objeto);
}
private function retornarTempoSegundos(){
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
public function calcularVizinhoMaisProximo(){
require_once 'VizinhoMaisProximo.class';
$grafo = $this->grafo;
$tempoInicial = $this->retornarTempoSegundos();
$vizinhoMaisProximo = new VizinhoMaisProximo($grafo, rand(1, $this->grafo->retornarQuantidadeVertices()));
$vizinhoMaisProximo->calcular();
$tempoFinal = $this->retornarTempoSegundos();
$this->tempo = $tempoFinal - $tempoInicial;
$this->imprimirResultado($vizinhoMaisProximo);
}
public function imprimirResultado($objeto){
echo 'Começando pelo vértice '.$this->verticeOrigem.'..\n\n';
echo 'O caminho é: '.implode('-',$objeto->caminho).'\n\n';
echo 'E a distância percorrida é de: '.$objeto->distanciaCaminho.'\n\n';
echo 'O tempo foi: '.$this->tempo.' segundos\n\n\n';
}
}
?>
VizinhoMaisProximo.class, que é a classe da Heurística do Vizinho Mais Próximo. É ela quem calcula os caminhos. Funciona bem se eu chamá-la uma vez só. Mas se eu chamar num loop sem destruir os outros objetos, tudo terá sido visitado a partir da segunda vez.
<?php
class VizinhoMaisProximo {
public $grafo;
public $caminho;
public $distanciaCaminho;
public $verticeAtual;
public $verticeOrigem;
public function __construct($grafo, $verticeOrigem) {
$this->grafo = $grafo;
$this->caminho = array();
$this->distanciaCaminho = 0;
$this->verticeOrigem = $verticeOrigem;
}
public function calcular(){
$qtdVerticesAux = $this->grafo->retornarQuantidadeVertices();
$qtdVertices = $qtdVerticesAux + 1;
$grafo = $this->grafo;
$caminho = array();
$verticeAtual = $this->verticeOrigem;
$distanciaCaminho = 0;
while($qtdVerticesAux != 0){
$menorDistancia = INF;
$grafo->arrayVertices[$verticeAtual]->visitado = true; //visitou
$caminho[] = $verticeAtual;//adiciona ao caminho
$verticeRetido = $verticeAtual;
for($posVizinho = 1; $posVizinho < $qtdVertices; $posVizinho++){
if($grafo->arrayVertices[$posVizinho]->visitado == false){//só se não tiver sido visitado
$distancia = $grafo->calcularDistancia($grafo->arrayVertices[$verticeAtual], $grafo->arrayVertices[$posVizinho]);
if($distancia < $menorDistancia){
$menorDistancia = $distancia;
$verticeRetido = $posVizinho;
}
}
}
$verticeAtual = $verticeRetido;
if(!is_infinite($menorDistancia)) $distanciaCaminho += $menorDistancia;
$qtdVerticesAux--;
}
//volta à origem
$caminho[] = $this->verticeOrigem;
$distanciaCaminho += $grafo->calcularDistancia($grafo->arrayVertices[$verticeAtual], $grafo->arrayVertices[$this->verticeOrigem]);
//torna global
$this->caminho = $caminho;
$this->distanciaCaminho = $distanciaCaminho;
}
}
?>
Como eu poderia usar Memento para solucionar o meu problema?
Abraços!
Victor,
Analisando seu código, vi que não é um caso de uso de Memento. Na verdade, existe um erro de implementação que causou o problema.
Vou ilustrar Memento aqui apenas para não ficar a citação no vazio, mas em seguida vou postar o algorítimo do vizinho mais próximo sem a necessidade de Memento.
Memento
O design pattern Memento permite, sem violar o encapsulamento, manter o estado interno de um objeto e restaurá-lo posteriormente.
Algumas vezes é necessário armazenar o estado de um objeto para restaurá-lo depois, por isso achei que seria adequado ao seu problema, pois você poderia ter um ponto de verificação (estado original não visitado), visitá-lo e, quando for necessário, voltar ao estado original (não visitado).
Os participantes do Memento são:
Memento:
Armazena o estado interno do Originator. O Memento pode armazenar muito, pouco ou o quanto for necessário do estado interno do Originator.
Originator:
O Originator cria um Memento contendo um ponto de verificação de seu estado interno e usa esse Memento para restaurar seu estado posteriormente.
Caretaker:
É o responsável por cuidar com segurança do Memento, ele apenas cuida, jamais faz qualquer operação ou analisa o conteúdo do Memento.
As consequências de uso do Memento são óbvias, como o Memento preserva o encapsulamento, informações que apenas o Originator deve conhecer não são expostas o que, consequentemente, não dá detalhes sobre a implementação. Porém, se o Originator possuir um volume de dados muito grande, a implementação pode causar problemas.
A implementação, no caso do PHP, é um tanto problemática. O ideal seria se PHP oferecesse artifícios como as classes amigas do C++, dessa forma a interface do Memento seria acessível apenas para o Originator.
<?php
class State {
}
class Memento {
private $state;
public function setState( State $state ) {
$this->state = $state;
}
public function getState() {
return $this->state;
}
}
class Originator {
private $visited;
public function createMemento() {
$state = new State();
$state->visited = $this->visited;
$memento = new Memento();
$memento->setState( $state );
return $memento;
}
public function isVisited() {
return !!$this->visited;
}
public function makeVisited( $visited = true ) {
$this->visited = !!$visited;
}
public function setMemento( Memento $memento ) {
$state = $memento->getState();
$this->visited = $state->visited;
}
}
Claro que essa é uma implementação muito simplista. O uso seria:
<?php
$o = new Originator();
$memento = $o->createMemento(); //cria o ponto de verificação
var_dump( $o->isVisited() ); //bool(false)
$o->makeVisited(); //Muda o estado do objeto
var_dump( $o->isVisited() ); //bool(true)
$o->setMemento( $memento ); //restaura o ponto de verificação
var_dump( $o->isVisited() ); //bool(false)
Você encontra uma outra implementação de Memento aqui :seta: http://forum.imasters.com.br/topic/426661-log-em-phpmysql/page__gopid__1686430#entry1686430
Travelling Salesman Problem
O problema do caixeiro viajante é bem interessante, principalmente quando aplicado à logística ou genética. Já o algorítimo nearest neighbour algorithm, dependendo da situação, pode não ser a melhor escolha. Ele consegue gerar um caminho curto rapidamente, mas não necessariamente o caminho ótimo.
Claro que TSP é um problema NP-completo com complexidade exponencial, mas o algorítimo do vizinho mais próximo pode não ser eficiente se o comprimento dos últimos passos forem muito maiores que dos primeiros passos.
De qualquer forma, você definitivamente não precisa de um participante VizinhoMaisProximo como você tem na sua implementação, principalmente pelo fato do VizinhoMaisProximo ser, por definição, um Vertice e seu participante VizinhoMaisProximo sequer é derivado de Vertice.
Outro detalhe da sua implementação é que um Grafo é composto por um conjunto V, não vazio, de vértices e um conjunto de arestas (pares de V). Sua implementação de grafo não leva em consideração as ligações, apenas os pontos, por isso não é, matematicamente, um Grafo.
A definição de Grafo seria: Graph<Point,Link<Point,Point>>
Sua implementação também dá um excesso de responsabilidade ao participante Grafo: Além de cuidar dos pontos, ele também é responsável pela exibição e isso viola o princípio da responsabilidade única. Escrevi um post sobre isso, veja para compreender o que quero dizer :seta: http://thegodclass.tumblr.com/post/9749358418/refatoracao-s-r-p-contactsdisplay
Como estamos falando de caixeiro viajante, vou fazer a implementação do grafo usando os aspectos geográficos, ou seja, não vou abstrair a definição dos pontos nem dos links.
com/imasters/tsp/GeoGraph.php
<?php
/**
* Exemplo de implementação do problema do caixeiro viajante
* @package com.imasters.tsp
*/
/**
* Definição de um conjunto de pontos ligados por retas.
* @author João Batista Neto <neto.joaobatista@imasters.com.br>
*/
class GeoGraph implements Countable, IteratorAggregate {
/**
* @var array
*/
private $points = array();
/**
* @var array
*/
private $links = array();
/**
* Adiciona uma ligação entre dois pontos ao grafo.
* @param GeographicLink $link
* @throws InvalidArgumentException Se os dois pontos não estiverem
* contidos no grafo.
*/
public function addLink( GeographicLink $link ) {
$a = $link->getPointA();
$b = $link->getPointB();
if ( $this->contains( $a ) && $this->contains( $b ) ) {
$this->links[] = $link;
} else {
$msg = 'Os dois pontos devem estar contidos no grafo';
throw new InvalidArgumentException( $msg );
}
}
/**
* Adiciona um ponto ao grafo se ele já não estiver contido no grafo.
* @param GeographicPoint $point
*/
public function addPoint( GeographicPoint $point ) {
if ( !$this->contains( $point ) ) {
$this->points[] = $point;
}
}
/**
* Verifica se um ponto está contido no grafo.
* @param GeographicPoint $point
* @return boolean TRUE se o ponto estiver contido no grafo.
*/
public function contains( GeographicPoint $point ) {
foreach ( $this->points as $p ) {
if ( $p === $point ) {
return true;
}
}
return false;
}
/**
* Recupera o total de ligações existentes no grafo.
* @return integer
* @see Countable::count()
*/
public function count() {
return count( $this->links );
}
/**
* Recupera o total de pontos existentes no grafo.
* @return integer
*/
public function countPoints() {
return count( $this->points );
}
/**
* Recupera um Iterator com as ligações existentes no grafo.
* @return Iterator
* @see IteratorAggregate::getIterator()
*/
public function getIterator() {
return new ArrayIterator( $this->links );
}
/**
* Recupera um Iterator com os pontos existentes no grafo.
* @return Iterator
*/
public function getPointsIterator() {
return new ArrayIterator( $this->points );
}
}
O GeoGraph, como já foi dito antes, possui um conjunto de pontos e um conjunto de links (pares de pontos):
com/imasters/tsp/GeographicPoint.php
<?php
/**
* Exemplo de implementação do problema do caixeiro viajante
* @package com.imasters.tsp
*/
/**
* Requerido para o cálculo da distância entre dois pontos
*/
require_once 'com/imasters/tsp/GeographicLink.php';
/**
* Definição de um ponto geográfico
* @author João Batista Neto <neto.joaobatista@imasters.com.br>
*/
class GeographicPoint {
/**
* @var float
*/
private $latitude;
/**
* @var float
*/
private $longitude;
/**
* Constroi o ponto indicando a latitude e longitude.
* @param float $latitude
* @param float $longitude
*/
public function __construct( $latitude , $longitude ) {
$this->latitude = $latitude;
$this->longitude = $longitude;
}
/**
* Cria uma ligação entre dois pontos.
* @param GeographicPoint $point O ponto de destino
* @return GeographicLink A ligação entre os dois pontos
*/
public function createLinkTo( GeographicPoint $point ) {
$link = new GeographicLink( $this , $point );
return $link;
}
/**
* Calcula a distância entre dois pontos.
* @param GeographicPoint $point O ponto de destino.
* @return float A distância em kilômetros.
*/
public function distanceTo( GeographicPoint $point ) {
return $this->createLinkTo( $point )->distance();
}
/**
* Recupera a latitude.
* @return float
*/
public function getLatitude() {
return $this->latitude;
}
/**
* Recupera a longitude.
* @return float
*/
public function getLongitude() {
return $this->longitude;
}
}
com/imasters/tsp/GeographicLink.php
<?php
/**
* Exemplo de implementação do problema do caixeiro viajante
* @package com.imasters.tsp
*/
/**
* Definição de uma ligação entre dois pontos geográficos
* @author João Batista Neto <neto.joaobatista@imasters.com.br>
*/
class GeographicLink {
/**
* @var GeographicPoint
*/
private $a;
/**
* @var GeographicPoint
*/
private $b;
/**
* Constroi um objeto de ligação entre dois pontos.
* @param GeographicPoint $a
* @param GeographicPoint $b
*/
public function __construct( GeographicPoint $a , GeographicPoint $b ) {
$this->a = $a;
$this->b = $b;
}
/**
* Calcula a distância entre os dois pontos.
* @return float
*/
public function distance() {
$aLa = $this->a->getLatitude() * M_PI / 180;
$aLo = $this->a->getLongitude() * M_PI / 180;
$bLa = $this->b->getLatitude() * M_PI / 180;
$bLo = $this->b->getLongitude() * M_PI / 180;
$dLa = $bLa - $aLa;
$dLo = $bLo - $aLo;
$a = pow( sin( $dLa / 2 ) , 2 ) + cos( $aLa )
* pow( sin( $dLo / 2 ) , 2 ) * cos( $bLa );
return 12742 * atan2( sqrt( $a ) , sqrt( 1 - $a ) );
}
/**
* Recupera o ponto A.
* @return GeographicPoint
*/
public function getPointA() {
return $this->a;
}
/**
* Recupera o ponto B.
* @return GeographicPoint
*/
public function getPointB() {
return $this->b;
}
}
Como uma cidade é um ponto no mapa que possui um nome, vamos derivar o GeographicPoint para que ele possua um nome:
com/imasters/tsp/City.php
<?php
/**
* Exemplo de implementação do problema do caixeiro viajante
* @package com.imasters.tsp
*/
/**
* A cidade é um ponto geográfico que possui um nome
*/
require_once 'com/imasters/tsp/GeographicPoint.php';
/**
* Definição de uma cidade por onde o caixeiro deverá passar.
* @author João Batista Neto <neto.joaobatista@imasters.com.br>
*/
class City extends GeographicPoint {
/**
* @var string
*/
private $name;
/**
* Cria o objeto que representa uma cidade por onde o caixeiro passará.
* @param string $name Nome da cidade.
* @param float $latitude Latitude da cidade.
* @param float $longitude Longitude da cidade.
*/
public function __construct( $name , $latitude , $longitude ) {
parent::__construct( $latitude , $longitude );
$this->name = $name;
}
/**
* Recupera o nome da cidade.
* @return string
*/
public function getName() {
return $this->name;
}
}
E, por fim, o caixeiro viajante, que usará o algorítimo do vizinho mais próximo para percorrer uma lista de cidades:
com/imasters/tsp/TravellingSalesmanProblem.php
<?php
/**
* Exemplo de implementação do problema do caixeiro viajante
* @package com.imasters.tsp
*/
/**
* Requerido para representação da solução para o problema do caixeiro viajante.
*/
require_once 'com/imasters/tsp/GeoGraph.php';
/**
* Problema do caixeiro viajante.
* @author João Batista Neto <neto.joaobatista@imasters.com.br>
*/
class TravellingSalesmanProblem {
/**
* @var array
*/
private $cities = array();
/**
* Adiciona uma cidade à lista de cidades por onde o caixeiro deverá
* passar.
* @param City $city
*/
public function addCity( City $city ) {
$this->cities[] = $city;
}
/**
* Determina a solução para o problema do caixeiro viajante utilizando
* o algorítimo do vizinho mais próximo.
* @return Graph
*/
public function nearestNeighbourAlgorithm() {
$visited = array();
$g = new GeoGraph();
$c = array_shift( $this->cities );
$t = count( $this->cities );
$d = INF;
$v = null;
$visited[] = $c;
$g->addPoint( $c );
while ( $t > 0 ) {
foreach ( $this->cities as $i => $city ) {
if ( ( $cd = $c->distanceTo( $city ) ) < $d ) {
$d = $cd;
$v = $i;
}
}
$g->addPoint( $this->cities[ $v ] );
$g->addLink( $c->createLinkTo( $this->cities[ $v ] ) );
$c = $this->cities[ $v ];
$d = INF;
$visited[] = $c;
unset( $this->cities[ $v ] );
--$t;
}
$this->cities = $visited; //reset
return $g;
}
}
A implementação é bem simples, o caixeiro viajante recebe uma lista de cidades por onde ele deverá passar. Com essa lista, pegamos um ponto qualquer e definimos como ponto de partida e, então, localizamos a cidade mais próxima de onde estamos.
Esse processo de estar em uma cidade e localizar a cidade mais próxima é feito até que não haja mais cidade para ser visitada.
Para deixar a implementação mais divertida, vamos utilizar as localizações reais (latitude e longitude) de algumas cidades e, para isso, vamos fazer a consulta no GoogleMaps pelo nome de cada cidade.
A implementação do GoogleMaps ficou bastante simplista por ser meramente ilustrativa:
com/imasters/google/maps/GoogleMaps.php
<?php
/**
* Implementação besta de consulta ao Google Maps
* @package com.imasters.google.maps
*/
/**
* Conexão HTTP requerida para consulta ao Google Maps
*/
require_once 'com/imasters/http/HTTPConnection.php';
/**
* Implementação simples de busca ao Google Maps
* @author João Batista Neto <neto.joaobatista@imasters.com.br>
*/
class GoogleMaps {
/**
* @var HTTPConnection
*/
private static $httpConnection;
/**
* Constroi o objeto responsável pelas buscas ao Google Maps
* @param string $language
* @param string $region
*/
public function __construct( $language = 'pt_BR' , $region = 'br' ) {
if ( self::$httpConnection == null ) {
self::$httpConnection = new HTTPConnection();
self::$httpConnection->initialize( 'maps.googleapis.com' );
self::$httpConnection->setParam( 'sensor' , 'false' );
self::$httpConnection->setParam( 'language' , $language );
self::$httpConnection->setParam( 'region' , $region );
}
}
/**
* Efetua uma busca ao Google Maps.
* @param string $address Endereço que será buscado.
* @return stdClass Objeto JSON convertido em stdClass com o resultado
* da busca.
* @throws RuntimeException Caso não seja possível fazer a consulta
*/
public function search( $address ) {
self::$httpConnection->setParam( 'address' , $address );
$r = self::$httpConnection->execute( '/maps/api/geocode/json' );
if ( $r->getStatusCode() == 200 ) {
return json_decode( $r->getContent() );
} else {
throw new RuntimeException( 'Falha ao consultar Google Maps' );
}
}
}
Essa implementação do GoogleMaps utiliza o pacote HTTP que segue abaixo:
com/imasters/http/*
com/imasters/http/Cookie.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
/**
* @brief Cookie HTTP
* @details Implementação de um cookie HTTP segundo a especificação
* RFC 2109.
*/
class Cookie {
/**
* @brief Comentário opcional do cookie
* @var string
*/
protected $comment;
/**
* @brief Domínio do cookie
* @var string
*/
protected $domain;
/**
* @brief Expiração do cookie (unix timestamp)
* @var integer
*/
protected $expires;
/**
* @brief Nome do cookie
* @var string
*/
protected $name;
/**
* @brief Caminho do cookie
* @var string
*/
protected $path;
/**
* @brief Ambiente seguro (HTTPS)
* @details Indica se o User-Agent deve utilizar o cookie apenas em ambiente
* seguro (HTTPS)
* @var boolean
*/
protected $secure;
/**
* @brief Valor do cookie
* @var string
*/
protected $value;
/**
* @brief Constroi um cookie
* @param string $name Nome do cookie
* @param string $value Valor do cookie
* @param string $domain Domínio do cookie
* @param integer $expires Timestamp da expiração do cookie
* @param string $path Caminho do cookie
* @param boolean $secure Se o cookie é usado apenas em ambiente seguro.
* @param string $comment Comentário do cookie
* @throws InvalidArgumentException Se $expires não for um número
*/
public function __construct( $name,
$value,
$domain,
$expires,
$path = '/',
$secure = false,
$comment = null ) {
$this->name = (string) $name;
$this->value = (string) $value;
$this->domain = (string) $domain;
if ( is_numeric( $expires ) ) {
$this->expires = (int) $expires;
} else {
$msg = '$expires deve ser um número representando o timestamp da ';
$msg .= 'expiração do cookie, "' . $expires . '" foi dado.';
throw new InvalidArgumentException( $msg );
}
$this->path = (string) $path;
$this->secure = $secure === true;
$this->comment = $comment;
}
/**
* @brief Retorna a representação do Cookie como uma string
* @return string
*/
public function __toString() {
return sprintf( '%s=%s' , $this->name , $this->value );
}
/**
* @brief Recupera o comentário do cookie
* @return string
*/
public function getComment() {
return $this->comment;
}
/**
* @brief Recupera o domínio do cookie
* @return string
*/
public function getDomain() {
return $this->domain;
}
/**
* @brief Recupera o timestamp da expiração do cookie
* @return integer
*/
public function getExpires() {
return $this->expires;
}
/**
* @brief Recupera o nome do cookie
* @return string
*/
public function getName() {
return $this->name;
}
/**
* @brief Recupera o caminho do cookie
* @return string
*/
public function getPath() {
return $this->path;
}
/**
* @brief Recupera o valor do cookie
* @return string
*/
public function getValue() {
return $this->value;
}
/**
* @brief Verifica ambiente seguro.
* @details Verifica se o User-Agent deve utilizar o cookie apenas em
* ambiente seguro.
* @return boolean
*/
public function isSecure() {
return $this->secure;
}
}
com/imasters/http/CookieManager.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/Cookie.php';
/**
*@brief Interface para definição de um gerenciador de cookies.
*/
interface CookieManager extends Serializable {
/**
* @brief Adiciona um cookie para ser armazenado pelo gerenciador.
* @param Cookie $cookie
*/
public function addCookie( Cookie $cookie );
/**
* @brief Recupera os cookies armazenados para um determinado domínio.
* @param string $domain Domínio dos cookies.
* @param boolean $secure Indica ambiente seguro (https).
* @param string $path Caminho dos cookies.
* @return string O valor retornado segue o padrão especificado pela
* RFC 2965 para ser utilizado diretamente no campo de cabeçalho
* Cookie.
*/
public function getCookie( $domain , $secure , $path );
/**
* @brief Recupera uma lista com os cookies gerenciados.
* @param string $domain Domínio dos cookies.
* @param boolean $secure Indica ambiente seguro.
* @param string $path Caminho dos cookies.
* @return Iterator
*/
public function getCookieIterator( $domain , $secure , $path );
/**
* @brief Define o conteúdo do campo de cabeçalho Set-Cookie
* retornado pelo servidor.
* @param string $setCookie
* @param string $domain
*/
public function setCookie( $setCookie , $domain = null );
}
com/imasters/http/CURL.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/HTTPRequest.php';
require_once 'com/imasters/http/HTTPResponse.php';
/**
* @brief Requisição HTTP cURL
* @details Implementação da interface HTTPRequest para uma requisição HTTP que
* utiliza cURL.
*/
class CURL implements HTTPRequest {
/**
* @var resource
*/
private $curlResource;
/**
* @var HTTPConnection
*/
private $httpConnection;
/**
* @var HTTPResponse
*/
private $httpResponse;
/**
* @var boolean
*/
private $openned = false;
/**
* @var string
*/
private $requestBody;
/**
* @var array
*/
private $requestHeader = array();
/**
* @var array
*/
private $requestParameter = array();
/**
* @brief Destroi o objeto
* @details Destroi o objeto e fecha a requisição se estiver aberta.
*/
public function __destruct() {
$this->close();
}
/**
* @see HTTPRequest::addRequestHeader()
*/
public function addRequestHeader( $name , $value , $override = true ) {
if ( is_scalar( $name ) && is_scalar( $value ) ) {
$key = strtolower( $name );
if ( $override === true || !isset( $this->requestHeader[ $key ] ) ) {
$this->requestHeader[ $key ] = array(
'name' => $name , 'value' => $value
);
return true;
}
return false;
} else {
throw new InvalidArgumentException(
'$name e $value precisam ser strings.'
);
}
}
/**
* @brief Autentica uma requisição HTTP.
* @param HTTPAuthenticator $authenticator
* @see HTTPRequest::authenticate()
*/
public function authenticate( HTTPAuthenticator $authenticator ) {
$authenticator->authenticate( $this );
}
/**
* @see HTTPRequest::close()
*/
public function close() {
if ( $this->openned ) {
curl_close( $this->curlResource );
$this->openned = false;
}
}
/**
* @see HTTPRequest::execute()
*/
public function execute( $path = '/' , $method = HTTPRequest::GET ) {
$targetURL = $this->httpConnection->getURI() . $path;
$query = null;
if ( ( $hasParameters = count( $this->requestParameter ) ) > 0 ) {
$query = http_build_query( $this->requestParameter );
}
switch ( $method ) {
case HTTPRequest::PUT :
case HTTPRequest::POST :
if ( $method != HTTPRequest::POST ) {
curl_setopt(
$this->curlResource,
CURLOPT_CUSTOMREQUEST,
$method
);
} else {
curl_setopt( $this->curlResource , CURLOPT_POST , 1 );
}
if ( empty( $this->requestBody ) ) {
curl_setopt(
$this->curlResource,
CURLOPT_POSTFIELDS,
$query
);
} else {
if ( $hasParameters ) {
$targetURL .= '?' . $query;
}
curl_setopt(
$this->curlResource,
CURLOPT_POSTFIELDS,
$this->requestBody
);
}
curl_setopt( $this->curlResource , CURLOPT_URL , $targetURL );
break;
case HTTPRequest::DELETE :
case HTTPRequest::HEAD :
case HTTPRequest::OPTIONS:
case HTTPRequest::TRACE:
curl_setopt(
$this->curlResource,
CURLOPT_CUSTOMREQUEST,
$method
);
case HTTPRequest::GET:
if ( $hasParameters ) {
$targetURL .= '?' . $query;
}
curl_setopt( $this->curlResource , CURLOPT_URL , $targetURL );
break;
default :
throw new UnexpectedValueException( 'Método desconhecido' );
}
$resp = curl_exec( $this->curlResource );
$errno = curl_errno( $this->curlResource );
$error = curl_error( $this->curlResource );
if ( $errno != 0 ) {
throw new RuntimeException( $error , $errno );
}
$this->httpResponse = new HTTPResponse();
$this->httpResponse->setRawResponse( $resp );
if ( $this->httpResponse->hasResponseHeader( 'Set-Cookie' ) ) {
$cookieManager = $this->httpConnection->getCookieManager();
if ( $cookieManager != null ) {
$cookieManager->setCookie(
$this->httpResponse->getHeader( 'Set-Cookie' ),
$this->httpConnection->getHostName()
);
}
}
$statusCode = $this->httpResponse->getStatusCode();
return $statusCode < 400;
}
/**
* @see HTTPRequest::getResponse()
*/
public function getResponse() {
return $this->httpResponse;
}
/**
* @see HTTPRequest::open()
*/
public function open( HTTPConnection $httpConnection ) {
if ( function_exists( 'curl_init' ) ) {
/**
* Fechamos uma conexão existente antes de abrir uma nova
*/
$this->close();
$curl = curl_init();
/**
* Verificamos se o recurso CURL foi criado com êxito
*/
if ( is_resource( $curl ) ) {
curl_setopt( $curl , CURLOPT_SSL_VERIFYPEER , 0 );
curl_setopt( $curl , CURLOPT_HEADER , 1 );
curl_setopt( $curl , CURLOPT_RETURNTRANSFER , 1 );
curl_setopt( $curl , CURLINFO_HEADER_OUT , 1 );
if ( ( $timeout = $httpConnection->getTimeout() ) != null ) {
curl_setopt( $curl , CURLOPT_TIMEOUT , $timeout );
}
$ct = $httpConnection->getConnectionTimeout();
if ( $ct != null ) {
curl_setopt( $curl , CURLOPT_CONNECTTIMEOUT , $ct );
}
$headers = array();
foreach ( $this->requestHeader as $header ) {
$headers[] = sprintf(
'%s: %s' , $header[ 'name' ] , $header[ 'value' ]
);
}
curl_setopt( $curl , CURLOPT_HTTPHEADER , $headers );
$this->curlResource = $curl;
$this->httpConnection = $httpConnection;
$this->openned = true;
} else {
throw new RuntimeException( 'Não foi possível iniciar cURL' );
}
} else {
throw new RuntimeException( 'Extensão cURL não está instalada.' );
}
}
/**
* @brief Define um parâmetro
* @details Define um parâmetro que será enviado com a requisição, um
* parâmetro é um par nome-valor que será enviado como uma query
* string (<b>ex:</b> <i>?name=value</i>).
* @param string $name Nome do parâmetro.
* @param string $value Valor do parâmetro.
* @throws InvalidArgumentException Se o nome ou o valor do campo não forem
* valores scalar.
* @see HTTPRequest::setParameter()
*/
public function setParameter( $name , $value ) {
$this->requestParameter[ $name ] = $value;
}
/**
* @see HTTPRequest::setRequestBody()
*/
public function setRequestBody( $requestBody ) {
$this->requestBody = $requestBody;
}
}
com/imasters/http/HTTPAuthenticator.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/HTTPRequest.php';
/**
* @brief Interface para definição de um autenticador HTTP.
*/
interface HTTPAuthenticator {
/**
* @brief Autentica uma requisição HTTP.
* @param HTTPRequest $httpRequest
*/
public function authenticate( HTTPRequest $httpRequest );
}
com/imasters/http/HTTPConnection.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/HTTPAuthenticator.php';
require_once 'com/imasters/http/CURL.php';
/**
* @brief Implementação de um conector HTTP.
*/
class HTTPConnection {
/**
* @brief Porta padrão de uma conexão HTTP não segura.
*/
const HTTP_PORT = 80;
/**
* @brief Porta padrão de uma conexão HTTP segura.
*/
const HTTPS_PORT = 443;
/**
* @var HTTPAuthenticator
*/
protected $httpAuthenticator;
/**
* @var CookieManager
*/
protected $cookieManager;
/**
* @var integer
*/
protected $connectionTimeout;
/**
* @var string
*/
protected $hostname;
/**
* @var boolean
*/
protected $initialized = false;
/**
* @var integer
*/
protected $port;
/**
* @var string
*/
protected $requestBody;
/**
* @var array
*/
protected $requestHeader;
/**
* @var array
*/
protected $requestParameter;
/**
* @var boolean
*/
protected $secure;
/**
* @var integer
*/
protected $timeout;
/**
* @var string
*/
protected static $userAgent;
/**
* @brief Constroi o objeto de conexão HTTP.
*/
public function __construct() {
if ( self::$userAgent == null ) {
$locale = setlocale( LC_ALL , null );
if ( function_exists( 'posix_uname' ) ) {
$uname = posix_uname();
self::$userAgent = sprintf(
'Mozilla/4.0 (compatible; %s; PHP/%s; %s %s; %s)',
PHP_SAPI , PHP_VERSION , $uname[ 'sysname' ],
$uname[ 'machine' ] , $locale
);
} else {
self::$userAgent = sprintf(
'Mozilla/4.0 (compatible; %s; PHP/%s; %s; %s)',
PHP_SAPI , PHP_VERSION , PHP_OS , $locale
);
}
}
$this->requestHeader = array();
$this->requestParameter = array();
}
/**
* @brief Adiciona um campo de cabeçalho para ser enviado com a
* requisição.
* @param string $name Nome do campo de cabeçalho.
* @param string $value Valor do campo de cabeçalho.
* @param boolean $override Indica se o campo deverá ser sobrescrito caso
* já tenha sido definido.
* @throws InvalidArgumentException Se o nome ou o valor do campo não forem
* valores scalar.
*/
public function addHeader( $name , $value , $override = true ) {
if ( is_scalar( $name ) && is_scalar( $value ) ) {
$key = strtolower( $name );
if ( $override === true || !isset( $this->requestHeader[ $key ] ) ) {
$this->requestHeader[ $key ] = array(
'name' => $name , 'value' => $value
);
return true;
}
return false;
} else {
throw new InvalidArgumentException(
'$name e $value precisam ser strings.'
);
}
}
/**
* @brief Fecha a conexão.
* @throws BadMethodCallException Se não houver uma conexão inicializada.
*/
public function close() {
$this->initialized = false;
}
/**
* @brief Executa a requisição
* @details Executa a requisição HTTP em um caminho utilizando um método
* específico.
* @param string $path Caminho da requisição.
* @param string $method Método da requisição.
* @return HTTPResponse Resposta HTTP.
* @throws BadMethodCallException Se não houver uma conexão inicializada ou
* se o objeto de requisição não for válido.
*/
public function execute( $path = '/' , $method = HTTPRequest::GET ) {
$request = $this->newRequest();
if ( $request instanceof HTTPRequest ) {
$host = $this->getHost();
$accept = '*/*';
$userAgent = self::$userAgent;
if ( isset( $this->requestHeader[ 'Host' ] ) ) {
$host = $this->requestHeader[ 'host' ][ 'value' ];
unset( $this->requestHeader[ 'host' ] );
}
if ( isset( $this->requestHeader[ 'accept' ] ) ) {
$accept = $this->requestHeader[ 'accept' ][ 'value' ];
unset( $this->requestHeader[ 'accept' ] );
}
if ( isset( $this->requestHeader[ 'user-agent' ] ) ) {
$userAgent = $this->requestHeader[ 'user-agent' ][ 'value' ];
unset( $this->requestHeader[ 'user-agent' ] );
}
$request->addRequestHeader( 'Host' , $host );
$request->addRequestHeader( 'Accept' , $accept );
$request->addRequestHeader( 'User-Agent' , $userAgent );
if ( $this->httpAuthenticator != null ) {
$request->authenticate( $this->httpAuthenticator );
}
foreach ( $this->requestHeader as $header ) {
$request->addRequestHeader(
$header[ 'name' ] , $header[ 'value' ]
);
}
$cookieManager = $this->getCookieManager();
if ( $cookieManager != null ) {
$cookies = $cookieManager->getCookie(
$this->getHostName() , $this->isSecure() , $path
);
if ( isset( $this->requestHeader[ 'cookie' ] ) ) {
$buffer = $this->requestHeader[ 'cookie' ][ 'value' ];
$buffer .= '; ' . $cookies;
} else {
$buffer = $cookies;
}
$request->addRequestHeader( 'Cookie' , $buffer );
}
foreach ( $this->requestParameter as $name => $value ) {
$request->setParameter( $name , $value );
}
$request->setRequestBody( $this->requestBody );
if ( $path == null || !is_string( $path ) || empty( $path ) ) {
$path = '/';
} else if ( substr( $path , 0 , 1 ) != '/' ) {
$path = '/' . $path;
}
if ( $this->timeout != null ) {
$request->setTimeout( $this->timeout );
}
if ( $this->connectionTimeout != null ) {
$request->setConnectionTimeout( $this->connectionTimeout );
}
$request->open( $this );
$request->execute( $path , $method );
return $request->getResponse();
} else {
throw new BadMethodCallException( 'Objeto de requisição inválido.' );
}
}
/**
* @brief Recupera o timeout de conexão.
* @return integer
*/
public function getConnectionTimeout() {
return $this->connectionTimeout;
}
/**
* @brief Recupera o gerenciador de Cookies.
* @return CookieManager
*/
public function getCookieManager() {
return $this->cookieManager;
}
/**
* @brief Recupera o host da conexão.
* @return string
* @throws BadMethodCallException Se a conexão não tiver
* sido inicializada.
*/
public function getHost() {
if ( $this->initialized ) {
$hostname = $this->getHostName();
if ( ( $this->secure && $this->port != HTTPConnection::HTTPS_PORT )
|| ( !$this->secure && $this->port != HTTPConnection::HTTP_PORT ) ) {
return $hostname . ':' . $this->port;
} else {
return $hostname;
}
} else {
throw new BadMethodCallException( 'Conexão não inicializada' );
}
}
/**
* @brief Recupera o nome do host.
* @return string
* @throws BadMethodCallException Se não houver uma conexão
* inicializada.
*/
public function getHostName() {
if ( $this->initialized ) {
return $this->hostname;
} else {
throw new BadMethodCallException( 'Conexão não inicializada' );
}
}
/**
* @brief Recupera a porta que será utilizada na conexão.
* @return integer
* @throws BadMethodCallException Se não houver uma conexão
* inicializada.
*/
public function getPort() {
if ( $this->initialized ) {
return $this->port;
} else {
throw new BadMethodCallException( 'Conexão não inicializada' );
}
}
/**
* @brief Recupera o timeout.
* @return integer
*/
public function getTimeout() {
return $this->timeout;
}
/**
* @brief Recupera a URI que será utilizada na conexão.
* @return string
* @throws BadMethodCallException Se não houver uma conexão
* inicializada.
*/
public function getURI() {
if ( $this->initialized ) {
return sprintf(
'%s://%s' , $this->isSecure() ? 'https' : 'http',
$this->getHost()
);
} else {
throw new BadMethodCallException( 'Conexão não inicializada' );
}
}
/**
* @brief Inicializa a conexão HTTP.
* @param string $hostname Servidor que receberá a requisição.
* @param boolean $secure Indica se a conexão será segura (https).
* @param integer $port Porta da requisição.
* @param integer $connectionTimeout Timeout de conexão em segundos.
* @param integer $timeout Timeout de espera em segundos.
*/
public function initialize( $hostname , $secure = false,
$port = HTTPConnection::HTTP_PORT,
$connectionTimeout = 0,
$timeout = 0 ) {
if ( $this->initialized ) {
$this->close();
}
$this->initialized = true;
$this->hostname = $hostname;
$this->secure = $secure === true;
if ( func_num_args() == 2 ) {
if ( $this->secure ) {
$this->port = HTTPConnection::HTTPS_PORT;
} else {
$this->port = HTTPConnection::HTTP_PORT;
}
} else {
$this->port = (int) $port;
}
$this->connectionTimeout = (int) $connectionTimeout;
$this->timeout = (int) $timeout;
}
/**
* @brief Verifica se é uma conexão segura.
* @return boolean
*/
public function isSecure() {
return $this->secure === true;
}
/**
* @brief Cria uma instância de um objeto de requisição HTTP.
* @return HTTPRequest
*/
public function newRequest() {
return new CURL();
}
/**
* @brief Define um autenticador HTTP.
* @param HTTPAuthenticator $httpAuthenticator
*/
public function setAuthenticator( HTTPAuthenticator $httpAuthenticator ) {
$this->httpAuthenticator = $httpAuthenticator;
}
/**
* @brief Define o timeout de conexão.
* @param integer $connectionTimeout
* @throws InvalidArgumentException Se $connectionTimeout não for um inteiro.
*/
public function setConnectionTimeout( $connectionTimeout ) {
if ( is_integer( $connectionTimeout ) ) {
$this->connectionTimeout = $connectionTimeout;
} else {
throw new InvalidArgumentException(
'$connectionTimeout precisa ser o tempo em segundos.'
);
}
}
/**
* @brief Define um gerenciador de cookies para essa conexão.
* @param CookieManager $cookieManager
*/
public function setCookieManager( CookieManager $cookieManager ) {
$this->cookieManager = $cookieManager;
}
/**
* @brief Define um parâmetro
* @details Define um parâmetro que será enviado com a requisição, um
* parâmetro é um par nome-valor que será enviado como uma query
* string (<b>ex:</b> <i>?name=value</i>).
* @param string $name Nome do parâmetro.
* @param string $value Valor do parâmetro.
* @throws InvalidArgumentException Se o nome ou o valor
* do campo não forem valores scalar.
*/
public function setParam( $name , $value = null ) {
if ( is_scalar( $name ) && ( is_scalar( $value ) || is_null( $value ) ) ) {
$this->requestParameter[ $name ] = $value;
} else {
throw new InvalidArgumentException(
'$name e $value precisam ser strings.'
);
}
}
/**
* @brief Define o corpo da requisição.
* @param string $requestBody
*/
public function setRequestBody( $requestBody ) {
$this->requestBody = $requestBody;
}
/**
* @brief Define o timeout.
* @param integer $timeout
* @throws InvalidArgumentException Se $timeout não for um inteiro.
*/
public function setTimeout( $timeout ) {
if ( is_integer( $timeout ) ) {
$this->timeout = $timeout;
} else {
throw new InvalidArgumentException(
'$timeout precisa ser o tempo em segundos.'
);
}
}
}
com/imasters/http/HTTPCookieManager.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/CookieManager.php';
/**
* @brief Gerenciador de Cookies HTTP
* @details Implementação da interface CookieManager para criação de um
* gerenciador de cookies que armazena os cookies em um arquivo em
* disco.
*/
class HTTPCookieManager implements CookieManager {
/**
* @var string
*/
private $cookieFile;
/**
* @var array
*/
private $cookies = array();
/**
* @brief Constroi o gerenciador de cookies que grava as informações em um
* arquivo.
* @param string $dirname Diretório onde os cookies serão gravados, caso
* não informado o diretório temporário do sistema será utilizado.
*/
public function __construct( $dirname = null ) {
if ( $dirname == null ) {
$dirname = sys_get_temp_dir();
}
if ( is_readable( $dirname ) && is_writable( $dirname ) ) {
$cookieFile = realpath( $dirname ) . '/cookie.jar';
if ( !is_file( $cookieFile ) ) {
touch( $cookieFile );
} else {
$cookieManager = unserialize( file_get_contents( $cookieFile ) );
if ( $cookieManager instanceof HTTPCookieManager ) {
$this->cookies = $cookieManager->cookies;
}
}
$this->cookieFile = $cookieFile;
} else {
$msg = 'O diretório ' . $dirname;
$msg .= ' precisa ter permissões de leitura e gravação.';
throw new RuntimeException( $msg );
}
}
/**
* @brief Destroi o objeto e salva os cookies armazenados
*/
public function __destruct() {
if ( $this->cookieFile != null ) {
file_put_contents( $this->cookieFile , serialize( $this ) );
}
}
/**
* @see CookieManager::addCookie()
*/
public function addCookie( Cookie $cookie ) {
$cookieDomain = $cookie->getDomain();
if ( !isset( $this->cookies[ $cookieDomain ] ) ) {
$this->cookies[ $cookieDomain ] = array();
}
$this->cookies[ $cookieDomain ][] = $cookie;
}
/**
* @see CookieManager::getCookie()
*/
public function getCookie( $domain , $secure , $path ) {
return implode(
'; ' , $this->getCookieArray( $domain , $secure , $path )
);
}
private function getCookieArray( $domain , $secure , $path ) {
$cookies = array();
$secure = $secure === true;
if ( isset( $this->cookies[ $domain ] ) ) {
foreach ( $this->cookies[ $domain ] as $cookie ) {
if (
$cookie->isSecure() == $secure &&
$cookie->getPath() == $path ) {
$cookies[] = $cookie;
}
}
}
return $cookies;
}
/**
* @see CookieManager::getCookieIterator()
*/
public function getCookieIterator( $domain , $secure , $path ) {
return new ArrayIterator(
$this->getCookieArray( $domain , $secure , $path )
);
}
/**
* @see CookieManager::setCookie()
*/
public function setCookie( $setCookie , $domain = null ) {
if ( is_array( $setCookie ) ) {
foreach ( $setCookie as $setCookieItem ) {
$this->setCookie( $setCookieItem );
}
} else {
$matches = array();
if ( preg_match(
'/(?<name>[^\=]+)\=(?<value>[^;]+)'.
'(; expires=(?<expires>[^;]+))?'.
'(; path=(?<path>[^;]+))?'.
'(; domain=(?<domain>[^;]+))?'.
'(; (?<secure>secure))?'.
'(; (?<httponly>httponly))?/' , $setCookie , $matches ) ){
$cookieName = null;
$cookieValue = null;
$cookieExpires = INF;
$cookiePath = '/';
$cookieDomain = $domain;
$cookieSecure = false;
foreach ( $matches as $key => $value ) {
if ( !empty( $value ) ) {
switch ( $key ) {
case 'name' :
$cookieName = $value;
break;
case 'value' :
$cookieValue = $value;
break;
case 'expires' :
$cookieExpires = strtotime( $value );
break;
case 'path' :
$cookiePath = $value;
break;
case 'domain' :
$cookieDomain = $value;
break;
case 'secure' :
$cookieSecure = true;
break;
}
}
}
if ( !isset( $this->cookies[ $cookieDomain ] ) ) {
$this->cookies[ $cookieDomain ] = array();
}
$this->cookies[ $cookieDomain ][] = new Cookie(
$cookieName,
$cookieValue,
$cookieDomain,
$cookieExpires,
$cookiePath,
$cookieSecure
);
}
}
}
/**
* @see Serializable::serialize()
*/
public function serialize() {
return serialize( $this->cookies );
}
/**
* @see Serializable::unserialize()
*/
public function unserialize( $serialized ) {
$cookies = unserialize( $serialized );
if ( is_array( $cookies ) ) {
$now = time();
foreach ( $cookies as $domain => $domainCookies ) {
foreach ( $domainCookies as $cookie ) {
if ( $cookie instanceof Cookie ) {
if ( $cookie->getExpires() > $now ) {
if ( !isset( $this->cookies[ $domain ] ) ) {
$this->cookies[ $domain ] = array();
}
$this->cookies[ $domain ][] = $cookie;
}
}
}
}
}
}
}
com/imasters/http/HTTPRequest.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/HTTPConnection.php';
require_once 'com/imasters/http/HTTPAuthenticator.php';
/**
* @brief Requisição HTTP
* @details Interface para definição de um objeto que fará uma requisição HTTP.
*/
interface HTTPRequest {
/**
* Método DELETE
*/
const DELETE = 'DELETE';
/**
* Método GET
*/
const GET = 'GET';
/**
* Método HEAD
*/
const HEAD = 'HEAD';
/**
* Método OPTIONS
*/
const OPTIONS = 'OPTIONS';
/**
* Método POST
*/
const POST = 'POST';
/**
* Método PUT
*/
const PUT = 'PUT';
/**
* Método TRACE
*/
const TRACE = 'TRACE';
/**
* @brief Adiciona um campo de cabeçalho para ser enviado com a requisição.
* @param string $name Nome do campo de cabeçalho.
* @param string $value Valor do campo de cabeçalho.
* @param boolean $override Indica se o campo deverá ser sobrescrito caso
* já tenha sido definido.
* @throws InvalidArgumentException Se o nome ou o valor do campo não forem
* valores scalar.
*/
public function addRequestHeader( $name , $value , $override = true );
/**
* @brief Autentica uma requisição HTTP.
* @param HTTPAuthenticator $authenticator
*/
public function authenticate( HTTPAuthenticator $authenticator );
/**
* @brief Fecha a requisição.
*/
public function close();
/**
* @brief Executa a requisição HTTP
* @details Executa a requisição HTTP em um caminho utilizando um método
* específico.
* @param string $method Método da requisição.
* @param string $path Alvo da requisição.
* @return string Resposta HTTP.
* @throws BadMethodCallException Se não houver uma conexão inicializada.
*/
public function execute( $path = '/' , $method = HTTPRequest::GET );
/**
* @brief Recupera a resposta da requisição.
* @return HTTPResponse
*/
public function getResponse();
/**
* @brief Abre a requisição.
* @param HTTPConnection $httpConnection Conexão HTTP relacionada com essa
* requisição
*/
public function open( HTTPConnection $httpConnection );
/**
* @brief Define um parâmetro
* @details Define um parâmetro que será enviado com a requisição, um
* parâmetro é um par nome-valor que será enviado como uma query
* string (<b>ex:</b> <i>?name=value</i>).
* @param string $name Nome do parâmetro.
* @param string $value Valor do parâmetro.
* @throws InvalidArgumentException Se o nome ou o valor
* do campo não forem valores scalar.
*/
public function setParameter( $name , $value );
/**
* @brief Corpo da requisição HTTP.
* @param string $contentBody
*/
public function setRequestBody( $requestBody );
}
com/imasters/http/HTTPResponse.php
<?php
/**
* @brief Protocolo HTTP
* @details Classes e interfaces relacionadas com o protocolo HTTP
* @package com.imasters.http
*/
require_once 'com/imasters/http/CookieManager.php';
/**
* @brief Resposta HTTP
* @details Implementação de um objeto representa uma resposta HTTP.
*/
class HTTPResponse {
/**
* @var array
*/
private $responseHeader = array();
/**
* @var string
*/
private $responseBody;
/**
* @var integer
*/
private $statusCode;
/**
* @var string
*/
private $statusMessage;
/**
* @brief Recupera o corpo da resposta HTTP.
* @return string
*/
public function getContent() {
return $this->responseBody;
}
/**
* @brief Recupera o tamanho do corpo da resposta.
* @return integer
*/
public function getContentLength() {
return $this->getHeaderInt( 'Content-Length' );
}
/**
* @brief Recupera o tipo de conteúdo da resposta.
* @return string
*/
public function getContentType() {
return $this->getHeader( 'Content-Type' );
}
/**
* @brief Recupera o código de status da resposta do servidor.
* @return integer
*/
public function getStatusCode() {
return $this->statusCode;
}
/**
* @brief Recupera a mensagem de status da resposta do servidor.
* @return string
*/
public function getStatusMessage() {
return $this->statusMessage;
}
/**
* @brief Verifica se existe um cabeçalho de resposta HTTP.
* @param string $name Nome do cabeçalho
* @return boolean
*/
public function hasResponseHeader( $name ) {
return isset( $this->responseHeader[ strtolower( $name ) ] );
}
/**
* @brief Recupera o valor um campo de cabeçalho da resposta HTTP.
* @param string $name Nome do campo de cabeçalho.
* @return string O valor do campo ou NULL se não estiver
* existir.
*/
public function getHeader( $name ) {
$key = strtolower( $name );
if ( isset( $this->responseHeader[ $key ] ) ) {
if ( !isset( $this->responseHeader[ $key ][ 'name' ] ) &&
is_array( $this->responseHeader[ $key ] ) ) {
$values = array();
foreach ( $this->responseHeader[ $key ] as $header ) {
$values[] = $header[ 'value' ];
}
return $values;
} else {
return $this->responseHeader[ $key ][ 'value' ];
}
}
return null;
}
/**
* @brief Recupera um valor como inteiro de um campo de cabeçalho da
* resposta HTTP.
* @param string $name Nome do campo de cabeçalho.
* @return integer
*/
public function getHeaderInt( $name ) {
return (int) $this->getHeader( $name );
}
/**
* @brief Recupera um valor como unix timestamp de um campo de cabeçalho
* da resposta HTTP.
* @param string $name Nome do campo de cabeçalho.
* @return integer UNIX Timestamp ou NULL se não estiver definido.
*/
public function getHeaderDate( $name ) {
$date = $this->getHeader( $name );
if ( !is_null( $date ) && !empty( $date ) ) {
return strtotime( $date );
}
}
/**
* @brief Define a resposta da requisição HTTP.
* @param string $response Toda a resposta da requisição
*/
public function setRawResponse( $response,
CookieManager $cookieManager = null ) {
$parts = explode( "\r\n\r\n" , $response );
if ( count( $parts ) == 2 ) {
$matches = array();
$this->responseBody = $parts[ 1 ];
if ( preg_match_all(
'/(HTTP\/[1-9]\.[0-9]\s+'.
'(?<statusCode>\d+)\s+(?<statusMessage>.*)|'.
"(?<headerName>[^:]+)\\s*:\\s*(?<headerValue>.*))\r\n/m",
$parts[ 0 ] , $matches ) ) {
foreach ( $matches[ 'statusCode' ] as $o => $match ) {
if ( !empty( $match ) ) {
$this->statusCode = (int) $match;
$this->statusMessage = $matches[ 'statusMessage' ][ $o ];
break;
}
}
foreach ( $matches[ 'headerName' ] as $o => $name ) {
if ( !empty( $name ) ) {
$k = strtolower( $name );
$header = array(
'name' => $name,
'value' => $matches[ 'headerValue' ][ $o ]
);
if ( isset( $this->responseHeader[ $k ] ) ) {
if ( isset( $this->responseHeader[ $k ][ 'name' ] ) ) {
$this->responseHeader[ $k ] = array(
$this->responseHeader[ $k ]
);
}
$this->responseHeader[ $k ][] = $header;
} else {
$this->responseHeader[ $k ] = $header;
}
}
}
}
} else {
$this->responseBody = $response;
}
}
}
Agora misturamos tudo isso ai e preparamos um exemplo de uso:
<?php
require_once 'com/imasters/google/maps/GoogleMaps.php';
require_once 'com/imasters/tsp/City.php';
require_once 'com/imasters/tsp/TravellingSalesmanProblem.php';
/**
* Objeto que representa o problema do caixeiro viajante
* @var TravellingSalesmanProblem
*/
$tsp = new TravellingSalesmanProblem();
/**
* Cidades por onde o caixeiro viajante deverá passar
* @var array
*/
$cities = array(
'Franca - SP, Brasil',
'São Paulo - SP, Brasil',
'Vitória - ES, Brasil',
'Ribeirão Preto - SP, Brasil',
'Guararema - SP, Brasil',
'Batatais - SP, Brasil'
);
/**
* Vamos utilizar o Google Maps para recuperar as coordenadas geográficas
* (latitude e longitude) de cada uma das cidades.
* @var GoogleMaps
*/
$maps = new GoogleMaps();
/**
* Percorremos a matriz de cidades e buscamos no Google Maps a latitude e
* longitude para criar os objetos City.
*/
foreach ( $cities as $city ) {
/**
* Faz a busca pelo nome da cidade
* @var stdClass
*/
$json = $maps->search( $city );
/**
* Se houver resultados, adicionamos a cidade na lista de cidades que o
* caixeiro deverá visitar.
*/
if ( count( $json->results ) == 1 ) {
$result = array_shift( $json->results );
$tsp->addCity( new City(
$result->formatted_address,
$result->geometry->location->lat,
$result->geometry->location->lng
) ); throw new RuntimeException(
'Não foi possível localizar cidade ' . $city
);
}
}
/**
* Nesse instante já fizemos a busca pela localização geográfica de cada uma
* das cidades e vamos marcar o tempo que o algorítimo levará para resolver
* o problema.
* @var float
*/
$start = microtime( true );
/**
* Utilizamos o algorítimo do vizinho mais próximo para identificar a rota que
* o caixeiro deverá percorrer.
*/
foreach ( $g = $tsp->nearestNeighbourAlgorithm() as $l ) {
$a = $l->getPointA();
$b = $l->getPointB();
$d = $l->distance();
printf( "%.02fKm de '%s' até '%s'\n" , $d , $a->getName() , $b->getName() );
}
printf(
"\nExistem %d pontos e %d arestas no grafo\n",
$g->countPoints() , $g->count()
);
printf( "Levamos %fs para resolver o problema.\n",
microtime( true ) - $start
);
A saída deverá ser alguma coisa parecida com:
>
42,56Km de 'Franca - São Paulo, Brasil' até 'Batatais - São Paulo, Brasil'
40,00Km de 'Batatais - São Paulo, Brasil' até 'Ribeirão Preto - São Paulo, Brasil'
290,43Km de 'Ribeirão Preto - São Paulo, Brasil' até 'São Paulo, Brasil'
62,84Km de 'São Paulo, Brasil' até 'Guararema - São Paulo, Brasil'
684,98Km de 'Guararema - São Paulo, Brasil' até 'Vitória - ES, Brasil'
Existem 6 pontos e 5 arestas no grafo
Levamos 0,007021ms para resolver o problema.
;)
PS: Se quiser continuar brincando disso, podemos implementar outros algorítimos.
E mais uma vez o João dá uma aula ajudando a galera do fórum! Valeu João! Parabéns cara! #orgulho
>
E mais uma vez o João dá uma aula ajudando a galera do fórum! Valeu João! Parabéns cara! #orgulho
Concordo em tudo
#orgulho³
>
E mais uma vez o João dá uma aula ajudando a galera do fórum! Valeu João! Parabéns cara! #orgulho
>
Concordo em tudo
#orgulho³
kkkkkkkkk
Valeu pessoal.
:lol:
Em PHP, objetos são passador SEMPRE por referência.
Para evitar isso, use a palavra chave clone, que cria uma cópia de um dado objeto.