Witaj, Gościu O nas | Kontakt | Mapa
Wortal Forum PHPEdia.pl Planeta Kubek IRC Przetestuj się!

cURL cz. 3: Zaawansowane funkcje

Czytnik RSS

Struktura klas

Zajmijmy się w końcu czytnikiem RSS, który obiecałem już od początku artykułu. W skład naszego skryptu będą wchodzić trzy klasy: Entry, Channel oraz Channels. Pierwsza będzie zawierać dane wpisu i referencję do obiektu klasy drugiej. Ta zaś będzie przechowywać informacje o kanale i jego wpisy. Ostatnia będzie odpowiedzialna za obsługę pobierania danych z kanałów. Wszystkie pliki umieścimy w jednym folderze, aby uniknąć nieporozumień.

Pierwsze dwie klasy będą bardzo proste i nie mają nic wspólnego z Client URL Library, dlatego pozwolę sobie wkleić ich kod bez objaśniania.

require_once 'Channel.php';

class Entry {
    public function __construct(Channel $c, $url, $title,
					   $date, $desc) {
        $this->channel     = $c;
        $this->url         = $url;
        $this->title       = $title;
        $this->date        = $date;
        $this->description = $desc;
    }
    
    public function getChannel() {
        return $this->channel;
    }
    
    public function getURL() {
        return $this->url;
    }
    
    public function getTitle() {
        return $this->title;
    }
    
    public function getDate() {
        return $this->date;
    }
    
    public function getDescription() {
        return $this->description;
    }
    
    private $channel;
    private $url;
    private $title;
    private $date;
    private $description;
}
require_once 'Entry.php';

class Channel {
    function __construct($url, $title, $desc) {
        $this->url         = $url;
        $this->title       = $title;
        $this->description = $desc;
    }
    
    public function getURL() {
        return $this->url;
    }
    
    public function getTitle() {
        return $this->title;
    }
    
    public function getDescription() {
        return $this->description;
    }
    
    public function addEntry(Entry $e) {
        $this->entries[] = $e;
    }
    
    public function getEntries() {
        return $this->entries;
    }
    
    private $url;
    private $title;
    private $description;
    private $entries = array();
}

Trzecia klasa jest już nieco ciekawsza, gdyż musimy udostępnić dwie metody publiczne do dodawania adresów kanałów oraz pobierania tablicy obiektów typu Channel. Pierwszą z nich otrzyma nazwę addURL i będzie zapisywać adresy kanałów w tablicy. Uczynimy ją statyczną. Oto ciało tej metody.

public static function addURL($url) {
    self::$urls[] = $url;
}

Cała obsługa połączeń znajdzie się w metodzie get, której konstrukcja może wydać się na pierwszy rzut oka nieco tajemnicza. Celem interfejsu multi nie jest uproszczenie korzystania z wielu połączeń, lecz przyspieszenie go. Zanim zajmiemy się implementacją pobierania danych, przyglądnijmy się najważniejszym funkcjom wspomnianego interfejsu.

Pobieranie danych

Od początku przygotowywaliśmy się na ten moment, gdy będziemy mogli napisać kod wykorzystujący interfejs multi. Nadszedł już czas, zacznijmy od ciała metody Channels::get(), a następnie omówimy ją linijka po linijce.

public static function get() {
    $mh       = curl_multi_init();
    $chs      = array();
    $channels = array();
    
    foreach (self::$urls as $url) {
        $ch = curl_init($url);
        $chs[] = $ch;
        
        curl_setopt_array($ch, self::$options);
        curl_multi_add_handle($mh, $ch);
    }
    
    do {
        $result = curl_multi_exec($mh, $active);
    } while ($result == CURLM_CALL_MULTI_PERFORM);
    
    do {
        if (curl_multi_select($mh) != -1) {
            do {
                $result = curl_multi_exec($mh, $active);
            } while ($result == CURLM_CALL_MULTI_PERFORM);
        }
    } while ($active && $result == CURLM_OK);
    
    foreach ($chs as $ch) {
        if (curl_errno($ch) == 0) {
            $content    = curl_multi_getcontent($ch);
            $channels[] = self::parseXML($content);
        }
    }
    return $channels;
}

Nie spodziewałeś się pewnie, że ujrzysz aż tyle kodu. Zacznijmy więc od początku. Najpierw inicjujemy uchwyt multi (zmienna $mh) oraz dwie tablice. W pierwszej będziemy przechowywać kolejne uchwyty cURL, a w drugiej obiekty Channel uzyskane po przetworzeniu danych xml. Pierwsza pętla to inicjacja połączeń oraz nadanie im odpowiednich opcji za pomocą funkcji curl_setopt_array. Przechowujemy je w prywatnej statycznej zmiennej $options. Każdy uchwyt zapisujemy w tablicy $chs oraz dodajemy do uchwytu multi.

do {
    curl_multi_exec($mh, $active);
} while ($active > 0);

Usunięcie funkcji curl_multi_select ciągnie ze sobą niemiłe konsekwencje. Tracimy możliwość blokowania wykonania skryptu, gdy wszystkie gniazdka oczekują na dane. Wywoływana jest w kółko funkcja curl_multi_exec, która natychmiast zwraca sterowanie do pętli.

Warunek drugiej pętli uwzględnia zmienną $active, która informuje nas o aktywnych gniazdkach. Dopóki będzie choćby jedno oczekiwało na dane, nie możemy kontynuować operacji, gdyż dane nie zostały do końca pobrane. Zwracamy wtedy sterowanie interfejsowi cURL, a ten próbuje pobrać pozostałą ich część.

Ostatnia pętla foreach służy przekazaniu do obróbki danych pobranych z kolejnych kanałów. Kod związany z interpretacją XML umieściliśmy w prywatnej, statycznej metodzie Channels::parseXML(). Nie chcę wchodzić zbyt głęboko w temat SimpleXML, dlatego nie będę objaśniał wklejonego poniżej kodu.

private static function parseXML($xml) {
    $root = new SimpleXMLElement($xml);
        
    $url         = $root->channel->link;
    $title       = $root->channel->title;
    $description = $root->channel->description;
    
    $channel     = new Channel($url, $title, $description);
    
    foreach ($root->channel->item as $item) {
        $entryUrl         = $item->link;
        $entryTitle       = $item->title;
        $entryDate        = strtotime($item->date);
        $entryDescription = $item->description;
        
        $entry = new Entry($channel, $entryUrl, $entryTitle,
                           $entryDate, $entryDescription);
        $channel->addEntry($entry);
    }

    return $channel;
}

Poniżej znajduje się kompletna klasa Channels.

require_once 'Entry.php';
require_once 'Channel.php';

class Channels {
    public static function addURL($url) {
        self::$urls[] = $url;
    }
    
    public static function get() {
        $mh       = curl_multi_init();
        $chs      = array();
        $channels = array();
        $active = 0;
        
        foreach (self::$urls as $url) {
            $ch = curl_init($url);
            $chs[] = $ch;
            
            curl_setopt_array($ch, self::$options);
            curl_multi_add_handle($mh, $ch);
        }
        
        do {
            $result = curl_multi_exec($mh, $active);
        } while ($result == CURLM_CALL_MULTI_PERFORM);
        
        do {
            if (curl_multi_select($mh) != -1) {
                do {
                    $result = curl_multi_exec($mh, $active);
                } while ($result == CURLM_CALL_MULTI_PERFORM);
            }
        } while ($active && $result == CURLM_OK);
        
        foreach ($chs as $ch) {
            if (curl_errno($ch) == 0) {
                $content    = curl_multi_getcontent($ch);
                $channels[] = self::parseXML($content);
            }
        }
        return $channels;
    }

    private static function parseXML($xml) {
        $root = new SimpleXMLElement($xml);
        
        $url         = $root->channel->link;
        $title       = $root->channel->title;
        $description = $root->channel->description;
        
        $channel = new Channel($url, $title, $description);
        
        foreach ($root->channel->item as $item) {
            $entryUrl         = $item->link;
            $entryTitle       = $item->title;
            $entryDate        = strtotime($item->date);
            $entryDescription = $item->description;
            
            $entry = new Entry($channel, $entryUrl,
                               $entryTitle, $entryDate,
                               $entryDescription);
            $channel->addEntry($entry);
        }
        
        return $channel;
    }
    
    private static $urls;
    private static $options = array(CURLOPT_HEADER => false,
                            CURLOPT_RETURNTRANSFER => true);
}

Obsługa klas

Mamy już gotowe wszystkie klasy, pora je w jakiś sposób wykorzystać. Stworzymy plik index.php, w którym zapiszemy adresy kanałów oraz pobierzemy z nich dane. Wpierw musimy dołączyć plik Channels.php, aby zdefiniować wszystkie klasy. Następnie dodajemy adresy kanałów. W artykule użyłem kanałów PHP.pl – Nowości oraz Artykuły. Gdy zakończymy ten etap, pobieramy dane z kanałów metodą Channels::get(), która zwróci nam listę obiektów Channel. W pierwszej pętli foreach będziemy iterować kolekcję kanałów, a wewnątrz niej napiszemy kolejną, wyświetlającą kolejno wpisy z danego RSS-a. Całość opatrzymy kodem XHTML oraz nieco upiększymy arkuszem CSS. Oto kod pozostałych nam plików.

<?php header('Content-Type: text/html; charset=UTF-8'); ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
	<link href="style.css" rel="stylesheet" type="text/css" />
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>Czytnik RSS</title>
</head>
<body>
<?php

require_once 'Channel.php';
require_once 'Entry.php';
require_once 'Channels.php';

Channels::addURL('http://wortal.php.pl/rss/feed/nowosci');
Channels::addURL('http://wortal.php.pl/rss/feed/artykuly');

$channels = Channels::get();

?>
<?php foreach ($channels as $channel): ?>
	<div class="channel">
	<h1><a href="<?php echo $channel->getURL(); ?>"><?php echo $channel->getTitle(); ?></a></h1>
	<?php 
	    $entries = $channel->getEntries(); 
	    foreach ($entries as $entry) : 
	?>
		<div class="entry">
    		<h2><a href="<?php echo $entry->getURL(); ?>"><?php echo $entry->getTitle(); ?></a></h2>
    		<h3><?php echo $entry->getDate(); ?></h3>
    		<p>
    			<?php echo $entry->getDescription(); ?>
    		</p>
    	</div>
	<?php endforeach; ?>
	</div>
<?php endforeach; ?>

</body>
</html>
.channel {
	margin:  5px;
	padding: 5px;
	border: 1px solid #000;
	
	font-family: Arial, sans-serif;
}

.entry {
	margin:  10px;
	padding: 10px;
	border: 1px solid #666;
}

h1 {
	font-size: 16px;
}

h2 {
	font-size: 14px;
}

p {
    font-size: 12px;
}

Pozostaje nam tylko skierować się pod odpowiedni adres, aby wywołać plik index.php. Oto efekt widoczny w mojej przeglądarce:

Informacje na podobny temat:
Wasze opinie
Wszystkie opinie użytkowników: (0)
Mentax.pl    NQ.pl- serwery z dodatkiem świętego spokoju...   
O nas | Kontakt | Mapa serwisu
Copyright (c) 2003-2022 php.pl    Wszystkie prawa zastrzeżone    Powered by eZ publish Content Management System eZ publish Content Management System