Redis: l'interfaccia da riga di comando
Redis è un database di tipo NoSQL, che salva i dati nella RAM anzichè su disco, come un insieme di coppie chiave-valore.
Ci siamo occupati ampiamente, in un precedente articolo, di come installare Redis in una distribuzione Centos 8, ed abbiamo iniziato ad utilizzare la riga di comando: vi invito a leggerlo in quanto sarà la premessa di quanto vedremo oggi.
In breve, tramite yum, avevamo installato il pacchetto Redis, disponibile nelle repo ufficiali di Centos
Effettuata l'installazione, nel file di configurazione "/etc/redis.conf", avevamo impostato una password di accesso a Redis.
requirepass pipp@1234p!uto
Bene, fatta questa premessa passiamo ad esempi pratici di utilizzo di Redis da riga di comando.
VERIFICA CONNESSIONE AL SERVER REDIS
Da terminale digita "redis-cli": così facendo accedi alla riga di comando. Apparirà l'ip della macchina su cui redis è installato (127.0.0.1) , la porta di connessione (6379) ed un prompt: redis resterà in attesa di un comando.
# redis-cli
127.0.0.1:6379>
Facciamo un semplice "ping" per verificare la connessione al server
# redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379>
Appare un errore "NOAUTH Authentication required", perchè, avendo impostato la password di accesso a Redis nel file di configurazione, adesso non l'abbiamo indicata.
Utilizziamo il comando "auth" seguito dalla password
# redis-cli
127.0.0.1:6379> auth pipp@1234p!uto
OK
127.0.0.1:6379>
Bene, da questo momento Redis è utilizzabile!
Al termini di ogni operazione viene riproposto il prompt, dove possiamo effettuare una nuova operazione
Vediamo adesso dei semplici esempi di creazione di una chiave / valore, e del recupero della stessa.
STRINGHE
Vediamo come gestire delle stringhe. Per stringhe in Redis non intendiamo solo stringhe di testo, ma anche stringhe numeriche, che vedremo nel prossimo paragrafo. Adesso occupiamoci di stringhe testuali.
Creiamo una chiave "name" con valore "Pippo".
Utilizziamo il comando SET per creare la chiave/valore, e GET per recuperare il valore della chiave.
127.0.0.1:6379> SET name Pippo
OK
127.0.0.1:6379> GET name
"Pippo"
Bingo!! Abbiamo creato la chiave "name" e poi abbiamo recuperato il valore salvato
VALORI NUMERICI
Nelle stringhe possiamo considerare anche i valor numerici.
Creiamo la key "contatore" e la impostiamo a valore 0.
Utilizzando i metodi INCR e DECR possiamo aumentare e diminuire il valore di una unità.
127.0.0.1:6379> SET contatore 0
OK
127.0.0.1:6379> INCR contatore
(integer) 1
127.0.0.1:6379> INCR contatore
(integer) 2
127.0.0.1:6379> INCR contatore
(integer) 3
127.0.0.1:6379> DECR contatore
(integer) 2
127.0.0.1:6379> GET contatore
"2"
Abbiamo utilizzato il comando GET alla fine, ma è superfluo in quanto dopo ogni operazione Redis ci rende il nuovo valore di contatore
Per incrementare o decrementare di valori superiori all'unità utilizziamo i metodi INCRBY e DECRBY
127.0.0.1:6379> SET contatore 0
OK
127.0.0.1:6379> INCRBY contatore 7
(integer) 7
127.0.0.1:6379> DECRBY contatore 2
(integer) 5
LISTA CHIAVI SALVATE: KEYS
Negli esempi precedenti abbiamo salvate le chiavi "name", "contatore". Per conoscere tutte le chiavi salvate utilizziamo il comando KEYS seguito dall'asterisco.
127.0.0.1:6379> KEYS *
1) "name"
2) "contatore"
Otteniamo la lista delle keys salvate in Redis, in ordine decrescente (dall'ultimo salvato, al primo)
CANCELLARE UNA CHIAVE: DELETE
Utilizziamo il comando DEL, ad esempio per cancellare la chiave "contatore":
127.0.0.1:6379> DEL contatore
(integer) 1
La riga di comando risponde "1" cioè che l'operazione ha avuto successo.
Verifichiamo adesso la lista di chiavi presente nel database
127.0.0.1:6379> KEYS *
1) "name"
CANCELLARE TUTTE LE CHIAVI: FLUSHALL
Per rimuovere tutte le chiavi utilizziamo il comando FLUSHALL
127.0.0.1:6379> FLUSHALL
(integer) 1
Verifichiamo che non esistano più chiavi
127.0.0.1:6379> KEYS *
(empty list or set)
VERIFICARE ESISTENZA DI UNA CHIAVE: EXISTS
Per verificare l'esistenza di una chiave usiamo il comando EXISTS. Se ad esempio voglio verificare se esite la chiave "contatore"
127.0.0.1:6379> EXISTS contatore
(integer) 0
La risposta 0 indica che non è stato trovata la chiave. Se fosse stata trovata avremmo avuto risposta 1.
LISTE
Dopo aver visto le stringhe, occupiamoci delle liste.
Creiamo una chiave, ad esempio "esempi-database" e dentro, invece che salvare una stringa, salviamo una lista di valori.
In questo caso non usiamo SET e GET , bensì LPUSH e LRANGE
Per inizializzare una lista, è sufficiente specificare la chiave "esempi-database", ed inserirvi elementi.
127.0.0.1:6379> LPUSH esempi-database MariaDB
(integer) 1
127.0.0.1:6379> LPUSH esempi-database Oracle
(integer) 2
127.0.0.1:6379> LPUSH esempi-database Redis
(integer) 3
127.0.0.1:6379> LPUSH esempi-database Mysql
(integer) 4
127.0.0.1:6379> LPUSH esempi-database Mongodb
(integer) 5
127.0.0.1:6379> LPUSH esempi-database DynamoDB
(integer) 6
Il comando LPUSH aggiunge un elemento all'inizio della lista. Questo significa che "DynamoDB" sarà il primo elemento della lista.
NOTA: è possibile inserire più elementi nella stessa operazione indicandoli uno dopo l'altro, cioè avremmo potuto scrivere
127.0.0.1:6379> LPUSH esempi-database MariaDB Oracle Redis Mysql Mongodb DynamoDB
(integer) 6
Per recuperare gli elementi della lista utilizziamo il comando LRANGE che si aspetta 3 parametri:
- "key": il nome della chiave da recuperare
- "start": è l'elemento da cui iniziare l'estrazione: 0 è primo valore cioè "DynamoDB". Avessi indicato 1, il primo valore estratto sarebbe stato "Mongodb"
- "stop": è l'elemento finale dell'estrazione: abbiamo indicato "2", per cui andiamo ad estrarre gli elementi "0 1 2" cioè "DynamoDB MongoDB MySQL". Avessi indicato "1" avremmo estratto solo i primi 2 elementi cioè "DynamoDB MongoDB". Indicando "-1" estraiamo tutti i records.
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "DynamoDB"
2) "Mongodb"
3) "Mysql"
4) "Redis"
5) "Oracle"
6) "MariaDB"
Se il primo elemento, "start", è superiore agli elementi presenti nella lista (ad es. 8) verrà restituita una lista vuota.
Se lo "stop" è maggiore del numero di elementi della lista, verrà semplicemente considerato come l'ultimo elemento della lista.
Altri comandi relativi alle liste sono i seguenti
- RPUSH: se vogliamo salvare un elemento della lista come ultimo elemento, anzichè come primo elemento come avviene con LPUSH
- LLEN: fornisce il numero di elementi della lista
- LPOP: elimina il primo elemento della lista
- RPOP: elimina l'ultimo elemento della lista
- LINDEX: se vogliamo accedere ad un elemento specifico della lista, in tal caso dobbiamo indicarne la posizione, facendo attenzione al fatto che la prima posizione è 0, per cui ad esempio la posizione 1 indica il secondo elemento.
E' possibile usare anche indici negativi, in tal caso "-1" è l'ultimo elemento, "-2" è il penultimo...
127.0.0.1:6379> RPUSH esempi-database Cassandra
(integer) 7
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "DynamoDB"
2) "Mongodb"
3) "Mysql"
4) "Redis"
5) "Oracle"
6) "MariaDB"
7) "Cassandra"
127.0.0.1:6379> LPOP esempi-database
(integer) 7
127.0.0.1:6379> LPOP esempi-database
"DynamoDB"
127.0.0.1:6379> RPOP esempi-database
"Cassandra"
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "Mongodb"
2) "Mysql"
3) "Redis"
4) "Oracle"
5) "MariaDB"
127.0.0.1:6379> LLEN esempi-database
(integer) 5
127.0.0.1:6379> LINDEX esempi-database 1
"Mysql"
Sono ammessi duplicati, per cui potete inserire più volte lo stesso valore, ad esempio
127.0.0.1:6379> LPUSH esempi-database Mysql
(integer) 6
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "Mongodb"
2) "Mysql"
3) "Redis"
4) "Oracle"
5) "MariaDB"
6) "Mysql"
Vediamo altri 2 comandi relativi all'inserimento dei dati:
- LSET: inserisce un elemento in una data posizione sostituendo il valore precedente presente in quella posizione. Si può fornire un indice positivo (ed in questo caso il conteggio partirà da 0 dalla testa) o uno negativo (ed in questo caso il conteggio partità da -1 dalla coda). Non è possibile fornire un indice inesistente nella lista
- LINSERT: possiamo richiedere di inserire un nuovo elemento prima o dopo di un altro già presente
Ad esempio sostituiamo l'elemento in posizione 2 (quindi come terzo elemento)
127.0.0.1:6379> LSET esempi-database 3 Mysql
OK
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "Mongodb"
2) "Mysql"
3) "Mysql"
4) "Oracle"
5) "MariaDB"
6) "Mysql"
Adesso inseriamo un nuovo elemento, "Redis", subito prima di "Oracle"
127.0.0.1:6379> LINSERT esempi-database BEFORE Oracle Redis
(integer) 7
Ed inseriamo un nuovo elemento, "MSSQL", subito dopo "Oracle"
127.0.0.1:6379> LINSERT esempi-database AFTER Oracle MSSQL
(integer) 8
Ecco la nuova lista
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "Mongodb"
2) "Mysql"
3) "Mysql"
4) "Redis"
5) "Oracle"
6) "MSSQL"
7) "MariaDB"
8) "Mysql"
Infine, concludiamo l'argomento liste con un ultimo comando relativo alla eliminazione di un elemento dalla lista LREM con il quale è possibile eliminare un determinato numero di istanze di un elemento nella lista.
Prendiamo l'elemento Mysql, presente 3 volte nell'elenco. Eliminiamo 2 istanze
> LREM esempi-database 2 Mysql
(integer) 2
Verifichiamo gli elementi della lista
127.0.0.1:6379> LRANGE esempi-database 0 -1
1) "Mongodb"
2) "Redis"
3) "Oracle"
4) "MSSQL"
5) "MariaDB"
6) "Mysql"
HASHES
Simili alle stringhe, gli hashes contentono di salvare non una semplice coppia chiave/valore, bensì una chiave (la chiave dell'hash), una o più sottochiavi (entries) ed il loro valori (i valori della entries). Potete pensarlo simile ad un array associativo.
Ad esempio, utilizziamo questa struttura per redigere una classifica di calcio. La chiave sarà "classifica-seriea" le sottochiavi le squadre di calcio e i relativi valori i punti in classifica.
Utilizziamo il comando HSET per creare una sottochiave/valore della chiave "classifica-seriea"
127.0.0.1:6379> HSET calendario-seriea juve 18
(integer) 1
127.0.0.1:6379> HSET calendario-seriea napoli 15
(integer) 1
127.0.0.1:6379> HSET calendario-seriea inter 13
(integer) 1
127.0.0.1:6379> HSET calendario-seriea milan 12
(integer) 1
127.0.0.1:6379> HSET calendario-seriea lazio 12
(integer) 1
Nota: se aggiungiamo nuovamente una sottochiave già esistente, il suo valore verrà sovrascritto. per cui se aggiungiamo questa riga, alla sottochiave "inter" verrà assegnato il nuovo valore 16
127.0.0.1:6379> HSET calendario-seriea inter 16
(integer) 0
In alternativa ad HSET è possibile usare HSETNX che esegue l'inserimento SOLO SE la sottochiave non esiste ancora, prevenendo quindi l'aggiornamento del record.
E' possibile anche effettuare inserimenti multipli con HMSET: i nostri inserimenti avremmo potuti indicarli così
127.0.0.1:6379> HMSET calendario-seriea juve 18 napoli 15 inter 16 milan 12 lazio 12
OK
Per leggere un valore inserito utilizziamo il comando HGET. Ad esempio
127.0.0.1:6379> HGET calendario-seriea inter
"16"
Utilizzando HGETALL ottengo la lista di tutte le sottochiavi e relativi valori
127.0.0.1:6379> HGETALL calendario-seriea
1) "juve"
2) "18"
3) "napoli"
4) "15"
5) "inter"
6) "16"
7) "milan"
8) "12"
9) "lazio"
10) "12"
Mentre per estrarre i valori solo di alcune sottochiavi, occorre usare HMGET
127.0.0.1:6379> HMGET calendario-seriea juve inter
1) "18"
2) "16"
Se volessimo incrementare il valore della sottochiave, utilizziamo il comando HINCRBY: ad esempio vogliamo aumentare il valore di "inter" di due unità indicheremo
127.0.0.1:6379> HINCRBY calendario-seriea inter 2
(integer) 18
Così facendo "inter" avrà come nuovo valore "18".
Questo sarà possibile solo per valori numerici, sia interi che di tipo float. Per i tipi float utilizziamo HINCRBYFLOAT.
Per cancellare una sottochiave utilizziamo il comando HDEL. Ad esempio per eliminare la sottochiave "inter" utilizziamo questa riga.
127.0.0.1:6379> HDEL calendario-seriea inter
(integer) 1
Per estrarre le sole sottochiavi utilizziamo HKEYS
127.0.0.1:6379> HKEYS calendario-seriea
1) "juve"
2) "napoli"
3) "milan"
4) "lazio"
Per estrarre i soli valori utilizziamo HVALS
127.0.0.1:6379> HVALS calendario-seriea
1) "18"
2) "15"
3) "12"
4) "12"
Per verificare l'esistenza di una sottochiave utilizziamo il comando HEXISTS, come in questo esempio
127.0.0.1:6379> HEXISTS calendario-seriea juve
(integer) 1
Renderà 1 se esiste, 0 se non esiste.
Per conoscere il numero di sottochiavi di un hash utilizziamo il comando HLEN, come in questo esempio
127.0.0.1:6379> HLEN calendario-seriea
(integer) 4
SET
Rappresenta un insieme (una collezione) non ordinata di dati stringa unici, non ordinati, chiamati membri.
Spiegamo meglio questo concetto con un esempio.
Creiamo un insieme di elementi, composto da studenti iscritti ad un mio corso di PHP
La chiave, cioè il nome dell'insieme, sarà ad esempio "allievi_php": questo insieme conterrà stringhe composte dai nome di ogni alllievo iscritto al corso, ad esempio 5 allievi.
L'insieme non ammette duplicati per cui non possiamo inserire due volte lo stesso allievo.
Per creare questo insieme utilizziamo il comando SADD come segue
127.0.0.1:6379> SADD allievi-php andrea
(integer) 1
127.0.0.1:6379> SADD allievi-php elisa
(integer) 1
127.0.0.1:6379> SADD allievi-php luca
(integer) 1
127.0.0.1:6379> SADD allievi-php stefano
(integer) 1
127.0.0.1:6379> SADD allievi-php maria
(integer) 1
Abbiamo così popolato l’insieme con cinque elementi (membri).
E' possibile popolare l'insieme con più records con una sola operazione. Ad esempio avremmo potuto scrivere
127.0.0.1:6379> SADD allievi-php andrea elisa luca stefano maria
(integer) 5
Per ottenere l'elenco dei membri utlizziamo SMEMBERS come segue
127.0.0.1:6379> SMEMBERS allievi-php
1) "luca"
2) "elisa"
3) "andrea"
4) "stefano"
5) "maria"
Per eliminare un elemento dalla collezione utilizziamo SREM come segue
127.0.0.1:6379> SREM allievi-php stefano
(integer) 1
Per eliminare più elementi
127.0.0.1:6379> SREM allievi-php maria luca
(integer) 2
Per verificare se un elemento esiste utilizziamo SISMEMBER come segue: restituirà true (1) se esiste o false (0) se non esiste
127.0.0.1:6379> SISMEMBER allievi-php luca
(integer) 0
Per contare il numero di elementi utilizziamo SCARD come segue
127.0.0.1:6379> SCARD allievi-php
(integer) 4
Se abbiamo più insiemi possiamo svolgere tra loro varie operazioni tra le quali l'unione, l'intersezione e la differenza.
A titolo di esempio, creiamo un altro insieme che raccolga allievi per un corso di Linux, alcuni dei quali (stefano ed elisa) frequentano anche il corso di PHP. Chiamiamo questo insieme "allievi_linux"
127.0.0.1:6379> SADD allievi-linux stefano giuseppe elisa mario
(integer) 4
Con SDIFF verifichiamo la differenza tra due insiemi, cioè tutti gli allievi che sono presenti solo in un corso.
127.0.0.1:6379> SDIFF allievi-php allievi-linux
1) "andrea"
2) "maria"
Con SINTER effettuiamo l’operazione di intersezione tra i due insiemi, cioè otteniamo l’elenco di tutti quelli che sono iscritti ad entrambi i costi
127.0.0.1:6379> SINTER allievi-php allievi-linux
1) "elisa"
2) "stefano"
Per ottenere tutti gli allievi, quindi l'unione dei due insiemi utilizziamo SUNION
127.0.0.1:6379> SUNION allievi-php allievi-linux
1) "andrea"
2) "elisa"
3) "mario"
4) "stefano"
5) "maria"
6) "giuseppe"
SORTED SET
Simile al caso precedente, ma in questo caso ad ogni elemento dell'insieme è associato a un valore numerico mobile, chiamato score (punteggio), che utilizziamo per ordinare gli elementi. Quindi è un insieme ordinato di elementi.
Per aggiungere un elemento nell'insieme utilizziamo ZADD seguito dalla chiave dell'insieme il punteggio associato e la stringa da memorizzare.
Ad esempio creiamo una chiave "allievi-html" ed al suo interno salviamo gli allievi del corso con il voto ottenuto all'esame.
127.0.0.1:6379> ZADD allievi-html 5 andrea
(integer) 1
127.0.0.1:6379> ZADD allievi-html 8 elisa
(integer) 1
127.0.0.1:6379> ZADD allievi-html 6 luca
(integer) 1
127.0.0.1:6379> ZADD allievi-html 4 stefano
(integer) 1
127.0.0.1:6379> ZADD allievi-html 7 maria
(integer) 1
E' possibile anche l'inserimento multiplo. Nel nostro esempio avrei potuto scrivere
127.0.0.1:6379> ZADD allievi-html 5 andrea 8 elisa 6 luca 4 stefano 7 maria
(integer) 5
Se un elemento è già presente verrà semplicemente aggiornato con il nuovo punteggio.
Per ottenere il numero di elementi presenti nella chiave utilizziamo ZCARD
127.0.0.1:6379> ZCARD allievi-html
(integer) 5
Se vogliamo conoscere il numero di elementi con un punteggio interno ad un certo range utilizziamo ZCONT. Ad esempio, per conoscere il numero di allievi con punteggio tra il 4 e il 5
127.0.0.1:6379> ZCOUNT allievi-html 4 5
(integer) 2
Per incrementare un punteggio utlizziamo il comando ZINCRBY. Ad esempio, per incrementare di 6 punti il punteggio di stefano, e portarlo quindi a 10
127.0.0.1:6379> ZINCRBY allievi-html 6 stefano
(integer) 10
Per eliminare un elemento utlizziamo ZREM
127.0.0.1:6379> ZREM allievi-html luca
(integer) 1
Per recuperare il punteggio di un elemento utilizziamo ZSCORE
127.0.0.1:6379> ZSCORE allievi-html andrea
(integer) 5
Infine vediamo come recuperare dei sottoinsiemi della nostra chiave.
Per estrarre un sottoinsieme di elementi ordinati, dal punteggio minore al maggiore, utilizziamo il comando ZRANGE: dobbiamo indicare la posizione inziale e quella finale da estrarre.
Nota: Redis utilizza la notazione Unix standard, il che significa che il primo elemento in un elenco è il numero 0, il secondo elemento è il numero 1 e così via.
Quindi, per estrarre i 3 allievi con il punteggio più basso indichiamo
127.0.0.1:6379> ZRANGE allievi-html 0 2
1) "andrea"
2) "maria"
3) "elisa"
Per estrarre tutti i records indichiamo le posizioni da 0 a -1
127.0.0.1:6379> ZRANGE allievi-html 0 -1
1) "andrea"
2) "maria"
3) "elisa"
4) "stefano"
Aggiungendo "WITHSCORES" dopo la posizione finale otteniamo anche i punteggi degli allievi
Per ottenere i 3 allievi con punteggio minore
127.0.0.1:6379> ZRANGE allievi-html 0 -1 WITHSCORES
1) "andrea"
2) "5"
3) "maria"
4) "7"
5) "elisa"
6) "8"
7) "stefano"
8) "10"
Per estrarre gli allievi con un punteggio interno ad un certo range utilizziamo ZRANGEBYSCORE, indicando l'intervallo dei punteggi da cercare. Ad esempio se vogliamo conoscere gli allievi con punteggio tra 4 e 6
127.0.0.1:6379> ZRANGEBYSCORE allievi-html 4 6
1) "andrea"
Per recuperare la posizione di un elemento utilizziamo il comando ZRANK. Ad esempio, per ottenere la posizione dell'allieva "elisa" indichiamo
127.0.0.1:6379> ZRANK allievi-html elisa
(integer) 2
INota: le posizioni vengono contate a partire da 0 e i punteggi vengono ordinati in senso crescente cioè dal più basso al più alto.
Per invertire l'ordine, dal punteggio maggiore al minore, utilizziamo ZREVRANGE, ZREVRANGEBYSCORE e ZREVRANK
Ad esempio, per indicare i 3 allievi con punteggio maggiore indichiamo
127.0.0.1:6379> ZREVRANGE allievi-html 0 2
1) "stefano"
2) "elisa"
3) "maria"
E per ottenere anche i punteggi
ZREVRANGE allievi-html 0 2 WITHSCORES
1) "stefano"
2) "10"
3) "elisa"
4) "8"
5) "maria"
6) "7"
SCADENZA CHIAVI
Redis, come sappiamo, salva i dati in memoria, ed è probabile che non vogliamo lasciare i dati memorizzati in eterno.
Il comando EXPIRE imposta un tempo di scadenza di una chiave in secondi per una chiave esistente. Ad esempio creo la chiave "cognome" con valore "rossi". Ed imposto la scadenza tra 3 minuti, cioè 180 secondi.
127.0.0.1:6379> SET cognome rossi
OK
127.0.0.1:6379> EXPIRE cognome 20
(integer) 1
Attendiamo 20 secondi e poi verifichiamo se esiste ancora la chiave
127.0.0.1:6379> GET cognome
(nil)
Il comando EXPIREAT imposta un tempo di scadenza di una chiave ultilizzano lo unix timestamp. Ad esempio, vogliamo che la chiave scada il giorno 29/09/2020 alle ore 18.15.00. Il timestamp di questa data è 1601403300
127.0.0.1:6379> SET cognome rossi
OK
127.0.0.1:6379> EXPIREAT cognome 1601403300
(integer) 1
Con il comando TTL otteniamo il tempo di vita, in secondi, di una chiave
127.0.0.1:6379> TTL cognome
(integer) 50672
Il comando PERSIST fa durare una chiave per sempre rimuovendo l'eventuale scadenza impostata
127.0.0.1:6379> PERSIST cognome
(integer) 1
Se adesso proviamo a verificare il tempo di vita della chiave la risposta sarà -1, cioè infinita
127.0.0.1:6379> TTL cognome
(integer) -1
Redis Cli online
Se non avete installato Redis Cli, potete utilizzare Redis online presente in questa pagina dove potrete testare i nostri esempi.
127.0.0.1:6379> KEYS *
1) "name"
2) "contatore"
Redis e PHP
Abbiamo dedicato un articolo all'utilizzo di Redis in PHP. Vedremo come replicare gli stessi esempi in una pagina PHP.