Eliminar ‘index.php’ de Laravel 4.1 en Apache 2 con mod_rewrite

Una vez que hayamos instalado Laravel, el acceso a los recursos precisa que las URLs queden como http://localhost/index.php/resource/. Sería más bonito poder quitar ese ‘index.php’, ¿verdad? Laravel incluye un archivo .htaccess en el directorio ‘public’ que sirve para reescribir las URLs internamente y que éstas sean como http://localhost/resource/.

Este archivo ‘.htacces’ contiene lo siguiente:

<IfModule mod_rewrite.c>
<IfModule mod_negotiation.c>
Options -MultiViews
</IfModule>

RewriteEngine On

# Redirect Trailing Slashes…
RewriteRule ^(.*)/$ /$1 [L,R=301]

# Handle Front Controller…
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
</IfModule>

Como podemos ver, este archivo habilita el RewriteEngine, pero para ello necesita el módulo mod_rewrite habilitado en Apache. Usaremos el siguiente comando para habilitarlo:

$ sudo a2enmod rewrite
Enabling module rewrite.
To activate the new configuration, you need to run:
service apache2 restart

Es posible que con esto no sea suficiente, ya que la configuración del VirtualHost default contiene una directiva AllowOverride None que impide que .htaccess funcione. Así que tendremos que editar el archivo de configuración del VirtualHost (/etc/apache2/sites-available/default) y cambiar ‘AllowOverride None’ por ‘AllowOverride All’.

Una vez tengamos habilitado el módulo mod_rewrite y configurada la directiva AllowOverride All, reiniciamos Apache:

$ sudo service apache2 restart
* Restarting web server apache2                                                                                                                                                 apache2: Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 for ServerName
… waiting apache2: Could not reliably determine the server’s fully qualified domain name, using 127.0.1.1 for ServerName
[ OK ]

Ya podremos navegar por nuestra aplicación web sin tener que usar ‘index.php’.

Instalación de Laravel 4.1 en Debian 7 Wheezy

Hace poco me he iniciado en este framework de desarrollo web basado en PHP. Se llama Laravel y actualmente va por la versión 4.1.18. En este primer artículo voy a explicar cómo instalarlo en Debian 7 Wheezy, aunque debe funcionar igualmente en cualquier versión de Linux. Como requisito hay que tener instalado PHP versión 5.3.7 o superior y la librería MCrypt de PHP.

En primer lugar necesitamos descargarnos composer. Como en mi sistema no tengo instalado curl pero sí tengo php, voy a usar este último para descargar composer, y posteriormente lo moveré al directorio /usr/local/bin para ejecutarlo de forma más fácil.

root@wheezy:/var/www# php -r “readfile(‘https://getcomposer.org/installer');” | php
#!/usr/bin/env php
All settings correct for using Composer
Downloading…

Composer successfully installed to: /var/www/composer.phar
Use it: php composer.phar
root@wheezy:/var/www# mv composer.phar /usr/local/bin/composer

Si os fijáis he usado el usuario root, así que que tened en cuenta el uso de sudo si no sois root, y el archivo descargado se llama composer.phar y lo he renombrado a composer.

Una vez descargado y movido a /usr/local/bin lo ejecutamos para comprobar que funciona:

root@wheezy:/var/www# composer
______
/ ____/___  ____ ___  ____  ____  ________  _____
/ /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
/_/
Composer version 7343198817f365b1676ced0f353808f0e408ff9a 2014-02-07 09:59:35

Usage:
[options] command [arguments]

Options:
–help           -h Display this help message.
–quiet          -q Do not output any message.
–verbose        -v|vv|vvv Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug
–version        -V Display this application version.
–ansi              Force ANSI output.
–no-ansi           Disable ANSI output.
–no-interaction -n Do not ask any interactive question.
–profile           Display timing and memory usage information
–working-dir    -d If specified, use the given directory as working directory.

Available commands:
about            Short information about Composer
archive          Create an archive of this composer package
config           Set config options
create-project   Create new project from a package into given directory.
depends          Shows which packages depend on the given package
diagnose         Diagnoses the system to identify common errors.
dump-autoload    Dumps the autoloader
dumpautoload     Dumps the autoloader
global           Allows running commands in the global composer dir ($COMPOSER_HOME).
help             Displays help for a command
init             Creates a basic composer.json file in current directory.
install          Installs the project dependencies from the composer.lock file if present, or falls back on the composer.json.
licenses         Show information about licenses of dependencies
list             Lists commands
require          Adds required packages to your composer.json and installs them
run-script       Run the scripts defined in composer.json.
search           Search for packages
self-update      Updates composer.phar to the latest version.
selfupdate       Updates composer.phar to the latest version.
show             Show information about packages
status           Show a list of locally modified packages
update           Updates your dependencies to the latest version according to composer.json, and updates the composer.lock file.
validate         Validates a composer.json

Parece que sí y la versión descargada es:

Composer version 7343198817f365b1676ced0f353808f0e408ff9a 2014-02-07 09:59:35

El siguiente paso será descargar e instalar laravel. Yo voy a crearme un nuevo proyecto que voy a llamar nagvel, así que en el directorio /var/www ejecuto el siguiente comando:

root@wheezy:/var/www# composer create-project –prefer-dist laravel/laravel nagvel

Si queréis que vuestro proyecto tenga otro nombre, sólo tenéis que cambiar “nagvel” en el comando anterior.

Empezará a descargarse (tarda un poco) y descargará también todas las dependencias que tenga:

root@wheezy:/var/www# composer create-project –prefer-dist laravel/laravel nagvel
Installing laravel/laravel (v4.1.18)
– Installing laravel/laravel (v4.1.18)
Downloading: 100%

Created project in nagvel
Loading composer repositories with package information
Installing dependencies (including require-dev)
– Installing symfony/translation (v2.4.1)
Downloading: 100%

– Installing psr/log (1.0.0)
Downloading: 100%

– Installing symfony/routing (v2.4.1)
Downloading: 100%

– Installing symfony/process (v2.4.1)
Downloading: 100%

– Installing symfony/finder (v2.4.1)
Downloading: 100%

– Installing symfony/console (v2.4.1)
Downloading: 100%

– Installing symfony/filesystem (v2.4.1)
Downloading: 100%

– Installing symfony/debug (v2.4.1)
Downloading: 100%

– Installing symfony/http-foundation (v2.4.1)
Downloading: 100%

– Installing symfony/event-dispatcher (v2.4.1)
Downloading: 100%

– Installing symfony/http-kernel (v2.4.1)
Downloading: 100%

– Installing symfony/dom-crawler (v2.4.1)
Downloading: 100%

– Installing symfony/css-selector (v2.4.1)
Downloading: 100%

– Installing symfony/browser-kit (v2.4.1)
Downloading: 100%

– Installing swiftmailer/swiftmailer (v5.0.3)
Downloading: 100%

– Installing stack/builder (v1.0.1)
Downloading: 100%

– Installing predis/predis (v0.8.5)
Downloading: 100%

– Installing phpseclib/phpseclib (0.3.5)
Downloading: 100%

– Installing patchwork/utf8 (v1.1.18)
Downloading: 100%

– Installing nesbot/carbon (1.8.0)
Downloading: 100%

– Installing monolog/monolog (1.7.0)
Downloading: 100%

– Installing nikic/php-parser (v0.9.4)
Downloading: 100%

– Installing jeremeamia/superclosure (1.0.1)
Downloading: 100%

– Installing filp/whoops (1.0.10)
Downloading: 100%

– Installing ircmaxell/password-compat (1.0.3)
Downloading: 100%

– Installing d11wtq/boris (v1.0.8)
Downloading: 100%

– Installing classpreloader/classpreloader (1.0.1)
Downloading: 100%

– Installing laravel/framework (v4.1.21)
Downloading: 100%

symfony/translation suggests installing symfony/config ()
symfony/translation suggests installing symfony/yaml ()
symfony/routing suggests installing symfony/config (For using the all-in-one router or any loader)
symfony/routing suggests installing symfony/yaml (For using the YAML loader)
symfony/routing suggests installing symfony/expression-language (For using expression matching)
symfony/routing suggests installing doctrine/annotations (For using the annotation loader)
symfony/event-dispatcher suggests installing symfony/dependency-injection ()
symfony/http-kernel suggests installing symfony/class-loader ()
symfony/http-kernel suggests installing symfony/config ()
symfony/http-kernel suggests installing symfony/dependency-injection ()
predis/predis suggests installing ext-phpiredis (Allows faster serialization and deserialization of the Redis protocol)
predis/predis suggests installing ext-curl (Allows access to Webdis when paired with phpiredis)
phpseclib/phpseclib suggests installing ext-gmp (Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.)
phpseclib/phpseclib suggests installing pear-pear/PHP_Compat (Install PHP_Compat to get phpseclib working on PHP >= 4.3.3.)
patchwork/utf8 suggests installing ext-intl (Use Intl for best performance)
monolog/monolog suggests installing mlehner/gelf-php (Allow sending log messages to a GrayLog2 server)
monolog/monolog suggests installing raven/raven (Allow sending log messages to a Sentry server)
monolog/monolog suggests installing doctrine/couchdb (Allow sending log messages to a CouchDB server)
monolog/monolog suggests installing ruflin/elastica (Allow sending log messages to an Elastic Search server)
monolog/monolog suggests installing ext-amqp (Allow sending log messages to an AMQP server (1.0+ required))
monolog/monolog suggests installing ext-mongo (Allow sending log messages to a MongoDB server)
monolog/monolog suggests installing aws/aws-sdk-php (Allow sending log messages to AWS services like DynamoDB)
d11wtq/boris suggests installing ext-readline (*)
laravel/framework suggests installing doctrine/dbal (Allow renaming columns and dropping SQLite columns.)
Writing lock file
Generating autoload files
Generating optimized class loader
Application key [USCuULF66wR8NykEDq6EWr7ZbVmAS5YW] set successfully.

Ya con esto tenemos instalado Laravel 4.1.18 en nuestro equipo.

Para probar nuestra web entraremos en http://localhost/nagvel/public/.

Muy probablemente nos mostrará una página en blanco. Esto se debe a que el usuario del servidor web (www-data) no tiene permisos de escritura sobre el directorio /var/www/nagvel/app/storage. Así que cambio el propietario de dicho directorio:

root@wheezy:/var/www# chown -R www-data nagvel/app/storage/

Y ahora sí deberíamos ver la siguiente página de bienvenida:

Imagen

El siguiente paso es opcional, pero recomendable, ya que lo voy a usar en mis siguientes artículos sobre Laravel. Se trata de instalar Laravel-4-Generators, un paquete de Laravel 4, que proporciona una variedad de generadores para acelerar el proceso de desarrollo de nuestra aplicación. Estos generadores incluyen:

  • generate:model
  • generate:controller
  • generate:seed
  • generate:view
  • generate:migration
  • generate:resource
  • generate:scaffold
  • generate:form
  • generate:test
  • generate:pivot

Instalar plugin de PHP de Aptana 1.5 en Aptana 2.0

En casa he instalado Aptana 3, pero en el trabajo tengo Aptana 2. Éste no tiene soporte de PHP, así que he buscado cómo instalarlo. He llegado a la siguiente página donde explican perfectamente cómo instalar el plugin de PHP de Aptana 1.5 en Aptana 2.0:

http://www.bram.us/2010/01/26/installing-the-original-aptana-15-php-plugin-in-aptana-20/

Resumo los pasos:

  1. En Aptana seleccionamos Help -> Install New Software…
  2. Pinchamos en el botón Add y añadimos una nueva entrada con nombre “Aptana PHP Update Site” y con localización “http://update.aptana.com/install/php“.
  3. Seleccionamos esta nueva entrada que hemos creado en el menú desplegable y luego el paquete “Aptana PHP 1.1 Development Environment“.
  4. Seguimos los pasos pulsando los botones Next y Finish.

También se puede hacer una instalación manual, pero eso mejor lo véis en el artículo original:

http://www.bram.us/2010/01/26/installing-the-original-aptana-15-php-plugin-in-aptana-20/

short_open_tag, .htaccess y AllowOverride

El otro día me dieron el CD de una aplicación realizada en PHP y que había que instalarla en uno de nuestros servidores. Copiar y pegar. La página de inicio se veía bien, pero cuando entré en una página con contenido PHP, como un formulario, se veía código PHP en el navegador. ¡WoW! Me quedé algo sorprendido.

Lo primero que hice fue buscar el archivo .php correspondiente a esa página y ver su contenido. Aparentemente todo estaba bien. Modifiqué allá y acá, y seguía viéndose el código. Entonces vi que la etiqueta para el código php era “<? … ?>” en vez de “<?php … ?>“. Misterio resuelto y solución fácil. Sólo habría que habilitar la directiva short_open_tag (etiqueta de apertura corta) en el archivo /etc/php5/apache2/php.ini. Esta modificación afecta a todas las aplicaciones, ya que se configura a nivel del servidor Apache2-PHP, lo que supone un problema con otras aplicaciones que usan etiquetas xml, las cuales también comienzan por “<?” y se confunden con las etiquetas de php. El ejemplo más claro es éste:

<?xml version=”1.0″ encoding=”UTF-8″?

Para solucionar este problemilla hay varias opciones. La primera de ellas y que me parece más chupucera es reescribir la línea anterior en todos los archivos donde aparezca y dejarla así:

<? echo ‘<‘.’?xml version=”1.0″ encoding=”UTF-8″?’.’>’; ?>

Esto hace que PHP la reescriba igual que la línea original: <?xml version=”1.0″ encoding=”UTF-8″?>

La segunda opción es la más correcta, pero también la más tediosa y sería usar la etiqueta de apertura correcta “<?php” para todo nuestro código PHP. Esto supone revisar la aplicación entera y cambiar todas las etiquetas de apertura de php. No era mi intención reescribir el código, ni siquiera usando comandos tan útiles como sed o similares.

Y la tercera opción, que es un caso intermedio y por lo que he optado yo, sería habilitar short_open_tag sólo para la aplicación o directorio que deseemos. Esto es posible gracias al archivo .htaccess. Para ello, creamos dicho archivo en el directorio de nuestra aplicación (también se puede hacer a nivel de subdirectorio) y añadimos la siguiente línea:

php_flag short_open_tag on

Con esto lo que hacemos es habilitar la directiva short_open_tag de PHP sólo en el directorio donde tengamos el .htacces y en todos sus subdirectorios. Por tanto, de esta manera tendremos deshabilitadas las etiquetas cortas en el archivo php.ini pero habilitadas en archivo .htaccess del directorio deseado.

Como anécdota, voy a comentar otro problema que he tenido con este archivo .htaccess, ya que inicialmente no me funcionaba. La razón era que en la configuración de Apache2 tengo la opción AllowOverride None, por lo que se ignoran completamente los archivos .htaccess. Por tanto había que reconfigurar esta directiva de Apache2. Esto se puede hacer a nivel global o sólo para un directorio particular. Obviamente opté por lo segundo, para tener lo más seguro posible el servidor. Así pues, añadí la siguiente configuración a Apache2:

<Directory /var/www/miAplicacion/>
AllowOverride Options
</Directory>

Podéis leer más opciones para AllowOverride en la documentación de Apache: Apache Core Features.

Por último comentaros que una forma sencilla de probar si nuestro archivo .htaccess está funcionando es escribiendo cualquier texto en él, por ejemplo:

test

Si Apache no está leyendo el archivo .htaccess, la página se mostrará y Apache no dará ningún error. La causa más frecuente de esto es que tengamos la directiva “AllowOverride None“.

Sin embargo, si el archivo sí está siendo leído por Apache, éste nos dará un error “500 Internal Server Error” y en el archivo error.log de Apache nos aparecerá el siguiente mensaje de error:

[Fri Oct 23 18:22:15 2009] [alert] [client 192.168.0.10] /var/www/miAplicacion/.htaccess: Invalid command ‘test’, perhaps misspelled or defined by a module not included in the server configuration

PHP, MySQL y UTF-8

Recientemente he tenido el problema de que los datos almacenados en MySQL no se guardaban correctamente. Me refiero a los datos con tilde, las eñes y demás caracteres similares. Lo que se me guardaba eran carácteres tales como estos:

áéíóúñ

La página web desde la que introducía los datos estaba codificaba en UTF-8, forzado con la cabecera:

header('Content-Type: text/html; charset=UTF-8');

La base de datos, la tabla y el campo correspondiente estaban como utf8_spanish_ci. Los datos insertados mediante el formulario web luego se visuualizaban  correctamente. Pero en la base de datos se almacenaban con otra codificación.

Buscando por Internet, encontré esta página donde explicaban la resolución a mi problema. Básicamente, éste residía en la clase mysqli de PHP, que requería especificar el charset correspondiente.

El código que no funcionaba era éste:

if (!($link = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname))){
   exit('Error al conectar a la base de datos.');
}
$sql = 'SELECT * FROM `mibd`.`mitabla`';
$result = mysqli_query($link, $sql);

Y lo cambié por este otro, que funcionaba perfectamente:

if (!($link = mysqli_connect($dbhost, $dbuser, $dbpass, $dbname))){
   exit('Error al conectar a la base de datos.');
}
mysqli_set_charset($link, "utf8");
$sql = 'SELECT * FROM `mibd`.`mitabla`';
$result = mysqli_query($link, $sql);

Espero que le pueda ayudar a alguien.

Login con sesiones en CodeIgniter

Soy un aprendiz en el framework CodeIgniter y estoy haciendo pequeños progresos, pero no sé si buenos o malos. Así que voy a publicar algunos progresos para que los más expertos que lean este artículo me puedan corregir o mejorar el código.

Mi objetivo es dotar a una pequeña aplicación realizada con CI de autenticación para que no se pueda acceder anónimamente. Para ello he creado un controlador ‘principal.php‘, un modelo ‘autenticacion_model.php‘ y una vista ‘form_login.php‘. La estructura queda así:

.
|– config
|– controllers
|   `– index.html
|   |– principal.php
|– models
|   |– autenticacion_model.php
|   `– index.html
`– views
|– autenticacion
|   `– form_login.php
`– index.html

El controlador principal.php tiene las siguientes funciones:

El constructor Principal carga los herlpers ‘url’ y ‘form’ y la library ‘form_validation’. Con esto podremos hacer uso de las funciones para crear urls y formularios y validaremos nuestro formulario.

La función index carga la vista ‘principal_view’ en caso de que exista la sesión de usuario, o, en caso contrario, redirige a la función login.

La función login comprueba que se ha enviado el formulario. En caso positivo, se pasan las variables ‘usuario’ y ‘contrasena’ a la función ‘verificaUsuario()’ que está en el modelo ‘Autenticacion_Model’ y que verificará que el usuario y contraseña existen en la base de datos y son correctos. Si son correctos se crean las variables de sesión ‘usuario_id’ y ‘login_ok’, y si no, genera un mensaje de error. Si el formulario no se envió, se carga la vista del formulario ‘form_login’ para que el usuario se autentique.

La función logout sólo elimina las variables de sesión anteriores y redirige al controlador ‘principal’.

<?php

class Principal extends Controller {

  function Principal()
  {
    parent::Controller();
    $this->load->helper(array('url', 'form'));
    $this->load->library('form_validation');
  }

  // Función index. Comprueba si existe la sesión de usuario
  function index()
  {
    // si no existe la sesión con la variable 'usuario_id'
    if (!$this->session->userdata('usuario_id')){
      // redirigimos a la función login
      redirect('principal/login', 'refresh');
    } else {
      // en caso contrario cargamos la vista principal
      $this->load->view('principal_view');
  }

  // Función login. Verifica el usuario/contraseña
  function login()
  {
    // si se ha enviado el formulario
    if ($this->input->post('usuario')){
      // recogemos las variables 'usuario' y 'contrasena'
      $usuario = $this->input->post('usuario');
      $contrasena = sha1($this->input->post('contrasena'));
      // cargamos el modelo para verificar el usuario/contraseña
      $this->load->model('Autenticacion_Model');
      // si el usuario y contraseña son correctos
      if ($this->Admin_Model->verificaUsuario($usuario, $contrasena)){
        // creamos un array con las variables de sesión 'usuario_id' y 'login_ok'
        $datasession = array(
          'usuario_id'  => '$usuario',
          'login_ok' => TRUE
        );
        // creamos la sesión con dichas variables
        $this->session->set_userdata($datasession);
        // y redirigimos al controlador principal
        redirect('principal', 'refresh');
      } else {
        // si el usuario y contraseña son incorrectos
        $this->session->set_flashdata('error', 'El usuario o contraseña son incorrectos.');
      }
    } else {
      // cargamos el formulario de login
      $this->load->view('autenticacion/form_login');
    }
  }

  // Función logout. Elimina las variables de sesión y redirige al controlador principal
  function logout()
  {
    // creamos un array con las variables de sesión en blanco
    $datasession = array('usuario_id' => '', 'logged_in' => '');
    // y eliminamos la sesión
    $this->session->unset_userdata($datasession);
    // redirigimos al controlador principal
    redirect('principal', 'refresh');
  }

}

El modelo Autenticacion_Model contiene la función ‘verificaUsuario’ que recibe como parámetros el usuario y contraseña y se conecta a la base de datos para comprobar que son correctos.

<?php

class Autenticacion_Model extends Model {

  function Autenticacion_Model()
  {
    parent::Model();
  }

  function verificaUsuario($usuario, $contrasena){
    // creamos la select
    // SELECT `usuario`, `contrasena`
    // WHERE `usuario` = $usuario AND
    // `contrasena` = $contrasena
    // FROM `ci_usuarios`
    // LIMIT 1
    $this->db->select('id, usuario');
    $this->db->where('usuario', $usuario);
    $this->db->where('contrasena', $contrasena);
    $this->db->limit(1);
    $query = $this->db->get('ci_usuarios');
    // si el resultado de la query es positivo
    if ($query->num_rows() > 0){
      // devolvemos TRUE
      return TRUE;
    // si el resultado de la query no es positivo
    } else {
      // devolvemos FALSE
      return FALSE;
    }
  }

}

Por último, la vista form_login sólo es un formulario solicitando usuario y contraseña:

<html>
<head>
<title>Login</title>
</head>
<body>

<?php echo form_open('principal/login') ?>
<?php echo form_label('Usuario: ', 'usuario') ?>
<?php echo form_input(array('name' => 'usuario', 'id' => 'nombre', 'size' => '50')) ?>
<?php echo form_label('Contraseña: ', 'contrasena') ?>
<?php echo form_input(array('name' => 'contrasena', 'id' => 'contrasena', 'size' => '50')) ?>
<?php echo form_submit('enviar', 'Enviar') ?>
<?php echo form_close() ?>

</body>
</html>

NOTA: Como veréis, cargué la library form_validation pero no la he usado. La razón es que aún no sé cómo funciona esa librería y espero aprenderlo en breve. En cuanto lo sepa modifico el código. Aún así, no es necesario validar el formulario para que nuestra autenticación funcione.

Buscar subcadena en cadena en PHP

Quiero comprobar si una cadena comienza por el carácter (o cadena) ‘#’:

$cadena = "#Mi cadena de prueba";
$busca  = "#";
if (strpos($cadena,$busca) === 0) {
   echo "La cadena comienza por '#'";
} elseif (strpos($cadena,$busca) == strlen($cadena)-1) {
   echo "La cadena termina por '#'";
} elseif (strpos($cadena,$busca) > 0) {
   echo "La cadena contiene '#'";
} else {
   echo "La cadena no tiene '#'";
}

Comprobad que la primera comparación se hace con el operador ‘===’ (triple igual o triple equals). Esto es así porque strpos devuelve 0 si encuentra la subcadena al principio y FALSE si no lo encuentra, y, en PHP, ambos tienen el mismo significado.

El operador ‘==’ compara si la expresión de la izquierda y la derecha tienen el mismo valor, mientras que ‘===’ compara además que sean del mismo tipo. Así se distingue 0 (entero) de FALSE (lógico).