Advertisement
  1. Code
  2. Web Development

Desarrollando una aplicación de citas con Sinch: RESTful API

Scroll to top
Read Time: 35 min
This post is part of a series called Creating a Dating Application with Sinch.
Creating a Dating Application with Sinch: Integrating Sinch
Sponsored Content

This sponsored post features a product relevant to our readers while meeting our editorial guidelines for being objective and educational.

() translation by (you can also view the original English article)

En este tutorial, vamos a crear una aplicación de citas para iOS similar a Tinder. Para voz y mensajería, aprovecharemos la plataforma Sinch, haciendo uso de su poderoso SDK.

En la primera parte, nos centraremos en el desarrollo de una API RESTful para almacenar y recuperar información del usuario. En la segunda parte, el cliente de iOS se conectará a esta API para encontrar usuarios cercanos según la ubicación actual del usuario.

Utilizaremos Laravel 5.0 para el servicio RESTful y cubriremos conceptos básicos, como rutas y controladores. También vamos a definir modelos personalizados para admitir la integración de MongoDB de una manera similar a ActiveRecord. Empecemos.

1. Configuración básica

Voy a asumir que ya has instalado Composer y el último instalador de Laravel. Si no lo has hecho, entonces sigue la documentación oficial de Laravel 5.0. El proceso de instalación no debería tomar más de un par de minutos.

Desde la línea de comandos, navega hasta la ubicación donde deseas crear la aplicación para el servicio RESTful y ejecuta el siguiente comando:

1
laravel new mobilesinch

Después de un par de segundos, el comando te dirá que la aplicación mobilesinch se ha creado correctamente. Navega a la nueva carpeta y ejecuta el siguiente comando:

1
php artisan fresh

Cualquier aplicación Laravel 5.0, por defecto, se envía con algunos 'scaffoldings' básicos para el registro y la autenticación de usuarios. Este comando se encarga de eliminar esto, ya que queremos comenzar con una pizarra limpia.

Hay otra cosa de la que tenemos que ocuparnos antes de escribir el código real para nuestro servicio RESTful. De forma predeterminada, Laravel 5.0 se entrega con un middleware para la protección de falsificación de solicitud entre sitios (CSRF). Sin embargo, dado que no estamos construyendo un sitio web sino una API REST, no tiene sentido tener esto en su lugar. Además, puede causar algunos problemas en el camino.

Para esta aplicación, lo mejor es eliminarlo. En la raíz de la carpeta de la aplicación, navega a app/Http. Dentro de esa carpeta, hay un archivo llamado Kernel.php. Ábrelo y quita la siguiente línea:

1
 'App\Http\Middleware\VerifyCsrfToken',

También puedes eliminar WelcomeController.php, que se encuentra dentro de app/Http/Controllers, así como la vista predeterminada welcome.blade.php dentro de la carpeta resources/views. No los usaremos, pero puedes dejarlos allí si quieres. Solo asegúrate de dejar la vista 503.blade.php en su lugar, ya que es útil para depurar la aplicación.

2. Modelo Base

La aplicación de citas que pretende crear este tutorial tiene una funcionalidad similar a Tinder en la que puedes encontrar usuarios cerca de tu ubicación actual. Para que esto funcione, la API debe realizar una búsqueda basada en la ubicación del usuario, conocida como una consulta geoespacial. Aunque podríamos hacer esto con MySQL, el estándar de la industria se inclina hacia MongoDB, y personalmente me gusta mucho más.

En lugar de usar la fachada de DB de Laravel, crearemos nuestra propia clase que los modelos de la aplicación ampliarán para realizar consultas en MongoDB.

Esta será una clase simple y no se integrará en el modelo Eloquent de Laravel, aunque podamos, me gustaría que sea simple por el momento.

Paso 1: Configuración de MongoDB

Antes de escribir la clase para realizar consultas MongoDB, debemos configurar la información de la base de datos, tal como lo haríamos para MySQL o PostgreSQL, o cualquier otro servidor de base de datos.

Dentro de la carpeta raíz config, crea un nuevo archivo y asígnale el nombre mongodb.php. Agrega el siguiente código:

1
<?php
2
3
return [
4
    'host'          => 'localhost',
5
    'port'          => 27017,
6
    'user'          => '',
7
    'pass'          => '',
8
    'db'            => 'mobilesinch'
9
];

Establecemos el host y el puerto para nuestro servidor MongoDB, si corresponde, configuramos el usuario y la contraseña para la conexión, y definimos la base de datos que usaremos, mobilesinch.

Como MongoDB es una base de datos orientada a documentos y no tiene esquemas, no necesitamos más configuración, ninguna definición de migración o cualquier otra cosa para estructurar las tablas. Simplemente funciona.

Paso 2: Conexión a la base de datos

Tenemos el archivo de configuración en su lugar y ahora es el momento de crear la clase real que manejará las interacciones con la base de datos.

Esta clase realizará consultas a MongoDB utilizando una sintaxis similar a ActiveRecord. Dentro de la carpeta app/Http, crea una nueva, Models, y agrega un archivo Base.php dentro de ella. Agrega el siguiente código:

1
<?php namespace App\Http\Models;
2
3
use Illuminate\Support\Facades\Config;
4
5
class Base {
6
   
7
    private $_config        = null;
8
   
9
    private $_conn            = null;
10
   
11
    private $_db            = null;
12
13
    public function __construct() {
14
        $this->_config        = Config::get( 'mongodb' );
15
       
16
        $this->_connect();
17
    }
18
   
19
    private function _connect() {}
20
}

Estos son los huesos básicos para nuestra clase de modelo Base. No se extiende desde nada y solo se basa en la fachada Config de Laravel para recuperar los parámetros de configuración que creamos anteriormente.

A continuación, necesitamos crear una conexión con la base de datos. Agrega el siguiente código al método privado _connect:

1
$conn = 'mongodb://'.$this->_config['host'];
2
if( ! empty( $this->_config['port'] ) ) {
3
    $conn .= ":{$this->_config['port']}";
4
}
5
6
$options = array();
7
if( ! empty( $this->_config['user'] ) && ! empty( $this->_config['pass'] ) ) {
8
    $options['username'] = $this->_config['user'];
9
    $options['password'] = $this->_config['pass'];
10
}
11
12
try {
13
    $this->_conn    = new \MongoClient( $conn, $options );
14
15
    $this->_db      = $this->_conn->{$this->_config['db']};
16
    return true;
17
} catch( \MongoConnectionException $e ) {
18
    $this->_conn    = null;
19
    return false;
20
}

En este método, creamos una cadena de conexión y establecemos el nombre de usuario y la contraseña, si se proporcionan. Luego utilizamos el controlador MongoDB de PHP para crear una conexión y establecer la base de datos en la que se especifica en el archivo de configuración.

Si estás familiarizado con la sintaxis de MongoDB desde la línea de comandos, este método es el equivalente a ingresar a la consola de Mongo y al teclear use mobilesinch. Consulta la documentación oficial de PHP MongoDB para obtener más información.

Paso 3: Métodos de ayuda

Antes de continuar con las operaciones CRUD de la base de datos, hay algunos métodos que nuestra clase Base debe implementar. Estos se utilizan para establecer filtros, seleccionar declaraciones y otras variables de consulta que se utilizan para realizar operaciones de base de datos. Comencemos agregando las variables miembro necesarias. Sobre el constructor de la clase, agregue el siguiente código:

1
private $_ws            = array();
2
3
private $_sls           = array();
4
5
private $_lmt           = 99999;
6
7
private $_ost           = 0;

Estos son los titulares de las consultas de base de datos, where, select, limit y offset . Para establecer estas variables miembro, crea los siguientes métodos de establecimiento:

1
protected function _limit( $limit, $offset = null ) {}
2
3
protected function _select( $select = "" ) {}
4
5
protected function _where( $key, $value = null ) {}

El método _limit será útil para paginar los resultados de una operación READ. El usuario puede configurar el parámetro limit para especificar el número de registros para recuperar y, opcionalmente, un parámetro offset para especificar la página para leer. Agrega el siguiente código al método _limit:

1
if ( $limit !== NULL && is_numeric( $limit ) && $limit >= 1 ) {
2
    $this->_lmt = $limit;
3
}
4
if ( $offset !== NULL && is_numeric( $offset ) && $offset >= 1 ) {
5
    $this->_ost = $offset;
6
}

El método _select se utilizará para determinar qué campos de un registro debe devolver una consulta READ. La declaración de selección debe proporcionarse como una cadena separada por comas.

1
$fields = explode( ',', $select );
2
foreach ( $fields as $field ) {
3
    $this->_sls[trim( $field )] = true;
4
}

Finalmente, el método _where se usará para filtrar los resultados de la consulta y puede ser una matriz o un par key/value.

1
if ( is_array( $key ) ) {
2
    foreach( $key as $k => $v ) {
3
        $this->_ws[$k] = $v;
4
    }
5
} else {
6
    $this->_ws[$key] = $value;
7
}

Ahora tenemos soporte para limitar y filtrar consultas, pero tenemos que agregar algunos otros métodos de tipo 'helper'. El primero se usará para combinar cualquier declaración where antes de emitir una consulta con el parámetro de la consulta where.

En un momento, cuando escribamos nuestros métodos CRUD, esto tendrá más sentido. En la parte inferior de la clase, agregue el siguiente método privado:

1
private function _set_where( $where = null ) {
2
    if ( is_array( $where ) ) {
3
        $where  = array_merge( $where, $this->_ws );
4
        foreach ( $where as $k => $v ) {
5
            if ( $k == "_id" && ( gettype( $v ) == "string" ) ) {
6
                $this->_ws[$k]  = new \MongoId( $v );
7
            } else {
8
                $this->_ws[$k]  = $v;
9
            }
10
        }
11
    } else if( is_string( $where ) ) {
12
        $wheres = explode( ',', $where );
13
        foreach ( $wheres as $wr ) {
14
            $pair = explode( '=', trim( $wr ) );
15
            if ( $pair[0] == "_id" ) {
16
                $this->_ws[trim( $pair[0] )] = new \MongoId( trim( $pair[1] ) );
17
            } else {
18
                $this->_ws[trim( $pair[0] )] = trim( $pair[1] );
19
            }
20
        }
21
    }
22
}

Parece un poco intimidante, pero en realidad es bastante simple. Primero verifica si el parámetro where es una matriz. Si es así, combina los valores dados con los existentes utilizando el método de ayuda _where.

Sin embargo, este método también admite una cadena para establecer lo que devuelve una operación READ. Esta cadena debe tener el siguiente formato:

1
name=John,last_name=Smith

Este ejemplo ejecutará una consulta y devolverá los campos donde el campo name se establece en John y el campo last_name se establece en Smith.

Sin embargo, ten en cuenta que, tanto para una matriz como para una cadena, verificamos si hay un campo _id presente. Si este es el caso y es una cadena, creamos un nuevo objeto MongoId a partir de él. Los identificadores son objetos en MongoDB y compararlos con una cadena devolverá false, por lo que esta conversión es necesaria.

Otra cosa que tenemos que hacer es restablecer todos los parámetros de consulta una vez que se haya realizado una operación para que no afecten las consultas posteriores. El método _flush se encargará de esto.

1
private function _flush() {
2
    $this->_ws      = array();
3
    $this->_sls     = array();
4
    $this->_lmt     = 99999;
5
    $this->_ost     = 0;
6
}

Paso 4: Operaciones CRUD

Ahora contamos con toda la funcionalidad requerida para filtrar y limitar los resultados de nuestras consultas. Es hora de las operaciones reales de la base de datos, que se basarán en el controlador MongoDB de PHP. Si no estás seguro de algo, consulta la documentación.

Operación CREATE

La primera operación que vamos a admitir es la de crear un registro en el sistema. Agrega el siguiente código a la clase Base:

1
protected function _insert( $collection, $data ) {
2
    if ( is_object( $data ) ) {
3
        $data   = ( array ) $data;
4
    }
5
6
    $result = false;
7
    try {
8
        if ( $this->_db->{$collection}->insert( $data ) ) {
9
            $data['_id']    = ( string ) $data['_id'];
10
            $result         = ( object ) $data;
11
        }
12
    } catch( \MongoCursorException $e ) {
13
        $result         = new \stdClass();
14
        $result->error  = $e->getMessage();
15
    }
16
    $this->_flush();
17
18
    return $result;
19
}

A pesar de que el controlador PHP espera que los datos insertados sean una matriz, nuestra clase admitirá tanto matrices como objetos. Primero verificamos lo que se nos pasa y lo lanzamos en consecuencia. Luego intentamos insertar el registro en la base de datos y devolver el registro insertado como un objeto, incluido el _id.

Operación READ

Vamos a implementar dos métodos de lectura, uno se usará para recuperar un solo registro, mientras que el otro se usará para obtener una lista de registros. Empecemos por lo primero.

1
protected function _findOne( $collection, $where = array() ) {
2
    $this->_set_where( $where );
3
4
    $row    = $this->_db->{$collection}->findOne( $this->_ws, $this->_sls );
5
    $this->_flush();
6
    return ( object ) $row;
7
}

Definimos la cláusula where para la consulta y usamos el controlador MongoDB de PHP para realizar una operación findOne. Luego vaciamos los parámetros de consulta y devolvemos el registro como un objeto.

El controlador MongoDB de PHP devuelve el resultado como una matriz, mientras que yo personalmente prefiero los objetos. Esa es la verdadera razón para el elenco.

A continuación, implementamos el método _find para obtener una lista de registros.

1
protected function _find( $collection, $where = array() ) {
2
    $this->_set_where( $where );
3
   
4
    $docs = $this->_db->{$collection}
5
        ->find( $this->_ws, $this->_sls )
6
        ->limit( $this->_lmt )
7
        ->skip( $this->_ost );
8
    $this->_flush();
9
10
    $result = array();
11
    foreach( $docs as $row ) {
12
        $result[] = ( object ) $row;
13
    }
14
    return $result;
15
}

En el método _find, usamos el método find del controlador, estableciendo el límite de la consulta mediante los parámetros limit y skip para admitir la paginación.

Sin embargo, este método devuelve un objeto MongoCursor, que luego repetimos para obtener los registros reales. Como antes, emitimos cada registro como un objeto, agregándolo a la matriz de resultados.

Operación UPDATE

Ya tenemos soporte para crear y leer registros de la base de datos. Ahora debemos poder editar esos registros y agregar o modificar los datos de un registro. Crea un nuevo método _update e implementarlo de la siguiente manera:

1
protected function _update( $collection, $data, $where = array() ) {
2
    if ( is_object( $data ) ) {
3
        $data   = ( array ) $data;
4
    }
5
    $this->_set_where( $where );
6
7
    if ( array_key_exists( '$set', $data ) ) {
8
        $newdoc     = $data;
9
    } else {
10
        $newdoc     = array( '$set' => $data );
11
    }
12
13
    $result         = false;
14
    try {
15
        if( $this->_db->{$collection}->update( $this->_ws, $newdoc ) ) {
16
            $result = ( object ) $data;
17
        }
18
    } catch( \MongoCursorException $e ) {
19
        $result         = new \stdClass();
20
        $result->error  = $e->getMessage();
21
    }
22
    $this->_flush();
23
24
    return $result;
25
}

Al igual que con la operación CREATE, admitimos matrices y objetos, lo que significa que verificamos y emitimos en consecuencia. Combinamos las cláusulas where pasadas al método utilizando el método auxiliar. El resto no es diferente del método _insert creado.

Sin embargo, hay una cosa especial a tener en cuenta. Cuando actualizamos un documento de MongoDB y pasamos los datos al método _update, el documento será reemplazado. Si solo estamos actualizando un campo y pasamos los datos para ese campo, el documento se convertirá en ese campo. Es por esto que necesitamos crear una matriz con la clave $set y la información adicional. El resultado es que nuestro registro no se reemplazará con la nueva información.

Operación DELETE

Finalmente, el controlador debe admitir la operación DELETE para eliminar documentos de la base de datos.

1
protected function _remove( $collection, $where = array() ) {
2
    $this->_set_where( $where );
3
4
    $result = false;
5
    try {
6
        if ( $this->_db->{$collection}->remove( $this->_ws ) ) {
7
            $result = true;
8
        }
9
    } catch( \MongoCursorException $e ) {
10
        $result         = new \stdClass();
11
        $result->error  = $e->getMessage();
12
    }
13
    $this->_flush();
14
15
    return $result;
16
}

Como antes, establecemos la cláusula where para la operación de eliminación y confiamos en el controlador MongoDB de PHP para realizar una operación remove en la base de datos.

Y eso es todo para nuestro modelo Base. Es mucho código, pero ahora podemos realizar operaciones en MongoDB para los modelos que heredan de la clase Base.

3. Modelo Session

El modelo Session se encargará de crear, eliminar y encontrar una sesión en la base de datos. Crea un nuevo archivo dentro de la carpeta Models de la aplicación, asígnale el nombre Session.php y agrega el siguiente código:

1
<?php namespace App\Http\Models;
2
3
class Session extends Base {
4
5
    private $_col   = "sessions";
6
7
    public function create( $user ) {
8
        $this->_where( 'user_id', ( string ) $user->_id );
9
        $existing   = $this->_findOne( $this->_col );
10
11
        if ( !empty( ( array ) $existing ) ) {
12
            $this->_where( 'user_id', ( string ) $user->_id );
13
            $this->_remove( $this->_col );
14
        }
15
16
        $session            = new \stdClass();
17
        $session->user_id   = ( string ) $user->_id;
18
        $session->user_name = $user->name;
19
        $session            = $this->_insert( $this->_col, $session );
20
21
        return $session;
22
    }
23
24
    public function find( $token ) {
25
        $this->_where( '_id', $token );
26
        return $this->_findOne( $this->_col );
27
    }
28
29
    public function remove( $token ) {
30
        $this->_where( '_id', $token );
31
        return $this->_remove( $this->_col );
32
    }
33
}

Este modelo se extiende desde la clase Base que creamos anteriormente para admitir las operaciones de MongoDB. También establece la colección que se utilizará para sessions.

El método create se utiliza para crear un registro de sesión de usuario. Antes de intentar crearlo, el método verifica si el usuario ya tiene una sesión activa. Si lo hace, lo elimina de la base de datos y crea el nuevo registro con la información de usuario pasada.

El método find se utiliza para recuperar un registro de sesión de la base de datos utilizando un token de sesión. Ten en cuenta que simplemente establece la cláusula where para la consulta y delega la tarea de encontrar el registro en el método _findOne de la clase Base.

Para finalizar una sesión de usuario, implementamos el método remove. Usando el token de sesión, delega el trabajo pesado al método _remove de la clase Base. Ten en cuenta que el modelo no verifica el token de sesión que se pasa. Esto debe ser manejado por el controlador. La única preocupación para el modelo es la manipulación de datos.

4. Modelo User

El otro modelo que necesita nuestra API REST es uno para manejar las interacciones relacionadas con el usuario. Dentro de la carpeta Models de la aplicación, crea un nuevo archivo User.php y agrega el siguiente código:

1
<?php namespace App\Http\Models;
2
3
use App\Http\Models\Base as Model;
4
5
class User extends Model {
6
7
    private $_col   = "users";
8
9
    private $_error = null;
10
11
    public function get( $where ) {}
12
13
    public function get_error() {}
14
15
    public function create( $user ) {}
16
17
    public function remove( $id ) {}
18
19
    public function retrieve( $id, $distance, $limit = 9999, $page = 1 ) {}
20
21
    public function update( $id, $data ) {}
22
}

El modelo User es un poco más complicado. Comencemos con los métodos para recuperar usuarios. El método get se encargará de recuperar un solo registro, utilizando la identificación del usuario. Agrega el siguiente código al método get:

1
if ( is_array( $where ) ) {
2
    return $this->_findOne( $this->_col, $where );
3
} else {
4
    $this->_where( '_id', $where );
5
    return $this->_findOne( $this->_col );
6
}

Estamos asumiendo que en el caso de que el parámetro where no sea una matriz, sea la identificación del usuario. El método get luego delega la tarea de encontrar el registro en el método _findOne de la clase Base.

El método get_error es un método auxiliar que le dará al controlador más información sobre fallas en el modelo.

1
return $this->_error;

La última operación de lectura en el modelo User es la del método retrieve. Esto traerá una lista de usuarios. Agrega el siguiente código al método retrieve.

1
if ( !empty( $id ) && !empty( $distance ) ) {
2
    $this->_where( '_id', $id );
3
    $this->_select( 'location' );
4
    $user   = $this->_findOne( $this->_col );
5
6
    if ( empty( ( array ) $user ) ) {
7
        $this->_error   = "ERROR_INVALID_USER";
8
        return false;
9
    }
10
11
    $this->_where( '$and', array(
12
            array(
13
                '_id'       => array( '$ne' => new \MongoId( $id ) )
14
            ),
15
            array(
16
                'location'  => array(
17
                    '$nearSphere'       => array(
18
                        '$geometry'     => array(
19
                            'type'          => "Point",
20
                            'coordinates'   => $user->location['coordinates']
21
                        ),
22
                        '$maxDistance'  => ( float ) $distance
23
                    )
24
                )
25
            )
26
        ) );
27
}
28
29
$this->_limit( $limit, ( $limit * --$page ) );
30
return $this->_find( $this->_col );

Este método soporta paginación y consultas geoespaciales. Si se pasan los parámetros id y distance, intenta buscar usuarios cercanos según la ubicación del usuario.

Si el id no coincide con ningún registro, devuelve falso. Si el usuario existe, prepara una consulta geoespacial utilizando un índice MongoDB 2dsphere.

Ten en cuenta que también estamos configurando la consulta para no devolver al usuario que coincide con el _id del usuario que realiza la búsqueda. Finalmente, establece el límite de consulta y los parámetros de desplazamiento, delegando la tarea al método _find de la clase Base.

Para eliminar usuarios, necesitamos implementar el método de eliminación. Agrega el siguiente código al método remove:

1
$this->_where( '_id', $id );
2
$user   = $this->_findOne( $this->_col );
3
4
if ( empty( ( array ) $user ) ) {
5
    $this->_error       = "ERROR_INVALID_ID";
6
    return false;
7
} else {
8
    $this->_where( '_id', $id );
9
    if ( !$this->_remove( $this->_col ) ) {
10
        $this->_error   = "ERROR_REMOVING_USER";
11
        return false;
12
    }
13
}
14
15
return $user;

Verificamos que el _id dado corresponde a un usuario existente e intentamos eliminarlo utilizando el método _remove de la clase Base. Si algo salió mal, establecemos la propiedad _error del modelo y devolvemos false.

Otra operación que nuestro modelo debería soportar es la creación de registros de usuarios. Agrega el siguiente código al método create:

1
if ( is_array( $user ) ) {
2
    $user   = ( object ) $user;
3
}
4
$this->_where( '$or', array(
5
        array(
6
            "email"     => $user->email
7
        ),
8
        array(
9
            "mobile"    => $user->mobile
10
        )
11
    )
12
);
13
$existing   = $this->_findOne( $this->_col );
14
15
if ( empty( ( array ) $existing ) ) {
16
    $user   = $this->_insert( $this->_col, $user );
17
} else {
18
    $user   = $existing;
19
}
20
21
$user->_id  = ( string ) $user->_id;
22
23
return $user;

En este método, nos aseguramos de que no haya un usuario asociado con el correo electrónico o móvil dado. Si eso es cierto, devolvemos el usuario correspondiente. Si no lo es, delegamos la tarea para crear un usuario al método _insert de la clase Base.

Antes de devolver el registro de usuario, colocamos el _id en una cadena. ¿Porqué es eso? El objeto que se nos devuelve define el campo _id como un objeto MongoId. La aplicación cliente, sin embargo, no necesita este objeto.

El modelo User también debe admitir la actualización de registros de usuario. Agrega el siguiente código al método update:

1
if ( is_array( $data ) ) {
2
    $data   = ( object ) $data;
3
}
4
if ( isset( $data->email ) || isset( $data->mobile ) ) {
5
        $this->_where( '$and', array(
6
            array(
7
                '_id'       => array( '$ne' => new \MongoId( $id ) )
8
            ),
9
            array(
10
                '$or'       => array(
11
                    array(
12
                        'email'     => ( isset( $data->email ) ) ? $data->email : ""
13
                    ),
14
                    array(
15
                        'mobile'    => ( isset( $data->mobile ) ) ? $data->mobile : ""
16
                    )
17
                )
18
            )
19
        )
20
    );
21
    $existing   = $this->_findOne( $this->_col );
22
    if ( !empty( ( array ) $existing ) && $existing->_id != $id ) {
23
        $this->_error   = "ERROR_EXISTING_USER";
24
        return false;
25
    }
26
}
27
28
$this->_where( '_id', $id );
29
return $this->_update( $this->_col, ( array ) $data );

Al igual que en la clase Base, el método update acepta tanto matrices como objetos como los datos para el usuario. Esto hace que el método sea mucho más flexible.

Antes de actualizar el registro de usuario, nos aseguramos de que email y mobile del usuario no estén en uso por otro usuario. Si ese es el caso, establecemos el error en EXISTING_USER y devolvemos false. De lo contrario, delegamos la operación de actualización a la clase Base.

5. Clase BaseController

Al igual que los modelos de la aplicación heredados de la clase Base, los controladores también heredan de una clase padre común, distinta de la clase Controller de Laravel. Sin embargo, esta clase BaseController no estará cerca de la complejidad del modelo Base.

La clase solo se utilizará para manejar algunas tareas simples. Para crear la clase, usamos el comando artisan de Laravel. Desde la línea de comandos, navega hasta la raíz de tu aplicación y ejecuta el siguiente comando:

1
php artisan make:controller BaseController --plain

Esto creará un archivo llamado BaseController.php dentro de la carpeta Controllers de la aplicación en la carpeta app/Http. Dado que estamos utilizando el indicador --plain, el controlador no tendrá ningún método, lo cual es lo que queremos.

Este controlador no utilizará la clase Request, por lo que puedes continuar y eliminar la siguiente línea:

1
 use Illuminate\Http\Request;

Debido a que necesitamos acceso al modelo Session, agrega la siguiente línea a la declaración de la clase BaseController:

1
use App\Http\Models\Session as SessionModel;

Ahora estamos listos para implementar los métodos de la clase BaseController. Comienza agregando los siguientes métodos dentro de la declaración de clase:

1
protected function _check_session( $token = "", $id = "" ) {
2
    $result = false;
3
    if ( !empty( $token ) ) {
4
        $SessionModel   = new SessionModel();
5
        $session        = $SessionModel->find( $token );
6
7
        if ( !empty( ( array ) $session ) ) {
8
            if ( !empty( $id ) ) {
9
                if ( $session->user_id == $id ) {
10
                    $result = $session;
11
                }
12
            } else {
13
                $result     = $session;
14
            }
15
        }
16
    }
17
18
    return $result;
19
}
20
21
protected function _response( $result ) {
22
    if ( is_object( $result ) && property_exists( $result, "status" ) ) {
23
        return response()->json( $result, $result->status );
24
    } else {
25
        return response()->json( $result );
26
    }
27
}

El método _check_session se utiliza para verificar el token de sesión, que se pasa como primer argumento. Algunas tareas en la aplicación requieren que el usuario esté conectado. Por ejemplo, al actualizar un registro de usuario, el usuario correspondiente a la sesión activa debe coincidir con el _id del registro que debe actualizarse.

La implementación es bastante sencilla. Obtenemos la sesión para el token de sesión y, si el ID del usuario que corresponde con la sesión coincide con el ID que se pasa como segundo argumento, devolvemos la sesión. De lo contrario, devolvemos false.

El otro método auxiliar se encarga de enviar un resultado al cliente que consume la API. Por el momento, solo apoyamos a JSON. Si el resultado a devolver es un objeto y tiene un parámetro de estado, lo configuramos utilizando el método de ayuda response de Laravel. De lo contrario, simplemente devolvemos el resultado.

6. Clase SessionController

El siguiente controlador que implementaremos es el que maneja las solicitudes del recurso de Sessions. Desde la línea de comandos, navega hasta la raíz de tu aplicación y ejecuta el siguiente comando:

1
php artisan make:controller SessionController --plain

Esto creará un nuevo archivo llamado SessionController.php dentro de la carpeta Controllers de la aplicación en la carpeta de app/Http. Antes de implementar esta clase, necesitamos cuidar algunas cosas.

La clase SessionController actualmente hereda de la clase Controller de Laravel. Necesitamos configurar esto para usar nuestra clase BaseController. Esto significa que necesitamos reemplazar

1
use App\Http\Controllers\Controller;

con

1
use App\Http\Controllers\BaseController;

También necesitamos cambiar la cláusula extends de la clase. En lugar de extenderse desde Controller, asegúrate de que tu clase esté extendiendo la clase BaseController. También debemos incluir los modelos utilizados en el controlador. Debajo de la última declaración, agrega las siguientes líneas:

1
use App\Http\Models\Session as SessionModel;
2
use App\Http\Models\User as UserModel;

Normalmente, solo usaríamos SessionModel, pero verás por qué también estamos usando UserModel en un momento. En cuanto a la propia clase del controlador, agrega el siguiente código:

1
private $_model = null;
2
3
public function __construct() {
4
    $this->_model   = new SessionModel();
5
}
6
7
public function create( Request $request ) {}
8
9
public function destroy( $token ) {}

Establecemos el objeto model del controlador en el constructor y declaramos un par de métodos, que son las acciones admitidas por el recurso Sessions.

Paso 1: Eliminación de la sesión

Para eliminar una sesión de usuario, simplemente usamos el token de sesión, que se proporciona como un parámetro en la URL del recurso. Lo declararemos más adelante en las rutas de aplicación. Dentro del método destroy, agrega el siguiente código:

1
$result = new \stdClass();
2
if ( !$this->_model->remove( $token ) ) {
3
    $result->error  = "ERROR_REMOVING_SESSION";
4
    $result->status = 403;
5
}
6
7
return $this->_response( $result );

El método utiliza el método remove de SessionModel y devuelve el resultado utilizando el método _response de la clase BaseController. Si la eliminación de la sesión es exitosa, devolvemos un objeto vacío. Si ocurrió un error, devolvemos un error con un código de estado 403.

Paso 2: Creación de la sesión

El método para crear una sesión es un poco más complicado. Ten en cuenta que en la declaración del método estamos utilizando el objeto Request de Laravel. Utilizamos este objeto para acceder a los parámetros POST de la solicitud. Dentro del método create, agrega el siguiente código:

1
$email      = $request->get( 'email' );
2
$mobile     = $request->get( 'mobile' );
3
$fbId       = $request->get( 'fbId' );
4
5
$result     = new \stdClass();
6
if ( ( empty( $email ) && empty( $mobile ) ) || empty( $fbId ) ) {
7
    $result->error  = "ERROR_INVALID_PARAMETERS";
8
    $result->status = 403;
9
} else {}
10
11
return $this->_response( $result );

Aún no hemos creado el objeto de sesión, porque hay algo que debemos discutir primero. La aplicación va a utilizar Facebook Login solamente. Desde el SDK de Facebook, obtenemos la información del usuario al realizar una operación de inicio de sesión. En el controlador POST de recursos API de Session, necesitamos admitir dos cosas:

  • iniciar una sesión para un usuario
  • crear el usuario cuando no existe y luego iniciando la sesión

Esta es también la razón para incluir UserModel en el controlador. En la cláusula else vacía anterior, agrega el siguiente código:

1
$UserModel  = new UserModel();
2
$where      = ( !empty( $email ) ) ? array( 'email' => $email ) : array( 'mobile' => $mobile );
3
$user       = $UserModel->get( $where );
4
5
if ( empty( ( array ) $user ) ) {
6
   
7
} else {
8
    if ( $fbId != $user->fbId ) {
9
        $result->error  = "ERROR_INVALID_CREDENTIALS";
10
        $result->status = 403;
11
    }
12
}
13
14
if ( !property_exists( $result, "error" ) ) {
15
    $result         = $this->_model->create( $user );
16
    $result->token  = $result->_id;
17
    unset( $result->_id );
18
}

Primero verificamos si hay un usuario existente con email o mobile. Si el usuario existe, verificamos que el ID de Facebook dada coincida con el ID de Facebook para el registro del usuario. Si ese es el caso, creamos el objeto de sesión. Si no lo es, el método devuelve un error INVALID_CREDENTIALS con un código de estado 403.

Comenzar una sesión ya está completo. Ten en cuenta que esto no es extra seguro. Sin embargo, para los fines de este tutorial, funcionará bien.

En el caso de que no haya ningún usuario asociado con el correo electrónico o el dispositivo móvil, deseamos crear un nuevo registro. En la cláusula if anterior que está vacía, agrega el siguiente código:

1
name           = $request->get( 'name' );
2
$gender         = $request->get( 'gender' );
3
$location       = $request->get( 'location' );
4
5
if ( empty( $name ) || empty( ( array ) $location ) || empty( $gender ) ) {
6
    $result->error  = "ERROR_INVALID_PARAMETERS";
7
    $result->status = 403;
8
} else {
9
    if ( gettype( $location ) == "string" ) {
10
        $location   = json_decode( $location );
11
    }
12
    $locObj                 = new \stdClass();
13
    $locObj->type           = "Point";
14
    $locObj->coordinates    = array( $location->lon, $location->lat );
15
16
    $user->name     = $name;
17
    $user->fbId     = $fbId;
18
    $user->email    = $email;
19
    $user->mobile   = $mobile;
20
    $user->gender   = $gender;
21
    $user->location = $locObj;
22
23
    $user           = $UserModel->create( $user );
24
}

Primero recuperamos el resto de los parámetros requeridos de la solicitud y luego verificamos si el parámetro location se proporciona como un objeto JSON o un objeto JSON codificado (string). El método espera que este parámetro esté en el siguiente formato:

1
{
2
    "lat"    : 37.427208696456866,
3
    "lon"    : -122.17097282409668
4
}

Luego, transformamos esta ubicación en una ubicación de MongoDB 2dSphere. Para ejecutar consultas geoespaciales, este campo debe tener el siguiente formato:

1
{
2
    "type"            : "Point"
3
    "coordinates"    : [ -122.17097282409668, 37.427208696456866 ]
4
}

Podríamos pedirle al cliente que envíe la ubicación en este formato. Sin embargo, es mejor que no sobrecarguemos al cliente con reformatear la ubicación del usuario ya que esto es específico para nuestra implementación.

Después de configurar el objeto de ubicación, verificamos que los parámetros requeridos por el usuario existen y, si ese es el caso, creamos un nuevo objeto de usuario usando el método create de la clase UserModel.

Eso es. Aunque podríamos iniciar una sesión enviando solo los parámetros email y fbId o los parámetros mobile y fbId, si se proporciona el resto de la información del usuario, nuestro controlador se encargará de crear un nuevo usuario cuando sea necesario y comenzar una sesión.

7. Clase UserController

El último controlador que necesita la aplicación es el encargado de manejar el recurso Users. Una vez más, usamos el comando artisan de Laravel. Desde la línea de comandos, navega hasta la raíz de tu aplicación y ejecuta el siguiente comando:

1
php artisan make:controller UserController --plain 

Esto creará un archivo UserController.php dentro de la carpeta Controllers de la aplicación en la carpeta app/Http. Al igual que con la clase SessionController, asegúrate de que la clase UserController hereda de BaseController y que incluya la clase UserModel. Dentro de la declaración de clase, agrega el siguiente código:

1
private $_model = null;
2
3
public function __construct() {
4
    $this->_model   = new UserModel();
5
}
6
7
public function create( Request $request ) {}
8
9
public function get( Request $request, $id ) {}
10
11
public function remove( Request $request, $id ) {}
12
13
public function retrieve( Request $request ) {}
14
15
public function update( Request $request, $id ) {}

Al igual que con la clase SessionController, inicializamos el objeto del modelo y declaramos los métodos que serán apoyados por el recurso Users. Comencemos con los de las operaciones GET. En el método get, agrega el siguiente código:

1
$token  = $request->get( 'token' );
2
3
$result = new \stdClass();
4
if ( !$this->_check_session( $token ) ) {
5
    $result->error  = "PERMISSION_DENIED";
6
    $result->status = 403;
7
} else {
8
    $result = $this->_model->get( $id );
9
}
10
11
return $this->_response( $result );

Para recuperar un registro del sistema, requerimos que el usuario tenga una sesión activa. No tiene que coincidir con el ID del usuario recuperado. Si el usuario no tiene una sesión válida, devolvemos un error PERMISSION_DENIED con un código de estado 403. De lo contrario, devolvemos el registro de usuario como un objeto JSON.

A continuación, para una lista de usuarios, necesitamos implementar el método de recuperación. Agrega el siguiente código al método retrieve:

1
$token      = $request->get( 'token' );
2
$distance   = $request->get( 'distance' );
3
4
$session    = $this->_check_session( $token );
5
$result     = $this->_model->retrieve( ( isset( $session->user_id ) ? $session->user_id : "" ), $distance, $request->get( 'limit' ), $request->get( 'page' ) );
6
if ( !is_array( $result ) && !$result ) {
7
    $result         = new \stdClass();
8
    $result->error  = $this->_model->get_error();
9
    $result->status = 403;
10
}
11
12
return $this->_response( $result );

Comenzamos por buscar los parámetros de solicitud, el token de sesión del usuario y los parámetros de distancia en particular. Este método, sin embargo, no requiere una sesión activa. Si una sesión es válida, pasamos el ID de usuario al método retrieve de la clase UserModel.

Si se pasa un parámetro distance, se ejecuta una consulta geoespacial. Si no, se realiza una consulta regular de tipo find. En caso de errores, recuperamos el error del modelo y lo devolvemos al usuario con un código de estado 403. De lo contrario, devolvemos una matriz que contiene los usuarios encontrados.

La creación del usuario se asignará a la operación POST del recurso Users. Agrega el siguiente código al método create:

1
$email      = $request->get( 'email' );
2
$fbId       = $request->get( 'fbId' );
3
$gender     = $request->get( 'gender' );
4
$location   = $request->get( 'location' );
5
$mobile     = $request->get( 'mobile' );
6
$name       = $request->get( 'name' );
7
8
if ( gettype( $location ) == "string" ) {
9
    $location   = json_decode( $location );
10
}
11
$locObj                 = new \stdClass();
12
$locObj->type           = "Point";
13
$locObj->coordinates    = array( $location->lon, $location->lat );
14
15
$result     = new \stdClass();
16
if ( empty( $name ) || empty( ( array ) $location ) || empty( $fbId ) || empty( $gender ) || ( empty( $email ) && empty( $mobile ) ) ) {
17
    $result->error  = "ERROR_INVALID_PARAMETERS";
18
    $result->status = 403;
19
} else {
20
    $user       = array(
21
        "email"     => $email,
22
        "fbId"      => $fbId,
23
        "gender"    => $gender,
24
        "location"  => $locObj,
25
        "mobile"    => $mobile,
26
        "name"      => $name
27
    );
28
    $result     = $this->_model->create( $user );
29
}
30
31
return $this->_response( $result );

Primero recuperamos la información necesaria para el usuario y, como en el controlador de creación de sesión, convertimos la ubicación del usuario al formato apropiado.

Después de esto, verificamos que se haya pasado la información requerida. Ten en cuenta que aunque los campos email y mobile son opcionales, al menos uno debe estar presente.

Después de estas comprobaciones, invocamos el método create de la clase UserModel para insertar el nuevo usuario en la base de datos. Finalmente, devolvemos el nuevo usuario o un error.

Para eliminar un usuario, necesitamos implementar el método de eliminación. Agrega el siguiente código al método de remove:

1
$token  = $request->get( 'token' );
2
$result = new \stdClass();
3
4
if ( !$this->_check_session( $token, $id ) ) {
5
    $result->error  = "PERMISSION_DENIED";
6
    $result->status = 403;
7
} else {
8
    $result = $this->_model->remove( $id );
9
    if ( !$result ) {
10
        $result         = new \stdClass();
11
        $result->error  = $this->_model->get_error();
12
        $result->status = 403;
13
    }
14
}
15
16
return $this->_response( $result );

Este es uno de esos métodos en los que queremos que el _id del usuario se elimine para que coincida con el _id del usuario con la sesión activa. Esto es lo primero que verificamos. Si ese es el caso, delegamos al método remove del modelo. De lo contrario, establecemos el error en PERMISSION_DENIED y enviamos el resultado al usuario.

Finalmente, implementemos la operación de actualización del usuario. Dentro del método update, agrega el siguiente código:

1
$token      = $request->get( 'token' );
2
$data       = new \stdClass();
3
if ( !empty( $email = $request->get( 'email' ) ) ) {
4
    $data->email    = $email;
5
}
6
if ( !empty( $fbId = $request->get( 'fbId' ) ) ) {
7
    $data->fbId     = $fbId;
8
}
9
if ( !empty( $gender = $request->get( 'gender' ) ) ) {
10
    $data->gender   = $gender;
11
}
12
if ( !empty( $location = $request->get( 'location' ) ) ) {
13
    if ( gettype( $location ) == "string" ) {
14
        $location   = json_decode( $location );
15
    }
16
    $locObj                 = new \stdClass();
17
    $locObj->type           = "Point";
18
    $locObj->coordinates    = array( $location->lon, $location->lat );
19
       
20
    $data->location = $locObj;
21
}
22
if ( !empty( $mobile = $request->get( 'mobile' ) ) ) {
23
    $data->mobile   = $mobile;
24
}
25
if ( !empty( $name = $request->get( 'name' ) ) ) {
26
    $data->name     = $name;
27
}
28
29
$result     = new \stdClass();
30
if ( !$this->_check_session( $token, $id ) ) {
31
    $result->error  = "PERMISSION_DENIED";
32
    $result->status = 403;
33
} else {
34
    $result = $this->_model->update( $id, $data );
35
    if ( !$result ) {
36
        $result         = new \stdClass();
37
        $result->error  = $this->_model->get_error();
38
        $result->status = 403;
39
    }
40
}
41
42
return $this->_response( $result );

Validamos los datos que se pasan y configuramos el objeto apropiado para que actualizar. En el caso del parámetro location, primero lo reformateamos.

Nuevamente, este método solo debe ser accesible para usuarios con una sesión activa que corresponda a su propio _id. Esto significa que primero comprobamos que ese es el caso.

Luego invocamos el método update de la clase UserModel y devolvemos el resultado al cliente.

8. Router de la aplicación

Con esa última pieza de código nuestra API está completa. Tenemos nuestros controladores y modelos en su lugar. Lo último que tenemos que hacer es asignar las solicitudes entrantes a los puntos finales apropiados.

Para eso, necesitamos editar el archivo route.php de tu aplicación. Se encuentra dentro de la carpeta app\Http. Si lo abres, deberías ver algo como esto:

1
Route::get( '/', 'WelcomeController@index' );

Cuando la aplicación recibe una solicitud GET sin ningún recurso especificado, el método index de la clase WelcomeController debería manejarlo. Sin embargo, es probable que ya hayas eliminado WelcomeController al comienzo de este tutorial. Si intentas navegar a este punto final en un navegador, obtendrás un error. Vamos a reemplazar esa última línea con el siguiente código:

1
Route::post( 'sessions', 'SessionController@create' );
2
3
Route::delete( 'sessions/{token}', 'SessionController@destroy' );
4
5
Route::delete( 'users/{id}', 'UserController@remove' );
6
7
Route::get( 'users', 'UserController@retrieve' );
8
9
Route::get( 'users/{id}', 'UserController@get' );
10
11
Route::post( 'users', 'UserController@create' );
12
13
Route::put( 'users/{id}', 'UserController@update' );

Asignamos las solicitudes API a los métodos previamente agregados a nuestros controladores. Por ejemplo, la siguiente llamada

1
[ DELETE ] - http://YOUR_API_URL/sessions/abc

se traduce en una solicitud DELETE a la URL dada. Esto significa que en el SessionController se llamará al método delete con un token de abc.

Conclusión

Eso es todo para la API REST con Laravel 5.0. Tenemos soporte para la administración de usuarios y sesiones, que es exactamente lo que necesitamos para implementar el cliente iOS.

En la siguiente parte de este tutorial, Jordan te mostrará cómo integrar esta API en una aplicación de iOS. También te mostrará cómo integrar el SDK de Sinch para mensajes y llamadas de voz.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.