Creative Commons License
This work is licensed under a
Creative Commons
Attribution-Share Alike 3.0
Unported License.

Master Symfony2 fundamentals

Be trained by SensioLabs experts (2 to 6 day sessions -- French or English).
trainings.sensiolabs.com

Symfony hosting done right

ServerGrove, outstanding support at the right price for your Symfony hosting needs.
servergrove.com

Discover the SensioLabs Support

Access to the SensioLabs Competency Center for an exclusive and tailor-made support on Symfony
sensiolabs.com

Il componente DomCrawler

Il componente DomCrawler

Il componente DomCrawler semplifica la navigazione nel DOM dei documenti HTML e XML.

Note

Dove possibile, il componente DomCrawler non è progettato per manipolare il DOM o per ri-esportare HTML/XML.

Installazione

È possibile installare il componente in diversi modi:

Utilizzo

La classe Crawler mette a disposizione metodi per effettuare query e manipolare i documenti HTML e XML.

Un'istanza di Crawler rappresenta un insieme (SplObjectStorage) di oggetti DOMElement, che sono, in pratica, nodi facilmente visitabili:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
use Symfony\Component\DomCrawler\Crawler;

$html = <<<'HTML'
<!DOCTYPE html>
<html>
    <body>
        <p class="messaggio">Ciao Mondo!</p>
        <p>Ciao Crawler!</p>
    </body>
</html>
HTML;

$crawler = new Crawler($html);

foreach ($crawler as $elementoDom) {
    print $elementoDom->nodeName;
}

Le classi specializzate Link e Form sono utili per interagire con collegamenti html e i form durante la visita dell'albero HTML.

Filtrare i nodi

È possibile usare facilmente le espressioni di XPath:

1
$crawler = $crawler->filterXPath('descendant-or-self::body/p');

Tip

internamente viene usato DOMXPath::query per eseguire le query XPath.

La ricerca è anche più semplice se si è installato il componente CssSelector. In questo modo è possibile usare lo stile jQuery per l'attraversamento:

1
$crawler = $crawler->filter('body > p');

È possibile usare funzioni anonime per eseguire filtri complessi:

1
2
3
4
$crawler = $crawler->filter('body > p')->reduce(function ($node, $i) {
    // filtra anche i nodi
    return ($i % 2) == 0;
});

Per rimuovere i nodi, la funzione anonima dovrà restituire false.

Note

Tutti i metodi dei filtri restituiscono una nuova istanza di Crawler contenente gli elementi filtrati.

Attraversamento dei nodi

Accedere ai nodi tramite la loro posizione nella lista:

1
$crawler->filter('body > p')->eq(0);

Ottenere il primo o l'ultimo nodo della selezione:

1
2
$crawler->filter('body > p')->first();
$crawler->filter('body > p')->last();

Ottenere i nodi allo stesso livello della selezione attuale:

1
$crawler->filter('body > p')->siblings();

Ottenere i nodi, allo stesso livello, precedenti o successivi alla selezione attuale:

1
2
$crawler->filter('body > p')->nextAll();
$crawler->filter('body > p')->previousAll();

Ottenere tutti i nodi figlio o padre:

1
2
$crawler->filter('body')->children();
$crawler->filter('body > p')->parents();

Note

Tutti i metodi di attraversamento restituiscono un nuova istanza di Crawler.

Accedere ai nodi tramite il loro valore

Accedere al valore del primo nodo della selezione attuale:

1
$message = $crawler->filterXPath('//body/p')->text();

Accedere al valore dell'attributo del primo nodo della selezione attuale:

1
$class = $crawler->filterXPath('//body/p')->attr('class');

Estrarre l'attributo e/o il valore di un nodo da una lista di nodi:

1
2
3
4
$attributes = $crawler
    ->filterXpath('//body/p')
    ->extract(array('_text', 'class'))
;

Note

L'attributo speciale _text rappresenta il valore di un nodo.

Chiamare una funzione anonima su ogni nodo della lista:

1
2
3
$nodeValues = $crawler->filter('p')->each(function ($nodo, $i) {
    return $nodo->nodeValue;
});

La funzione anonima riceve la posizione e il nodo come argomenti. Il risultato è un array contenente i valori restituiti dalle chiamate alla funzione anonima.

Aggiungere contenuti

Il crawler supporta diversi modi per aggiungere contenuti:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$crawler = new Crawler('<html><body /></html>');

$crawler->addHtmlContent('<html><body /></html>');
$crawler->addXmlContent('<root><node /></root>');

$crawler->addContent('<html><body /></html>');
$crawler->addContent('<root><node /></root>', 'text/xml');

$crawler->add('<html><body /></html>');
$crawler->add('<root><node /></root>');

Essendo l'implementazione del Crawler basata sull'estensione di DOM, è anche possibile interagire con le classi native DOMDocument, DOMNodeList e DOMNode:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$documento = new \DOMDocument();
$documento->loadXml('<root><node /><node /></root>');
$listaNodi = $documento->getElementsByTagName('node');
$nodo = $documento->getElementsByTagName('node')->item(0);

$crawler->addDocument($documento);
$crawler->addNodeList($listaNodi);
$crawler->addNodes(array($nodo));
$crawler->addNode($nodo);
$crawler->add($documento);

Questi metodi di Crawler servono per popolare inizialmente il proprio Crawler e non per essere usati per manipolare ulteriormente un DOM (sebbene sia possibile). Tuttavia, poiché il Crawler è un insieme di oggetti DOMElement, si può usare qualsiasi metodo o proprietà disponibile in DOMElement, DOMNode o DOMDocument. Per esempio, si può ottenre l'HTML di un Crawler con qualcosa del genere:

1
2
3
4
5
$html = '';

foreach ($crawler as $domElement) {
    $html.= $domElement->ownerDocument->saveHTML();
}

Supporto per i collegamenti e per i form

Per i collegamenti e i form, contenuti nell'albero DOM, è riservato un trattamento speciale.

Collegamenti

Per trovare un collegamento tramite il suo nome (o un'immagine cliccabile tramite il suo attributo alt) si usa il metodo selectLink in un crawler esistente. La chiamata restituisce un'istanza di Crawler contenente il/i solo/i collegamento/i selezionato/i. La chiamata link() restituisce l'oggetto speciale Link:

1
2
3
4
5
$linksCrawler = $crawler->selectLink('Vai altrove...');
$link = $linksCrawler->link();

// oppure, in una sola riga
$link = $crawler->selectLink('Vai altrove...')->link();

L'oggetto Link ha diversi utili metodi per avere ulteriori informazioni relative al collegamento selezionato:

1
2
// restituisce la URI che può essere utilizzata per effettuare nuove richieste
$uri = $link->getUri();

Note

Il metodo getUri() è specialmente utile perché pulisce il valore di href e lo trasforma nel modo in cui dovrebbe realmente essere processato. Ad esempio, un collegamento del tipo href="#foo" restituirà l'URI completo della pagina corrente con il suffisso #foo. Il valore restituito da getUri() è sempre un URI completo, sul quale è possibile lavorare.

I Form

Un trattamento speciale è riservato anche ai form. È disponibile, in Crawler, un metodo selectButton() che restituisce un altro Crawler relativo al pulsante (input[type=submit], input[type=image], o button) con il testo dato. Questo metodo è specialmente utile perché può essere usato per restituire un oggetto Form, che rappresenta il form all'interno del quale il pulsante è definito:

1
2
3
4
5
6
$form = $crawler->selectButton('Valida')->form();

// o "riempie" i campi del form con dati
$form = $crawler->selectButton('Valida')->form(array(
    'nome' => 'Ryan',
));

L'oggetto Form ha molti utilissimi metodi che permettono di lavorare con i form:

$uri = $form->getUri();

$metodo = $form->getMethod();

Il metodo getUri() fa più che restituire il mero attributo action del form. Se il metodo del form è GET, allora, imitando il comportamento del browser, restituirà l'attributo dell'azione seguito da una stringa di tutti i valori del form.

È possibile impostare e leggere virtualmente i valori nel form:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// imposta, internamente, i valori del form
$form->setValues(array(
    'registrazione[nomeutente]' => 'fandisymfony',
    'registrazione[termini]'    => 1,
));

// restituisce un array di valori in un array "semplice", come in precedenza
$values = $form->getValues();

// restituisce i valori come li vedrebbe PHP
// con "registrazione" come array
$values = $form->getPhpValues();

Per lavorare con i campi multi-dimensionali:

1
2
3
4
5
<form>
    <input name="multi[]" />
    <input name="multi[]" />
    <input name="multi[dimensionale]" />
</form>

È necessario specificare il nome pienamente qualificato del campo:

1
2
3
4
5
6
7
8
// Imposta un singolo campo
$form->setValue('multi[0]', 'valore');

// Imposta molteplici campi in una sola volta
$form->setValue('multi', array(
    1              => 'valore',
    'dimensionale' => 'un altro valore'
));

Se questo è fantastico, il resto è anche meglio! L'oggetto Form permette di interagire con il form come se si usasse il browser, selezionando i valori dei radio, spuntando i checkbox e caricando file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$form['registrazione[nomeutente]']->setValue('fandisymfony');

// cambia segno di spunta a un checkbox
$form['registrazione[termini]']->tick();
$form['registrazione[termini]']->untick();

// seleziona un'opzione
$form['registrazione[data_nascita][anno]']->select(1984);

// seleziona diverse opzioni da una lista di opzioni o da una serie di checkbox
$form['registrazione[interessi]']->select(array('symfony', 'biscotti'));

// può anche imitare l'upload di un file
$form['registrazione[foto]']->upload('/percorso/al/file/lucas.jpg');

A cosa serve tutto questo? Se si stanno eseguendo i test interni, è possibile recuperare informazioni da tutti i form esattamente come se fossero stati inviati utilizzando i valori PHP:

1
2
$valori = $form->getPhpValues();
$files = $form->getPhpFiles();

Se si utilizza un client HTTP esterno, è possibile usare il form per recuperare tutte le informazioni necessarie per create una richiesta POST dal form:

1
2
3
4
5
6
$uri = $form->getUri();
$metodo = $form->getMethod();
$valori = $form->getValues();
$files = $form->getFiles();

// a questo punto si usa un qualche client HTTP e si inviano le informazioni

Un ottimo esempio di sistema integrato che utilizza tutte queste funzioni è Goutte. Goutte usa a pieno gli oggetti del Crawler di Symfony e, con essi, può inviare i form direttamente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
use Goutte\Client;

// crea una richiesta a un sito esterno
$client = new Client();
$crawler = $client->request('GET', 'https://github.com/login');

// seleziona il form e riempie alcuni valori
$form = $crawler->selectButton('Log in')->form();
$form['login'] = 'fandisymfony';
$form['password'] = 'unapassword';

// invia il form
$crawler = $client->submit($form);