Mostrando entradas con la etiqueta php. Mostrar todas las entradas
Mostrando entradas con la etiqueta php. Mostrar todas las entradas

2011/11/15

Sesiones PHP a lo campestre III. No se puede hacer más lento.

Parafraseando al famoso mago (por si no lo conocéis es manco y hace trucos de cartas con una única mano), voy a darle la vuelta de tuerca definitiva a este método de obtener la información de sesión sin abrir la sesión.

Una vez que ya sabemos dónde están los ficheros de sesión tal y como se ha visto en las entregas anteriores, podemos simular de forma idéntica a como hace PHP la carga de la información de la sesión en una matriz asociativa.

El primer paso es cargar los contenidos del fichero de la sesión.
$fname    = session_save_path() . "/sess_" . $sid;
$_session = unserializeSession( file_get_contents( $fname ) );

Y para ello, se usa la siguiente función. Si es que es demasiado fácil...
///////////////////////////////////////////////////////////////////////////////
/**
 * unserializeSession - Unserialize para ficheros de sesión
 *
 * Convierte una cadena que se corresponde con los contenidos de un fichero de sesión en un array 
 * asociativo con las claves y valores de la sesión correspondiente.
 *
 * @access    public
 * @link      http://www.php.net/manual/en/function.session-decode.php#101687
 *
 * @param     string  $data  Cadena de texto del fichero de la sesión
 * @return    array          Array asociativa con las claves y valores
 */
function unserializeSession( &$data )
{
       if(  strlen( $data) == 0)
       {
              return array();
       }

       // match all the session keys and offsets
       preg_match_all('/(^|;|\})([a-zA-Z0-9_]+)\|/i', $data, $matchesarray, PREG_OFFSET_CAPTURE);

       $returnArray = array();

       $lastOffset = null;
       $currentKey = '';
       foreach ( $matchesarray[2] as $value )
       {
              $offset = $value[1];
              if(!is_null( $lastOffset))
              {
                     $valueText = substr($data, $lastOffset, $offset - $lastOffset );
                     $returnArray[$currentKey] = unserialize($valueText);
              }
              $currentKey = $value[0];

              $lastOffset = $offset + strlen( $currentKey )+1;
       }

       $valueText = substr($data, $lastOffset );
       $returnArray[$currentKey] = unserialize($valueText);

       return $returnArray;
}

2011/07/29

Lectura de los ficheros de un directorio con ordenación en PHP

Un apunte rápido para hoy.

Si necesitáis leer los nombres de los ficheros de un directorio en función de un filtrado arbitrario y recorrerlos en orden de fecha de creación, se puede hacer un bucle muy sencillo usando la maravillosa función glob que combinada con la función usort que permite especificar una función para ordenar, al final tendremos un array ordenado según el criterio definido.

Al turrón!
$ruta = "directorio/*.*"; // Cambiad la máscara de ficheros por la que deseéis
$arr = glob( $ruta ); 
usort( $arr, create_function( '$a,$b', 'return filemtime($a) - filemtime($b);' ) );

Rápido y sencillo, como debe ser.

2011/05/27

Sesiones PHP a lo campestre II. Seamos civilizados

En contra de lo que el título puede indicar, a priori vamos a ver el método "correcto" de almacenar la información de nuestra aplicación en el pseudo-array $_SESSION de PHP.

A la hora de almacenar los datos, siempre podemos usar el método que aparece en todos los sitios, incluida la documentación de http://php.net/ donde de forma simplista dice
"Para almacenar una variable en la sesión, haz $_SESSION['variable'] = $valor;"
Muy bonito y sencillo, pero las cosas no se pueden reducir tanto. Imagináos el escenario de que en un mismo servidor hay varias aplicaciones web con PHP funcionando. Inicialmente creamos una sesión en una aplicación que alegremente nos mete en nuestra sesión un valor 'id' con valor 3 mediante $_SESSION['id'] = 3;

Hasta aquí, bien, todo funciona. Ahora abrimos una segunda aplicación dentro del mismo servidor que usa esa misma variable de sesión, pero ahora con valor 'nulo' mediante $_SESSION['id'] = 'nulo';

Volvemos a la primera aplicación y cuando ésta pregunta por el valor de 'id' en la sesión NO RECIBE el valor 3 que es lo que espera, si no 'nulo'. Ea!, ahora vuélvete loco depurando o directamente vete a por una pistola para pegarte un tiro, porque no lo vas a encontrar...

Esto se arregla usando la misma técnica que se usa en javascript para optimizar el ámbito de las variables. Lo que hay que hacer es crear un ámbito de cada aplicación dentro de la sesión. Para ello, en vez de asignar un valor directamente a $_SESSION, vamos a crear una nueva varible dentro PERO DE TIPO ARRAY con nombre, siendo originales, el de nuestra aplicación. Así, todo quedaría así:
// Inicia la sesión
session_start();
// Crea el contexto
if(!isset($_SESSION['aplicacion1'])) $_SESSION['aplicacion1'] = array(); 

$_SESSION['aplicacion1']['id'] = 3;
// Ahora sí que cuando se lea la segunda vez valdrá 3
Con ésto, nos aseguramos que todo va a funcionar como debe sin tener dolores de cabeza.

Visto lo visto, si queremos realizar la misma barbaridad que vimos ayer accediendo a los valores de la sesión usando el contexto que hemos creado, habría que cambiarlo por
// Coge los valores de la sesión para nuestro contexto
$aplicacion = 'aplicacion1';
$res   = preg_match('/' . $aplicacion . '\|a\:\d+\:\{(.*)\};?/i', $txt, $data);
if($res) $txt    = $data[1];
else die("No hay sesión iniciada");

// Lee el valor de la variable id del contexto aplicacion1
$var   = 'id';
$res   = preg_match('/"' . $var . '"\;s\:\d*\:\"(\d+)\";/i', $txt, $data);
if($res) $idt    = $data[1];
Los cambios son pocos pero sutiles. Ahora, nuestra variable estará encerrada entre comas (antes no lo estaba) y el separador entre el nombre de la variable y el valor pasa de ser dos puntos a ser punto y coma.

Cuidadín con lo que haceís ya que en ese fichero de sesión se almacenan todas las sesiones iniciadas por ese cliente, tanto para la "aplicacion1" como para la "aplicacion2" siguiendo el ejemplo del principio.

2011/05/26

Sesiones en PHP a lo campestre

Hola a todos, hoy toca ser un poco "bruto". En uno de mis desarrollos, me he encontrado con un bug de PHP de modo que al trabajar con ficheros en tiempo real, cada vez que abría la sesion son session_start() para leer los datos del usuario actual, se me bloqueaban los ficheros por lo que se el tempo real se me iba al garete y el tiempo re sespuesta al timeout.

En vez de desesperar, me las he ingeniado para realizar las operaciones que jace el session_start() a lo basto, leyendo directamente las sesiones desde disco y extrayendo los datos que necesitaba.

Aquí tenéis un ejemplo. Sed buenos, que os estoy vigilando ;)

El primer paso es recibir el session_id() bueno de la sesión que tengamos iniciada (suponemos que en algún momento hemos hecho un session_start() con éxito. Si no, mal vamos).

El session_id se puede obtener llamando a la función session_id() después de un session_start() o mediante la lectura de la cookie PHPSESSID mediante $_COOKIE['PHPSESSID']. En el ejemplo se puede pasar el is mediante el parámetro "sid" de la página o mediante la cookie.
// Obtiene el session_id
$sid = $_REQUEST['sid'];
if(!$sid) $sid = $_COOKIE['PHPSESSID'];
if(!$sid) die "Necesito un identificador de sesión!!!";

Ahora calculamos el nombre del fichero donde se almacena la sesión
// Lee los IDs desde los ficheros de la sesión
$fname = session_save_path() . "/sess_" . $sid;
$txt   = file_get_contents($fname);

Los ficheros son ficheros de texto con cada variable expresada sefún el formato
variable|tipo:longitud:"valor";

variable es el nombre de la variable
tipo es un carácter que indica el tipo de datos. por ejemplo las cadenas son la letra 's'
longitud es el número de caracteres del valor
valor es el valor de la variable de sesión
Así, vamos a leer el valor de las variables usando expresiones regulares
$res = preg_match('/variable\|s\:\d*\:\"(\d+)\";/i', $txt, $data);
if($res) $valor = $data[1];

2011/05/07

Bucle de alfabeto en PHP

Hoy algo ligero para sobrellevar los efectos perniciosos de la feria.

Me ha hecho falta crear una paginación mediante letras y quería hacer algo elegante en PHP. La primera solución no me gustó demasiado, pero puede servir para ciertas situaciones.
<?php for ($i=65; $i<=90; $i++) echo "Mi código que usa " . chr($i); ?>
La solución más "bonita", sin embargo sería ésta
<?php foreach(range('A','Z') as $i) echo "Mi código que usa " . $i; ?>

2011/04/22

Longpolling

Como os dije ayer, la técnica del longpolling merece una mención aparte principalmente porque es una técnica universal, es decir, si tu navegador soporta AJAX, también soporta longpolling y lo más importante, funciona y lo hace muy bien.

Solo tiene una pega, que mantiene una conexión permanentemente abierta con el servidor con lo que si tenemos muchas visitas simultáneas, podemos llegar a tener problemas a medida que nos vayamos acercando al límite de conexiones del servidor... pero vamos, que en la práctica no vamos a hacer nuestro Google particular donde hay que optimizar cada conexión y cada bit.

Si realmente el número de conexiones es un problema, habrá que recurrir a otras tecnologías como por ejemplo websockets, que es la que tiene más posibilidades (ver el artículo de ayer).

Bueno, pues vamos al lío. En esta técnica hay dos partes, el código del cliente y el del servidor.

En el servidor, vamos a tener un PHP que se encarga de la lógica de negocio cuya pieza clave es la posibilidad de hacer un fork poniendo el proceso en estado de sleep mientras no hayan cambios.
usleep(20000);    // sleep 20ms to unload the CPU
clearstatcache();
Al estar el proceso dormido, no estará bloqueando la ejecución y liberará la CPU. Ahora, la segunda clave, es esperar x segundos mientras no haya que enviar nada al cliente siendo x suficientemente largo como para no tener que estar reabriendo la petición por parte del cliente y suficientemente corto como para que la petición al PHP no de timeout del servidor. En la práctica, el tope son 30 segundos aproximadamente (depende de la configuración del servidor), así que 25 segundos es un buen número. El ejemplo que vamos a ver es el de un chat así que ahí va el código de al parte servidora.

<?php
$fileName = 'chatdata.txt'; //    File to save latest chat in
$maxTime = 25; // Maximum loop time in seconds before we end the script
// Note: The front end will wait 5 seconds, and then re-connect.

if( ! is_file( $fileName ) ) file_put_contents( $fileName, "Hello from the chat server!" ); // Controla que exista el fichero

header('Content-Type: application/javascript');
if( isset( $_REQUEST['msg'] ) ) {
    //    Only 1024 chars per chat
    $msg = substr( $_REQUEST['msg'], 0, 1024 );
    file_put_contents( $fileName, $msg );
    exit();
}

$serverTime = isset( $_REQUEST['ts'] )? $_REQUEST['ts']: 0;
$fileTime = filemtime( $fileName );

$started = time();
$timedOut = false;

// Loop till the file is modified or loop times out
while( ( $fileTime <= $serverTime ) && ( ! $timedOut ) ) {
    usleep(20000);    // sleep 20ms to unload the CPU
    clearstatcache();
    $fileTime = filemtime($fileName);
    $timedOut = ( ( time() - $started ) >= $maxTime );
}

// JSON response, only if we didn't time out
if( ! $timedOut ) {
    print $_REQUEST['callback'] . "(" . json_encode( array(
        "data" => file_get_contents( $fileName ),
        "ts" => $fileTime
    ) ) . ")";
}
else
    header("HTTP/1.0 204 No Content");

flush();
?>

Cómo funciona. Todo se basa en que el PHP del servidor está vigilando un archivo "compartido" que crea y que es usado por todos los clientes del chat. Cuando un cliente escribe un texto y lo manda al PHP, éste lo cobreescribe inmediatamente en el fichero. Como el resto de los clientes tienen sus propios procesos PHP a la escucha, en el momento que se detecta un cambio en la fecha del fichero con filemtime, automáticamente vuelcan el contenido del fichero a sus respectivos clientes mediante JSONP que están a la espera mediante el longpolling. De este modo en el fichero siempre está la última línea de texto escrita en el chat.

Ahora la parte cliente. Para su funcionamiento, se ha usado jQuery, y un plugin llamado jquery-longpoll-0.0.1.js que se encarga de la comunicación con el servidor y que a su vez necesita tener el plugin de jQuery de JSONP para funcionar.

/*
 jQuery Long Poll plugin 0.0.1 (02-05-2010)
 
 Note: requires jQuery-JSONP: http://code.google.com/p/jquery-jsonp/

 Copyright (c) 2010 JavaScript Guy

 This script is licensed as free software under the terms of the
 LGPL License: http://www.opensource.org/licenses/lgpl-license.php
 
 TODO: Implement a way to hide the spinner in FF and IE, possibly use an iFrame?
 

*/
(function($){
 
 $.longPoll = function( args ) {
  args = args || {};
  var self = $.extend( {
   url: '',    // URL To connect to
   tsParameter: 'ts',  // Parameter used for the timestamp
   reconnect: true,  // If we're automatically reconnecting
   errorTimeout: 60,  // Timeout for errors, eg: server timeout, execution time limit, etc.
   errorTimeoutDelay: 5, // Seconds to delay, if the connection failed in an error
   hasConnection: false, // If we have a connection
   timestamp: 0,
   error: false,
   request: null,
   reconnectFunc: function() {
    if( self.reconnect ) {
     self.disconnect();
     // Reconnect - with longer error timeout delay (in case we have an intermittent connection)
     if( self.error ) {
      setTimeout( function() { self.connect(); }, self.errorTimeoutDelay * 1000 );
     } else {
      setTimeout( function() { self.connect(); }, 100 );
     }
    }
   },
   connect: function() {
    self.reconnect = true;
    // self.request = $.ajax( {
    self.request = $.jsonp( {
     url: self.url + '?' + self.tsParameter + '=' + self.timestamp + '&callback=?',
           timeout: self.errorTimeout * 1000,  // Timeout and reconnect
           error: function(XHR, textStatus, errorThrown) {
      self.reconnectFunc();
      self.error = true;
           },
     dataType: "jsonp",
     success: function( data ) {
      self.timestamp = (data[self.tsParameter])? data[self.tsParameter]: self.timestamp;
      self.success( data );
      self.error = false;
     },
     complete: function( data ) {
      self.reconnectFunc();
     }
     
    } );
    self.hasConnection = true;
   },
   
   // Aborts request
   disconnect: function() {
    self.reconnect = false;
    if( self.request )self.request.abort();
    self.hasConnection = false;
   },
   
   // User defined
   success: function( data ) {}
   
  }, args );
  return self;
 };

})(jQuery);

La aplicación de chat en sí, tiene dos partes y está hecha como se deben hacer las aplicaciones en javascript mediante contextos, aunque generalmente somos demasiado vagos como para hacerlo. La primera parte encapsula la aplicación de chat que se encargará de realizar las peticiones JSONP mediante el plugin de longpolling.

// Chat app
var chatApp = function( url ) {
 
 var self = {
  connection: $.longPoll( {
   url: url,
   success: function( data ) {
    // Ignore server timeouts, the script will handle it.
    if( data['data'].indexOf( "Maximum execution time of" ) == -1 ) {
     jQuery('#content').append( '
' + data['data'] + '
' ); } } } ), // Sends a chat message sendChat: function(request) { jQuery.ajax( { url: url, type: "GET", dataType: "jsonp", data: { 'msg' : request } } ); }, // Toggle connection button toggleConnect: function( button ) { if( self.connection.hasConnection ) { self.connection.disconnect(); jQuery( button ).val("Connect"); } else { self.connection.connect(); jQuery( button ).val("Disconnect"); } } }; return self; };

Básicamente, lo que hace es implementar una clase que controla la conexión al chat, el envío de mensajes y la puesta en espera de nuevos mensajes del resto de los clientes. La segunda parte es el uso de la clase chatApp definida en la primera parte.

// Chat app initialisation
// var chatAppContext = chatApp( 'http://paxjs.com/labs/longpolljsonp/chat.php' );
var chatAppContext = chatApp( 'chat.php' );

var sendMessage = function() {
 chatAppContext.sendChat( jQuery('#message').val() );
 jQuery('#message').val('');
};

jQuery( '#connect' ).click( function() {
 chatAppContext.toggleConnect( jQuery( this ).get(0) );
} );

jQuery( '#sendMsg' ).click( sendMessage );
jQuery( '#message').keyup(function(e) {
 if(e.keyCode == 13)sendMessage();
});

Todo junto en jsfiddle y funcionando (pinchad en la pestaña de "Result" y pulsad el botón "Connect")


Las posibilidades de comunicación son infinitas ya que es realmente sorprendente la velocidad de respuesta ya que los cambios son instantáneos en todos los clientes.

Como conclusión, decir que la aproximación óptima debería ser mixta de modo que los clientes que soporten técnicas más modernas que no mantengan una conexión permanentemente abierta como websockets de HTML5 los usen y los clientes más viejos usen longpolling. Con la fusión de estas dos técnicas se soportaría el 100% de los navegadores del mercado y nos permitiría el desarrollo de aplicaciones de tiempo real con comunicación bidireccional cliente-servidor.

2011/04/13

Modificando los headers con PHP

No siempre se necesita enviar los headers por defecto al generar una página con PHP. Uno de los secretos para hacer sencillas cosas complicadas es el correcto manejo de los headers. Quizás el modo más común de un header es escribir directamente en el HTML un redirect para que salte a otra página en X segundos, pero eso y más se puede controlar desde PHP como se puede ver en ésta página.

Podemos ver que un uso inteligente de los headers que se generan desde PHP puede servirnos además de para redirigir el tráfico, para control de acceso, cambiar el idioma, controlar la caché, controlar correctamente las descargas, el tipo mime, etc...

Siempre es bueno tener una chuleta como ésta a mano.


// See related links for more status codes
 
// Use this header instruction to fix 404 headers
// produced by url rewriting...
header('HTTP/1.1 200 OK');
 
// Page was not found:
header('HTTP/1.1 404 Not Found');
 
// Access forbidden:
header('HTTP/1.1 403 Forbidden');
 
// The page moved permanently should be used for
// all redrictions, because search engines know
// what's going on and can easily update their urls.
header('HTTP/1.1 301 Moved Permanently');
 
// Server error
header('HTTP/1.1 500 Internal Server Error');
 
// Redirect to a new location:
header('Location: http://www.example.org/');
 
// Redriect with a delay:
header('Refresh: 10; url=http://www.example.org/');
print 'You will be redirected in 10 seconds';
 
// you can also use the HTML syntax:
// <meta http-equiv="refresh" content="10;http://www.example.org/ />
 
// override X-Powered-By value
header('X-Powered-By: PHP/4.4.0');
header('X-Powered-By: Brain/0.6b');
 
// content language (en = English)
header('Content-language: en');
 
// last modified (good for caching)
$time = time() - 60; // or filemtime($fn), etc
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $time).' GMT');
 
// header for telling the browser that the content
// did not get changed
header('HTTP/1.1 304 Not Modified');
 
// set content length (good for caching):
header('Content-Length: 1234');
 
// Headers for an download:
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="example.zip"'); 
header('Content-Transfer-Encoding: binary');
// load the file to send:
readfile('example.zip');
 
// Disable caching of the current document:
header('Cache-Control: no-cache, no-store, max-age=0, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header('Pragma: no-cache');
 
// set content type:
header('Content-Type: text/html; charset=iso-8859-1');
header('Content-Type: text/html; charset=utf-8');
header('Content-Type: text/plain'); // plain text file
header('Content-Type: image/jpeg'); // JPG picture
header('Content-Type: application/zip'); // ZIP file
header('Content-Type: application/pdf'); // PDF file
header('Content-Type: audio/mpeg'); // Audio MPEG (MP3,...) file
header('Content-Type: application/x-shockwave-flash'); // Flash animation
 
// show sign in box
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Basic realm="Top Secret"');
print 'Text that will be displayed if the user hits cancel or ';
print 'enters wrong login data';

2011/04/01

Minimización de fuentes II. Minify.

Como os decía ayer, hay una alternativa a la compresión en tiempo de desarrollo de los fuentes javascript y CSS, y esa alternativa se llama Minify (http://code.google.com/p/minify/).

Lo que hace que funcione esta librería PHP es que incluye en motor de compresión de JSMin que vimos ayer portado a PHP para javascript y CSS (y también HTML) de modo que en tiempo real se minimizan los fuentes ya sean uno a uno o en grupos, lo que además de reducir el tamaño, permite reducir el número de peticiones HTTP.
Antes

Después

La instalación es sencilla... se descarga el paquete y se descomprime en un directorio accesible desde PHP del servidor. A partir de ahí, suponiendo por ejemplo  que se ha descomprimido en el document root, solo habrá sustituir las inclusiones javascript y CSS por
<script source="/min/b=directorio_base&amp;f=fichero1.js,fichero2.js,fichero3.js"></script>
<link rel="stylesheet" href="/min/b=directorio_base&amp;f=style1.css,style2.css" type="text/css">
El directorio base con el b=directorio es opcional y se puede incluir en el nombre de cada fichero.
Alguno dirá... "espera! y qué pasa con las directivas url() de CSS... si me cambian la localización del CSS, se me escogorciará todo!" y tiene razón... pero el propio minify es capaz de analizar el directorio del CSS y generar el código final con los url cambiados para que se mantenga el camino hasta el fichero indicado en el url.

La ventaja es que las minimizaciones se cachean de modo que cuando se vuelve a pedir el códido minimizado si los fuentes no han cambiado, se devuelve el caché incluso pre-comprimido por gzip haciendo que vaya muy rápido. En el momento que se cambia un fichero, se reminimiza todo y se devuelve el resultado.

Ahora llega el momento es que se está en medio del desarrollo y se quiere depurar el código porque algo está dando lata. Muy fácil... solo hay que añadir al final del URL la coletilla &debug=true (&amp;debug=true) para que no minimice el código y además añada como comentario el número de línea original antes de concatenar los ficheros unos con otros.

Este es el funcionamiento base, pero puede que os pase como a mí, que no me gusta dar "pistas" de la estructura interna de la página o tener mayor control sobre qué se incluyo en el minimizado o no. Pues ello se consigue definiendo grupos de minimización.

Los grupos se configuran en el fichero /min/groupsConfig.php dentro del paquete de minify y son un array asociativo con clave el nombre del grupo y con valor un array con la ruta al fichero a minimizar. Se puede usar en la ruta del fichero el pronombre '//' al principio para indicar el document_root del servidor web. Un ejemplo es:
return array(
    'js' => array('//js/file1.js', '//js/file2.js')
);
Para CSSs seria igual. Para incluir un grupo, solo hay que cambiar el /f (&amp;f) del url minimizado, por /g (&amp;g) indicando a continuación el nombre del grupo.

Como esto puede ser un tanto engorroso, minify proporciona una herramienta para generar el código a incluir en el groupsConfig.php simplemente abriendo la dirección /min de nuestro servidor sin parámetros, con lo que se muestra una interfaz para añadir ficheros y al final se genera el código PHP a pegar.

Minify, además tiene estructura modular y se le puede integrar el toolchain para el closure compiler de google, otros motores de minimización como por ejemplo el jsmin+ o el yui. Para ésto hay que mirar la documentación.

No hay que llegar a tantas virguerías ya que el caso más común que se puede dar es que haya código ya minimizado porque forme parte de otro paquete y que no se quiera minimizar ya que al reminimizar un código minimizado con otro motor de lugar a un código inválido y nos volvamos locos al no poder depurar. La solución es usar el módulo de trasformación de código que se usaría para cosas más complejas como he dicho antes pero forzando que la salida sea igual a la entarada. Un ejemplo con el jQuery:


//'//directorio/js/jquery-1.5.1.min.js' // Línea inicialnew Minify_Source(array('filepath' => '//directorio/js/jquery-1.5.1.min.js','minifier' => create_function('$a', 'return $a;'))) // Línea nueva
Realmente es más complicado explicarlo que usarlo. Quizás no de la mejor tasa de compresión de todas, pero funciona tan bien y es son tan fáciles de mantener la reglas que no da ni pereza. Yo lo que hago es generar un grupo para el CSS con todos los CSS aplicables y 2 ó 3 grupos de javascript con más o menos paquetes en función del uso más normal en las distintas páginas e incluyo el URL correspondiente en cada página.

2011/03/30

Control de buffer con PHP

Hola, para controlar la salida del buffer de PHP, hay una serie de funciones para manejarlo. Como una imagen vale más que 1000 palabras, un ejemplo:
<?php
  // Comienzo del almacenamiento en el buffer
  ob_start();
  // Toda la salida se escribe en el BUFFER, OJO!
  echo 'Print to the screen!!!';
  // Si se quiere consultar qué se ha sacado en el buffer hasta el momento
  $getContent = ob_get_contents();
  // Fin del almacenamiento en el buffer
  ob_end_clean();
  // Procesar el contenido almacenado en el buffer mediante $getContent. Se puede cambiar todo ya que realmente aún no se ha escrito en la salida.
  // Se vuelca el buffer en la salida mediante $getContent
  echo 'Ahora: ' . $getContent;
?>

2011/03/29

Detección de clientes móviles mediante javascript, PHP y .htaccess

Recopilo las forma de detectar navegadores de dispositivos móviles mediante distintas tecnologías:

Javascript
var ua = navigator.userAgent.toLowerCase();
var isAndroid = ua.indexOf("android") > -1; // && ua.indexOf("mobile");
var isiPhone = ua.indexOf("iphone") > -1;
var isiPod = ua.indexOf("ipod") > -1;
var isiPad = ua.indexOf("ipad") > -1;
// For use within iPad developer UIWebView
if(!isiPad) isiPad = /iPad/i.test(ua) || /iPhone OS 3_1_2/i.test(ua) || /iPhone OS 3_2_2/i.test(ua);
var isWPx = ua.indexOf("windows phone") > -1;
PHP
$ua = strtolower($_SERVER['HTTP_USER_AGENT']);
$isAndroid = stripos($ua, 'android') !== false; // && stripos($ua,'mobile') !== false
$isiPhone = stripos($ua, 'iphone') !== false;
$isiPod = stripos($ua, 'ipod') !== false;
$isiPad = stripos($ua, 'ipad') !== false;
$isWPx = stripos($ua, 'windows phone') !== false;
.htaccess
RewriteCond %{HTTP_USER_AGENT} ^.*Android.*$ RewriteRule ^(.*)$ http://android.site.com [R=301] RewriteCond %{HTTP_USER_AGENT} ^.*iPhone.*$ RewriteRule ^(.*)$ http://iphone.site.com [R=301] RewriteCond %{HTTP_USER_AGENT} ^.*iPod.*$ RewriteRule ^(.*)$ http://ipod.site.com [R=301] RewriteCond %{HTTP_USER_AGENT} ^.*iPad.*$ RewriteRule ^(.*)$ http://ipad.site.com [R=301]RewriteCond %{HTTP_USER_AGENT} ^.*Windows Phone.*$RewriteRule ^(.*)$ http://wpx.site.com [R=301]
HTML

<!--[if IEMobile]>
    Explorer mobile en Windows Phone 7
<![endif]-->
<![if !IEMobile]>
    Otros
<![endif]>

2011/03/21

Detección del navegador con PHP

A veces nos hace falta saber qué navegador y versión está visualizando una página antes de nada en el propio PHP... es decir... si es un navegador de verdad o una "cosa" como IE6.

Un código sencillo para hacerlo sería el siguiente, dejando la variable $IE = 1 si la versión de Internet Explorer es <= que la 6.

<?php
// Identifica el navegador usado
$IE = 0;
$agent = trim($_SERVER['HTTP_USER_AGENT']);
if ((preg_match('|MSIE ([0-9.]+)|', $agent, $version)) || (preg_match('|Internet Explorer/([0-9.]+)|', $agent, $version)))
$IE = 1; // INTERNET EXPLORER
// Versiones modernas de Internet Explorer funcionan como debe
if ($version && $version[1] > 6) $IE = 0;
?>

2011/03/09

La importancia de tirar de la cadena

Para evitar que la carga de la página web bloquee la carga de otros componentes, una cosa importante que hay que hacer siempre en PHP es empezar pronto, y eso se hace con la instrucción flush(), de modo que correctamente colocada, nos dará una ventaja muy grande en la carga de la página.

El sitio correcto es entre el <head> y el <body> ya que ese momento supuestamente ya tendríamos definido dónde están los css y los js.
<html>
    <head>
        ... scripts y css ...
    </head>
    <?php flush(); ?>
    <body>
        ...