Herramientas de usuario

Herramientas del sitio


cursos:yii2:restapi:servidor_rest_api

¡Esta es una revisión vieja del documento!


Servidor REST API con Yii2

Podemos crear fácilmente un servidor REST API con Yii2, que puede hacer las mismas funciones que node.js, aprovechando parte de una aplicación que ya tengamos con Yii2 o partiendo de cero.

Los pasos son:

Habilitar URL's limpias y configurar reglas

En este documento se explica como habilitar las URL's limpias. Para el caso del servidor REST API debemos asignar la propiedad enableStrictParsing de urlManager a true, y definir la propiedad rules,que especificarán la relación entre rutas y controladore/acciones. Ver este documento. Si vamos a definir muchas reglas, es cómodo separar esta información en un fichero aparte y cargarlo en config/web.php:

 'urlManager' => [
        'enablePrettyUrl' => true,
        'enableStrictParsing' => true,
        'showScriptName' => false,
        'rules' => require(__DIR__ . '/rules.php')

Y así, en rules.php ponemos las reglas:

<?php
return  [
['class' => 'yii\rest\UrlRule',
    'pluralize'=>false,
    'controller' => ['grupos','usuarios'],
],
['class' => 'yii\rest\UrlRule',
    'controller' => ['user'],
    'pluralize'=>false,
    'extraPatterns'=>['POST authenticate'=>'authenticate']
]
 
];

Es habitual que en los verbos POST, PUT y PATCH, los datos del objeto a actualizar se envíen en formato JSON, como es el caso de ANGULAR JS. Para que Yii pueda procesarlos correctamente, tenemos que activar el parser de JSON en el componente Request→parsers:

 'request' => [
    'cookieValidationKey' => '....',
    'parsers' => [
        'application/json' => 'yii\web\JsonParser',
    ]
 ],

Controladores

Los controladores para servicios REST API derivan de la clase yii\rest\ActiveController. Para crear un controlador que defina los servicios básicos (GET, GET id,POST, PUT, PATCH, DELETE), basta con especificar el modelo con el que va a trabajar el controlador

namespace app\controllers;
use yii\rest\ActiveController;
 
class UsuariosController extends Controller
{
    public $modelClass = 'app\models\Usuarios';
}

Con esto, ya podemos hacer pruebas de solicitar datos a nuestro servidor. Es conveniente utilizar algún programa para poder hacer tests de nuestro servidor. Uno muy conveniente es POSTMAN, que tiene versión de escritorio y también puede instalarse como plugin de Chrome. Si accedemos a la ruta: http://SERVIDOR/APLICACION/web/usuarios, debería salir una lista de usuarios, en formato JSON. (Si lo hacemos en el navegador, se mostrará en XML, porque Yii detecta que se está accediendo de esa forma).

Selección de datos a Recibir

En el servidor es posible definir qué atributos queremos enviar en las peticiones GET, y que atributos puede solicitar el usuario bajo demanda. Para ello, utilizaremos los métodos fields() y extrafields() en los modelos implicados. El método fields() devuelve un array con las propiedades que se devolverán por defecto, si no se especifican en la petición

public function fields(){
    return ['id','nombre','estado','email','rol'];
}

Por defecto, fields() devuelve todos los atributos del modelo. Podemos eliminar o añadir atributos a partir de los predefinidos en la clase base:

public function fields(){
    $fields=array_diff(parent::fields(),['password','authkey']); //Nunca devuelve password ni authkey
    return array_merge($fields,['estadoText','comentarios']); //Añade estadoText y el array de comentarios definidos mediante una relación con el modelo Comentarios
}

El cliente puede especificar los atributos que desea que se devuelvan, que han de estar en la lista generada en el modelo mediante fields(). Para ello, se añade el parámetro ?fields=propiedades en la URL:

http://SERVIDOR/usuarios/7?fields=id,nombre,email // Únicamente se reciben estas 3 propiedades

El método extrafields es similar a fields(), y devuelve las propiedades opcionales que se pueden obtener, mediante el parámetro ?expand=propiedades en la URL

public function extrafields(){
    return ['grupos','apuntes','eventos','saldo']); 
}

Así, para obtener los apuntes del usuario:

http://SERVIDOR/usuarios/7?extrafields=apuntes // Devuelve el usuario junto con sus apuntes

Autenticación

Podemos obligar a que en determinados controladores o peticiones sea necesario que el usuario se identifique. A diferencia de las aplicaciones estándar, donde se suelente utilizar las sesiones de usuario ($_SESSION de php), en las aplicaciones Rest API se suele enviar en cada petición lo necesario para que el servidor identifique al usuario. Hay diversas formas de hacerlo.Ver documentación. Una solución típica es implementar una acción de autenticación con usuario y contraseña, que devuelve un token (cadena aleatoria asociada a cada usuario), y en las sucesivas peticiones se envía este token en las HEADERS de la petición http para identificar al usuario. (Bearer token). Para ello,los pasos a seguir son: En la configuraciób de rules de urlManager (rules.php, o web.php si no utilizamos un fichero aparte), definimos un verbo particular para esta acción

'rules'=>....
 
['class' => 'yii\rest\UrlRule',
    'controller' => ['user'],
    'pluralize'=>false,
    'extraPatterns'=>['POST authenticate'=>'authenticate',
            'OPTIONS authenticate'=>'authenticate',
            ]
],
...

Creamos un controlador UserController que contiene la acción de autenticación, utilizando el modelo Usuario:

namespace app\controllers;
use yii\rest\Controller;
 
class UserController extends Controller
{
    public $modelClass = 'app\models\Usuarios';
 
  public function actionAuthenticate(){
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
      // Si se envían los datos en formato raw dentro de la petición http, se recogen así:
      $params=json_decode(file_get_contents("php://input"), false);
      @$username=$params->username;
      @$password=$params->password;
      // Si se envían los datos de la forma habitual (form-data), se reciben en $_POST:
      //$username=$_POST['username'];
      //$password=$_POST['password' ];
 
      if($u=\app\models\Usuarios::findOne(['usuario'=>$username]))
          if($u->password==md5($password)) {//o crypt, según esté en la BD
 
              return ['token'=>$u->token,'id'=>$u->id,'nombre'=>$u->nombre];
          }
 
      return ['error'=>'Usuario incorrecto. '.$username];
    }
  }

En la tabla de usuarios necesitaremos un campo 'token', asociado a cada usuario, que podemos generar de forma aleatoria en el momento del alta del mismo. Este dato es el que se devuelve en la autenticación y se empleará en las sucesivas peticiones para identificar al usuario.

El modelo Usuarios ha de implementar UserIdentityInterface, tal como se detalla aquí Por defecto, Yii utiliza el modelo User para la autenticación. Si lo cambiamos por Usuarios, debemos modificar la configuración de components en web.php:

  'components' => [
    ...
    'user' => [
            'identityClass' => 'app\models\Usuarios',
            'enableAutoLogin' => true,
        ],
    ...

En las acciones donde queramos que el usuario tenga que identificarse, activaremos el behavior authenticator en el método behaviors() del controlador.

...
use yii\filters\auth\HttpBearerAuth;
 
public function behaviors() {
       $behaviors = parent::behaviors();
            ...
       $behaviors['authenticator'] = [
          'class' => HttpBearerAuth::className(),
          'except' => ['options', 'authenticate'],
       ];
       return $behaviors;
}

En el modelo User implementaremos el método que localiza al usuario a partir del token:

public static function findIdentityByAccessToken($token, $type = null) {
                return self::findOne(['token' => $token]);
 
        }

Personalización de acciones

Si queremos personalizar una acción sobreescribiremos el método actions del controlador. Podemos eliminar acciones , añadir o modificar las predefinidas. Por ejemplo, si queremos que un usuario solamente pueda acceder a información suya respecto a la tabla Apuntes:

namespace app\controllers;
use Yii;
use yii\rest\ActiveController;
use yii\data\ActiveDataProvider;
use app\models\Apuntes;
 
class ApuntesController extends BearerController
{
    public $modelClass = 'app\models\Apuntes';
 
    public function actions() {
            $actions = parent::actions();
            //Eliminamos acciones de crear y eliminar apuntes. Eliminamos update para personalizarla
            unset($actions['delete'], $actions['create'],$actions['update']);
            // Redefinimos el método que prepara los datos en el index
            $actions['index']['prepareDataProvider'] = [$this, 'indexProvider'];
            return $actions;
    }
 
    public function indexProvider() {
            $uid=Yii::$app->user->identity->id;
            return new ActiveDataProvider([
                'query' => Apuntes::find()->where('usuarios_id='.$uid )->orderBy('id')
            ]);
    }
    public function actionUpdate($id){
       // Hacemos lo queramos y devolvemos información con return (un array, un objeto...)
    }
 
}

Acceso desde otros servidores. CRSF

Para evitar el problema de referencias cruzadas, CRSF, lo desactivaremos asignando a false $enableCsrfValidation en el controlador.

use Yii;
use yii\filters\Cors;
 
class UsuariosController extends \yii\rest\ActiveController {
   public $enableCsrfValidation = false;
 
   public function behaviors() {
        $behaviors = parent::behaviors();
        // Si queremos habilitar la autenticación mediante Bearer token
        $behaviors['authenticator'] = [
        'class' =>  HttpBearerAuth::className(),
        'except' => ['options','authenticate'],
        ];
 
        return $behaviors;
    }
 

Si queremos habilitar el acceso a nuestra API desde otros servidores, podemos activar CORS

   public function behaviors() {
        $behaviors = parent::behaviors();
        ...
        $behaviors['corsFilter'] = [
        'class' => Cors::className(),
        'cors' => [
            'Origin' => ['*'],
            'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
            'Access-Control-Request-Headers' => ['*'],
            'Access-Control-Allow-Credentials' => true,
        ],
        ];
        ...
        return $behaviors;
  }