19 agosto 2017

Aplicaciones de una página (Single Page App) con Angular CLI Version 4

  3 comentarios
21:24


En estos días, me he encontrado con un problema bastante grande, y es que para las personas que estábamos acostumbrados a utilizar el framework AngularJS o como muchos le llaman, Angular 1, nos hemos topado con la gran sorpresa de que las versiones siguientes de dicho framework no se parecen ni en la mas mínima parte a la sintaxis de programación a este, y es que según nos enteramos, esto se debe a que a no es un continuación del framework sino, un framework totalmente nuevo, diferente y con un nuevo concepto en la programación. En particular, yo he sido uno de los que me ha costado migrar ya que estaba acostumbrado a la versión inicial, y por falta de tiempo, no me había adentrado a la programación de componentes, sin embargo, nuestro papel como desarrolladores es estar siempre a la vanguardia con lo nuevo.

Uno de los usos mas comunes que solía dar al framework AngularJS es la maravillosa manera en la que maneja los datos con su doble binding y en particular, las aplicaciones de una sola pagina con el Routing de AngularJS que en realidad, me vi obligado a investigar de que manera trabaja este concepto en el nuevo framework de Angular, sin embargo, me tope con que no se encuentra en la Red mucha información en español con respecto a estos temas en particular, y no me quedo de otra que leer blogs y documentación en ingles, que ahora me gustaría compartirles, sin mas que añadir, empecemos con el tutorial.

Antes de comenzar, es necesario saber que este tutorial cuenta con los siguientes recursos de software:
  1. Sistema Operativo Ubuntu 14.04 LTS
  2. Editor de Texto Visual Studio Code
  3. Instalación previa de NodeJS y NPM (Gestor de paquetes de Node)
  4. Navegador Chromium Version 60.0.3112.78
  5. Angular CLI Version 4
Nos basaremos especialmente en la terminal de Ubuntu o en su defecto, Linux para los comandos, en realidad, los recursos previamente mencionados no es que sean indispensable, excepto NodeJS y NPM, y la instalación de Angular CLI, con respecto a los recursos de hardware es irrelevante.

Primeramente, abrimos la terminar de Ubuntu con las teclas Ctrl + Alt + T o en el caso de Windows, Inicio + R para abrir Ejecutar y escribes "cmd" sin comillas, y teclea en ENTER, para genear un nuevo proyecto, introduce las siguientes lineas una por una


ng new miApp

cd miApp

ng serve


Los comandos anteriores crea una aplicación de Angular, una vez creadas accedemos a su carpeta y después, servimos la aplicación para poder visualizar los cambios que vamos haciendo en el navegador con el LiveReload interno que trae Angular CLI la cual por defecto, se ejecutará en http://localhost:4200, a no ser que hayas configurado una dirección personalizada. Por favor, minimiza esta terminal, ya que mientras el servidor este corriendo, no podrás escribir mas comandos acá.

Luego de esto, vamos a Visual Studio Code o nuestro editor de código favorito, y abrimos un nuevo folder (Ctrl + KO) y buscamos nuestra carpeta creada, entramos y buscamos la carpeta "src", donde se ubica la fuente de nuestro proyecto, y hacemos click en el botón "Abrir", y te cargará la siguiente estructura o esquema de carpetas.


Creando los componentes que funcionaran como páginas

Abrimos un nuevo terminal, e introducimos el siguiente comando

cd ~/miApp

Una vez ubicados en la carpeta de nuestro proyecto, introducimos el siguiente comando para generar los componentes

ng generate component inicio

ng generate component contacto

ng generate component informacion

ng generate component paginaNoEncontrada

Lo que nos generará 4 componentes que fungirán como las paginas a las que accederemos mediante el Routing, una vez hecho esto, vamos a Visual Studio Code y en la barra lateral, y editamos el siguiente archivo

app/app.module.ts


import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { InicioComponent } from './inicio/inicio.component';
import { ContactoComponent } from './contacto/contacto.component';
import { InformacionComponent } from './informacion/informacion.component';
import { PaginaNoEncontradaComponent } from './pagina-no-encontrada/pagina-no-encontrada.component';

const appRoutes: Routes = [
  { path: 'inicio', component: InicioComponent },
  { path: 'contacto', component: ContactoComponent },
  { path: 'informacion', component: InformacionComponent },
  { path: '',
    redirectTo: '/inicio',
    pathMatch: 'full'
  },
  { path: '**', component: PaginaNoEncontradaComponent }
];


@NgModule({
  declarations: [
    AppComponent,
    InicioComponent,
    ContactoComponent,
    InformacionComponent,
    PaginaNoEncontradaComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } 
    )
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Ahora nos dirigimos al siguiente archivo, borramos todo lo que tenga, e introducimos el siguiente código

app/app.component.html


<a [routerLink]="['/inicio']">Inicio</a>
<a [routerLink]="['/contacto']">Contacto</a>
<a [routerLink]="['/informacion']">Informacion</a>

<router-outlet></router-outlet>

Si ingresamos a http://localhost:4200 veremos que ya tenemos un Routing totalmente funcional.

Página de Inicio
Página de Contacto
Página de Información

Por supuesto, la parte de estilo y diseño es de tu parte, puedes jugar con frameworks CSS como Bootstrap o Materialize para hacer un diseño mas llamativo, la edición de la información en cada página debes hacerla en su respectivo archivo HTML y CSS en la carpeta correspondiente al componente. He aquí un ejemplo usando los componentes del framework CSS Bootstrap


Routing con Parámetros


No podia pasar por alto este punto, ya que es una de las herramientas mas comunes que utilizamos los desarrolladores, sobre todo si trabajamos con API Rest o Web Services en general, el Routing con parámetros es de suma importancia para filtrar información esencial que dependa de una variable en particular. Para comenzar, abrimos nuevamente la terminar e introducimos el siguiente comando ubicados en la carpeta de nuestro proyecto

ng generate component detalles

Una vez generado el componente, nos dirigimos a nuestro archivo app.module.ts y e insertamos el siguiente código.

app/app.module.ts



import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { AppComponent } from './app.component';
import { InicioComponent } from './inicio/inicio.component';
import { ContactoComponent } from './contacto/contacto.component';
import { InformacionComponent } from './informacion/informacion.component';
import { PaginaNoEncontradaComponent } from './pagina-no-encontrada/pagina-no-encontrada.component';
import { DetallesComponent } from './detalles/detalles.component';

const appRoutes: Routes = [
  { path: 'inicio', component: InicioComponent },
  { path: 'contacto', component: ContactoComponent },
  { path: 'informacion', component: InformacionComponent },
  { path: 'inicio/:id', component: DetallesComponent },
  { path: '',
    redirectTo: '/inicio',
    pathMatch: 'full'
  },
  { path: '**', component: PaginaNoEncontradaComponent }
];

@NgModule({
  declarations: [
    AppComponent,
    InicioComponent,
    ContactoComponent,
    InformacionComponent,
    PaginaNoEncontradaComponent,
    DetallesComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } 
    )
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }


Ahora, nos dirigimos al siguiente archivo, y editamos

app/detalles/detalles.component.ts


import { Component, OnInit } from '@angular/core';
import {ActivatedRoute} from "@angular/router";

@Component({
  selector: 'app-detalles',
  templateUrl: './detalles.component.html',
  styleUrls: ['./detalles.component.css']
})
export class DetallesComponent implements OnInit {
  id: String;
  constructor(private route: ActivatedRoute) {
    this.route.params.subscribe( 
    params => this.id = params.id
   );
   }

  ngOnInit() {
  }

}


Nos dirigimos al siguiente archivo, borramos todo lo que tenga e insertamos el siguiente código

app/detalles/detalles.component.html

<p>
  El id es {{id}}
</p>

Ahora, si intentas ingresar a una dirección al estilo de http://localhost:4200/inicio/3, nos daría el siguiente resultado


Si tu deseo es enlazar a cierta pagina con cualquier Id (por ejemplo, una Id correspondiente a la entrada de un post, en el caso de los blogs), debes hacerlo con insertando el link donde corresponda de la siguiente manera

<a [routerLink]="['/inicio', 3]"/>Entrada con Id 3</a>;

Con esto, ya estas completamente preparado para seguir haciendo tus aplicaciones de una sola página y desempeñarte tan bien como la hacias con AngularJS.

Leer completo

15 enero 2017

Subir imagenes al servidor usando un Web Services API REST

  4 comentarios
14:13

Interfaz visual del servidor de imagenes
Desde hace rato que me surgía esta duda y es que los API Rest actualmente son la manera de trabajar la web, ya que nos ofrecen infinitas herramientas y recursos para un trabajo grupal y de optimizacion, pero, en vista de que los web services son archivos textuales que nos sirve el servidor en formato JSON o XML, parece un poco complicado subir una imagen a través de un API ya que una imagen no es un texto, o si? Si no has leído mis entradas anteriores te invito a leerlas para que te empapes mas de lo que hablare en esta.


Luego de dar lectura a las dos entradas anteriores, y entender en que consiste la creación y consumo de un web services, podemos proseguir. En este caso, vamos a crear un servicio completo de subida de imágenes al servidor usando esta metodología de trabajo y entendiendo ciertas características del mismo. Primero, entendiendo que si vamos a subir una imagen al servidor haciendo uso de un REST API, es necesario que la imagen sea un texto, sino, como envías una imagen en un objeto de este estilo, y se preguntaran, en serio se puede hacer esto? codificar una imagen a texto? Y la verdad es que si, se puede, y de esta herramienta tan preciada haremos uso hoy.

 Antes de comenzar, cabe destacar que al final del post dejare un demo y un link para descargar los archivos. Tenemos 4 archivos esenciales que se basan en la siguiente estructura, cabe acotar que no son los únicos archivos incluidos dentro del directorio puesto a que tengo incluida también ciertas librerías de diseño y dinamismo, entre ellas angularJS y Materialize para CSS, pero, en el siguiente esquema, solo nos centraremos en los archivos que modificaremos, el resto de los archivos podrán encontrarlos en el enlace de descarga del proyecto completo


  • css
    • custom.css
  • js
    • custom.js
  • upload
  • index.html
  • server.php
css/custom.css

Este código no requiere mucha explicación, aunque haremos uso del framework de diseño Materialize, necesitaremos definir los siguientes estilos para ciertos elementos


body {
 background-color: #fefefe;
}

#imgCont {
 background:#eeeeee;
 width:auto;
 height:300px;
 padding: 5px 5px 5px 5px;
 border: 1px solid #cccccc;
 margin-top:10px;
 overflow: auto;
}

#imgCont img {
 width:90px;
 border:1px solid #bbbbbb;
 margin-right:5px;
 transition:all ease .2s;
 -webkit-transition:all ease .2s;
}
#imgCont img:hover {
 opacity:.8;
 transition:all ease .2s;
 -webkit-transition:all ease .2s;
}
css/custom.js

Aquí empieza la función, si bien no podemos enviar una imagen como tal a un servicio web, si lo podemos hacer si codificamos la imagen, en este caso, haremos uso de base64, la cual es una codificación que tiene la capacidad de convertir cualquier dato definido a nivel de bytes en un formato seguro de transportar por Internet como lo son los caracteres ASCII, es decir, no solo sirve para codificar imágenes, sirve para codificar cualquier archivo, música, vídeos, documentos, o simplemente, cualquier cadena de texto o carácter que se represente en bytes, es decir, puede transformar todo lo que sea recurso digital. En nuestro controlador de imagen en angular simplemente definimos la función encodeImageFileAsURL(); la cual se encarga de hacer todo el trabajo de codificación, y luego la cadena es enviada al web services a través de un objeto.


     var app = angular.module('crud',[]);


  // Controlador principal de nuestra pagina
  app.controller('mainCtrl', function($scope,$http) {
   $scope.dominio = "http://localhost/imagenes/";
         $scope.message = 'Ejemplo';
         $scope.subtitle = "Subir una imagen haciendo uso de API REST";

         $scope.imageJSON = {};

         $scope.enlaces = {};

         angular.element(document).ready(function () {
       $scope.get();  
      });

         // La funcion post() que hace la solicitud para publicar un nuevo elemento
      $scope.post = function() {
       $http.post("server.php", $scope.imageJSON)
        .then(function (response){
               console.log(response);
               Materialize.toast(response.data.statusMessage, 4000);


               if(response.data.data != null) {
                $scope.cargarImagen(response.data.data);
               } 

               $scope.get();
           }, 
           function(response) {
            // Aqui va el codigo en caso de error
           });
      }

      $scope.get = function() {
       $http.get("server.php")
        .then(function (response){
               console.log(response);
               $scope.imagenes = response.data.data;

               
           }, 
           function(response) {
            // Aqui va el codigo en caso de error
           });
      }

      $scope.cargarImagen = function(ruta) {


       var container = document.getElementById('imgTest');
       var newImage = document.getElementById('imagen');
          newImage.src = ruta;
          container.style.width = 'auto';
          container.style.height = 'auto'

          container.innerHTML = newImage.outerHTML;

         $scope.enlaces.link = $scope.dominio + ruta;
         $scope.enlaces.html = "<a href='"+ $scope.dominio + ruta +"'>"+$scope.dominio + ruta+"</a>";
         $scope.enlaces.foros = "[IMG]"+$scope.dominio + ruta+"[/IMG]"
      }


         $scope.encodeImageFileAsURL = function() {

       var filesSelected = document.getElementById("inputFileToLoad").files;

       if (filesSelected.length > 0) {
         var fileToLoad = filesSelected[0];

         var fileReader = new FileReader();

         fileReader.onload = function(fileLoadedEvent) {
          
           var srcData = fileLoadedEvent.target.result; // <--- data: base64
      $scope.imageJSON.encodedImage = srcData;


      $scope.post();
         }
         fileReader.readAsDataURL(fileToLoad);
       }
     }



     });
server.php

Este archivo trabaja del lado del servidor, en este caso apache, el archivo recibe el texto de la imagen codificada, y luego la pasa a la función save_base64_image();, la cual, no hace mas que analizar la cadena enviada y mediante el uso de herramientas de decodificacion y escritura en el servidor, crea la imagen en el directorio upload del mismo, almacenando asi una imagen con una extensión, y creando la URL especifica del mismo.


<?php 
 header("Access-Control-Allow-Origin: *");
 // Permite la ejecucion de los metodos
 header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE"); 
 
 switch ($_SERVER['REQUEST_METHOD']) {

  case 'GET':
   $directorio = opendir("./upload"); //ruta actual

   $contador = 0;

   while ($archivo = readdir($directorio)) //obtenemos un archivo y luego otro sucesivamente
   {
       if (!is_dir($archivo))//verificamos si es o no un directorio
       {
           $listado[$contador] = $archivo;
       }
     $contador++;
   }


   

   $listado = array_values($listado);

   if(count($listado)!=0) {
    print_json(200, true, $listado);
   } else {
    print_json(200, "No existen elementos", null);
   }

   break;
  case 'POST':
    $content = file_get_contents("php://input");

    $array = json_decode($content, true);


    $validar = validate($array['encodedImage']);

    if($validar==1) {
     $imagen = save_base64_image($array['encodedImage'], "upload/IMG_" . strtoupper(md5($array['encodedImage'])));

     if($imagen!=null) {
      print_json(200, "Completado", $imagen);

     } else {
      print_json(200, "Este archivo ya existe", null);
     }
    } else {
     print_json(200, "Extension invalida", null);
    }

    
    
   break;
  default:
   print_json(405, 'Metodo no permitido', null);
   break;
 }

 function validate($string_base64) {
  $array = explode(",", $string_base64);
  if( ($array[0] == "data:image/jpeg;base64") || ($array[0] == "data:image/gif;base64") || ($array[0] == "data:image/png;base64") ) {
   return 1;
  } else {
   return 0;
  }
 }
 



 function save_base64_image($base64_image_string, $output_file_without_extentnion, $path_with_end_slash="" ) {
     //usage:  if( substr( $img_src, 0, 5 ) === "data:" ) {  $filename=save_base64_image($base64_image_string, $output_file_without_extentnion, getcwd() . "/application/assets/pins/$user_id/"); }      
     //
     //data is like:    data:image/png;base64,asdfasdfasdf
     $splited = explode(',', substr( $base64_image_string , 5 ) , 2);
     $mime=$splited[0];
     $data=$splited[1];

     $mime_split_without_base64=explode(';', $mime,2);
     $mime_split=explode('/', $mime_split_without_base64[0],2);
     if(count($mime_split)==2)
     {
         $extension=$mime_split[1];
         if($extension=='jpeg')$extension='jpg';
         //if($extension=='javascript')$extension='js';
         //if($extension=='text')$extension='txt';
         $output_file_with_extentnion.=$output_file_without_extentnion.'.'.$extension;
     }

     if(file_exists($output_file_with_extentnion)) {
      return null;
     } else {
      file_put_contents( $path_with_end_slash . $output_file_with_extentnion, base64_decode($data) );
      return $output_file_with_extentnion;
     }
     
     
 }

 function print_json($status, $mensaje, $data) {
  header("HTTP/1.1 $status $mensaje");
  header("Content-Type: application/json; charset=UTF-8");

  $response['statusCode'] = $status;
  $response['statusMessage'] = $mensaje;
  $response['data'] = $data;

  echo json_encode($response, JSON_PRETTY_PRINT);
 }


?>
index.html
Esta sera la pagina principal, donde se encuentra toda la interaccion con el usuario, y donde se embeden todos los archivos necesarios para el funcionamiento.


<!DOCTYPE html>
<html lang="es" ng-app="crud" >
<head>
 <meta charset="UTF-8">
 <title>Rest API Visual Client</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <!-- Incluimos el CND de Materialize CSS -->
 <link rel="stylesheet" href="css/materialize.min.css">
 <!-- Nuestro CSS personalizado -->
 <link rel="stylesheet" href="css/custom.css">
 <!-- Incluimos el CND de AngularJS -->
 <script src="js/angular.min.js"></script>
 <!-- Incluimos el CND de la libreria jQuery -->
 <script src="js/jquery-3.1.1.min.js"></script>
 
</head>
<body class="container" ng-controller="mainCtrl">

 <div class="row">
  <div class="col s12">
   <h1>{{message}}</h1>
   <p>{{subtitle}}</p> 
  </div>
 </div>

 <div class="row">
  <div class="col s12 m6 l6">
   <div class="row">
    <div class="col s12">
     <h6><small>Solo JPG, PNG, GIF</small></h6>
     <input id="inputFileToLoad" type="file"/>
     <button class="btn" ng-click="encodeImageFileAsURL()">Subir</button>
     
     
    </div>
    <div class="col s12">
     <div id="imgCont">
      <img ng-click="cargarImagen('upload/' + x)" ng-repeat="x in imagenes" src="upload/{{x}}" title="{{x}}" alt="{{x}}">
     </div>


    </div>

    <div class="input-field col s12">
        <input id="Link" type="text" placeholder="Enlace directo" class="validate" readonly ng-value="enlaces.link" ng-model="enlaces.link">
    </div>
    <div class="input-field col s12">
        <input id="HTML" type="text" class="validate" placeholder="Codigo HTML" readonly ng-value="enlaces.html" ng-model="enlaces.html">
    </div>
    <div class="input-field col s12">
        <input id="Forum" type="text" class="validate" placeholder="Codigo para foros" readonly ng-value="enlaces.foros" ng-model="enlaces.foros">
    </div>

   </div>
   
  </div>

  <div class="col s12 m6 l6">
   <div id="imgTest" class="center-align">

    <div class="preloader-wrapper small active">
        <div class="spinner-layer spinner-green-only">
          <div class="circle-clipper left">
            <div class="circle"></div>
          </div><div class="gap-patch">
            <div class="circle"></div>
          </div><div class="circle-clipper right">
            <div class="circle"></div>
          </div>
        </div>


    </div>
    <p>Esperando imagen</p>

    <img id="imagen" width="100%" alt="">

   </div>
  </div>
 </div>



 
     
 <!-- Nuestro codigo JavaScript personal -->
 <script src="js/custom.js"></script> 
 <!-- Incluimos el CND de Materialize JS -->
   <script src="js/materialize.min.js"></script>
</body>
</html>

Esto es todo, para entender mejor, les voy a dejar una demo y el link de descarga en Github para que lo tengan en su computador

  Demo Descargar

Leer completo

08 enero 2017

Consumir un Web Services API Rest con AngularJS

  6 comentarios
23:07


Actualización 12 de Julio de 2018

En vista de que algunas personas me comentaron que no funcionaba este tutorial en Windows, quiero aclarar que cuando lo realice, lo trabaje en Ubuntu, distribución GNU/Linux, por lo tanto, no me di cuenta de los errores que pudiera generar en Windows, esto ha sido corregido, y el código ha sido actualizado para funcionar en cualquier plataforma.

Como lo prometido es deuda y quiero saldar esta deuda con ustedes, ayer mas o menos a esta misma hora hice publica una entrada sobre como Crear un Web Service API Rest con PHP y MySQL en el que prometí traer pronto como consumir dicho servicio y he aquí el tutorial, por eso, si no has visto el tutorial anterior, te recomiendo leerlo antes de empezar con este, créeme, tendrás una mejor comprensión del mismo y así vamos al ritmo.

Tal cual mencione el dia de ayer, los API Rest o Web Service no son mas que archivos en formato JSON (JavaScript Object Notation) o XML (eXtended Markup Language) que nos sirven información con respecto a una entidad en una base de datos haciendo uso de los métodos HTTP (GET, POST, PUT y DELETE), justo ayer, hacíamos las pruebas básicas del API Rest creado con la aplicación de tipo extensión de Chrome Insomnia, pero, eso es solo para efectos de pruebas, valga la redundancia.

Los Web Service API Rest son usualmente consumidos desde JavaScript en el cliente, usando librerías como jQuery o el framework AngularJS, por efectos de facilidad y ademas, de visibilidad, usare AngularJS en este tutorial específicamente.

Aspecto de nuestro API Rest Visual Client


Ahora, recordemos la estructura que llevabamos anteriormente, donde teniamos completado el Web Service

Esta no es mas que la estructura básica del back-end donde podemos encontrar con los archivos de Web Services y que explique en el tutorial anterior.

Habíamos quedado pendiente con los archivos de la carpeta public_html, donde podemos encontrar el archivo index.html que sera el archivo de nuestra pagina principal y su carpeta parent sera la carpeta del Front-end específicamente.

Todo lo que este en la carpeta public_html pertenece única y exclusivamente a código estructura y maquetado del cliente, mientras que lo que este fuera de ella, pertenece al código que corre en el servidor, por lo tanto, hoy nos enfocaremos específicamente en dicha carpeta ya que haremos un cliente visual para el consumo del Web Services que realizamos previamente.

Antes de comenzar, quiero acotar que voy a dejar todos los archivos del proyecto en un enlace para descargar al final, asi que no se preocupen, presten atencion al tutorial y luego, hacen lo que deseen con el codigo.

Se ubican dentro de la carpeta public_html y crean el siguiente directorio

Donde tenemos los archivos básicos de todo proyecto web.

Es importante destacar, que ahora que tenemos mas archivos, lógicamente vamos a tener mas URL's a donde ir, entonces, antes de empezar, en el archivo .htaccess que ya tenemos creado, vamos a agregar las siguientes lineas para controlar estas direcciones

# Acortar URL
RewriteRule ^([a-zA-Z]+)/([a-zA-Z0-9-.]+)$ public_html/$1/$2
RewriteRule ^([a-zA-Z]+)/([a-zA-Z]+)/([a-zA-Z0-9-.]+)$ public_html/$1/$2/$3

Esto con el fin de que los usuarios comunes no puedan ver el ingresar directamente a la carpeta public_html desde su navegador si no que mas bien, nos genere una URL mas estilizada y amigable.

Una vez realizado esto, empezamos a crear nuestro API Rest Visual Client (Como lo he llamado).

Para empezar, en el archivo index pegaramos el siguiente codigo


core/public_html/index.html

<!DOCTYPE html>
<html lang="es" ng-app="crud" >
<head>
 <meta charset="UTF-8">
 <title>Rest API Visual Client</title>
 <meta name="viewport" content="width=device-width, initial-scale=1">
 <meta http-equiv="X-UA-Compatible" content="IE=edge">
 <!-- Incluimos el CND de Materialize CSS -->
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">
 <!-- Nuestro CSS personalizado -->
 <link rel="stylesheet" href="css/custom.css">
 <!-- Incluimos el CND de AngularJS -->
 <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
 <!-- Incluimos el CND de Angular Route -->
 <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular-route.min.js"></script>
 <!-- Incluimos el CND de la libreria jQuery -->
 <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
 
</head>
<body ng-controller="mainCtrl">

    <nav>
      <div class="nav-wrapper blue">
        <a href="#!/" class="brand-logo">Friki Bloggeo</a>
        <ul id="nav-mobile" class="right hide-on-med-and-down">
         <li><a href="#!/">Home</a></li>
          <li><a href="#!/usuario">Usuario</a></li>
          
        </ul>
      </div>
    </nav>
          
 <div class="container">
  <!-- ng-view renderiza nuestras views y las muestra en el index -->
  <div ng-view></div>
 </div>
     
 <!-- Nuestro codigo JavaScript personal -->
 <script src="js/custom.js"></script> 
 <!-- Incluimos el CND de Materialize JS -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/js/materialize.min.js"></script>
</body>
</html>

Al igual que el tutorial anterior, no hare mucho enfasis en explicar los codigos ya que los mismos estan documentados. Continuamos.

Bien, hasta ahora tenemos solo nuestra pagina básica donde llamaremos las vistas o templates. Entonces, para continuar vamos a agregar los siguientes códigos a nuestros templates

core/public_html/templates/home.html


<div class="row">
 <div class="col s6">
  <h1>{{message}}</h1>
  <p>{{subtitle}}</p>
 </div>
</div>


core/public_html/templates/usuario.html


<div class="row">
  <div class="col s6">
   <h1>{{message}}</h1>
   <p>{{subtitle}}</p>
  </div>
 </div>

 <div class="row">   
      <div class="col s4">
    <form>
     <div class="row">
      <div class="input-field col s12">
              <input required id="Usuario" type="text" class="validate" ng-value="nuevo.Usuario" ng-model="nuevo.Usuario">
              <label for="Usuario">Usuario</label>
            </div>
     </div>

     <div class="row">
      <div class="input-field col s12">
              <input required id="Clave" type="password" class="validate" ng-value="nuevo.Clave" ng-model="nuevo.Clave">
              <label for="Clave">Clave</label>
            </div>
     </div>

     <div class="row">
      <div class="input-field col s12">
              <input required id="Estatus" type="text" class="validate" ng-value="nuevo.Status" ng-model="nuevo.Status">
              <label for="Estatus">Estatus</label>
            </div>
     </div>

     <div class="row">
      <div class="col-s4" ng-if="nuevo.Id == null">
       <button class="btn waves-effect blue" ng-click="post()">Submit</button>
      </div>

      <div class="col-s4" ng-if="nuevo.Id != null">
       <button class="btn waves-effect blue" ng-click="put(nuevo.Id)">Submit</button>
      </div>
     
     </div>
       </form>


      </div>
      <div class="col s8">
      <input type="text" ng-model="search" placeholder="Buscar">
    


       <table class="highlight responsive-table">
        <thead>
         <tr>
          <th>ID</th>
          <th>Usuario</th>
          <th>Clave</th>
          <th>Estatus</th>
          <th></th>
          <th></th>
         </tr>
        </thead>

        <tbody ng-if="lista.length != null">
         <tr ng-click="get(x.Id)" ng-repeat="x in lista | filter : search">
          <td>{{x.Id}}</td>
          <td>{{x.Usuario}}</td>
          <td>{{x.Clave}}</td>
          <td>{{x.Status}}</td>
          <td><button class="btn btn-small waves-effect red" ng-click="delete(x.Id)">eliminar</button></td>
         </tr>
        </tbody>

        <tbody ng-if="lista.length == null">
         <tr>
          <td colspan="4">No hay datos dispobibles</td>
         </tr>
        </tbody>


       </table>
      </div>
     </div>

Bien, nos restan tan solo dos archivos, el custom.css y custom.js los cuales contienen codigo personalizado, cada uno en su respectivo lenguaje, pero basicamente, custom.css es tu estilo personal asi que no hare ningun codigo para ello, continuamos con el siguiente archivo.
core/public_html/js/custom.js

  var app = angular.module('crud',['ngRoute']);

  // Configuracion de las rutas y sus respectivas plantillas
  app.config(function($routeProvider) {
    $routeProvider
              .when('/', {
                  templateUrl : 'templates/home.html',
                  controller  : 'mainCtrl'
              })
              .when('/usuario', {
                  templateUrl : 'templates/usuario.html',
                  controller  : 'usuario'
              })

            
      });

  // Controlador principal de nuestra pagina
  app.controller('mainCtrl', function($scope) {
         $scope.message = 'Friki Bloggeo';
         $scope.subtitle = "Ejemplo de consumo de un API Rest con AngularJS";
     });


  // Controlador de la entidad Usuario donde se incluiran cada una de sus funciones
  app.controller('usuario', function($scope,$http) {

   $scope.message = 'Friki Bloggeo';
         $scope.subtitle = "Gestion de Usuario";

   // Al cargar la pagina, ejecutamos la funcion get() para rellenar la tabla
      angular.element(document).ready(function () {
       $scope.get("");
       
      });

      // La funcion get() que hace la solicitud para obtener los datos
      $scope.get = function(id){
       // Si la Id esta en blanco, entonces la solicitud es general
       if(id=="") {
        $http.get("api/usuario").then(function (response) {
            $scope.lista = response.data.data;
            Materialize.toast(response.data.statusMessage, 4000);
            
        }, function(response) {
         // Aqui va el codigo en caso de error
        });
    // Si la Id no esta en blanco, la solicitud se hace a un elemento especifico
       } else {
        $http.get("api/usuario/" + id).then(function (response) {
            $scope.nuevo = response.data.data[0];
            Materialize.toast(response.data.statusMessage, 4000);
        }, function(response) {
         // Aqui va el codigo en caso de error
        });
       }
      }

      // La funcion post() que hace la solicitud para publicar un nuevo elemento
      $scope.post = function() {
       $http.post("api/usuario", $scope.nuevo)
        .then(function (response){
               Materialize.toast(response.data.statusMessage, 4000);
               $scope.nuevo = null;
               $scope.get("");
           }, 
           function(response) {
            // Aqui va el codigo en caso de error
           });
      }

      // La funcion put() que hace la solicitud para modificar un elemento especifico
      $scope.put = function(id) {
   
       $http.put("api/usuario/" + id, $scope.nuevo)
        .then(
         function (response){
               Materialize.toast(response.data.statusMessage, 4000);
               $scope.nuevo = null;
               $scope.get("");
           }, 
           function(response) {
            // Aqui va el codigo en caso de error
           });

      }

      // La funcion delete() que hace la solicitud para eliminar un elemeto esepecifico
      $scope.delete = function(id) {
       $http.delete("api/usuario/" + id)
       .then(
           function (response){
             console.log(response);
             Materialize.toast(response.data.statusMessage, 4000);
             $scope.nuevo = null;
             $scope.get("");
           }, 
           function (response){
             // Aqui va el codigo en caso de error
           }
        );
      }

  });

Con esto, ya tenemos listo nuestro cliente visual, y ya podremos empezar a crear cuantas templates o views deseemos para consumir cada tabla especifica. Ademas, dejo el archivo del proyecto (con archivos script locales) para su uso.


Demo Descargar - 1.1 MB

Leer completo

07 enero 2017

Crear un Web Service API Rest con PHP y MySQL

  6 comentarios
23:46



Actualización 12 de Julio de 2018
En vista de que algunas personas me comentaron que no funcionaba este tutorial en Windows, quiero aclarar que cuando lo realice, lo trabaje en Ubuntu, distribución GNU/Linux, por lo tanto, no me di cuenta de los errores que pudiera generar en Windows, esto ha sido corregido, y el código ha sido actualizado para funcionar en cualquier plataforma.
Los API Rest son en la actualidad la nueva manera de trabajar los sistemas web, los mismos se encargan de servir la información que luego sera consumida por algún cliente, usualmente usando alguna librería JavaScript como jQuery o AngularJS.

Los Web Services no son mas que archivos en formato JSON que sirven los datos de una base de datos de una manera mas dinámica y apreciable, estos proporcionan las acciones que desde tiempo remotos trabajan las paginas web dinámicas, el tipo CRUD (Create-Read-Update-Delete) son ahora manejados de una forma mas sencilla usando un Web Services API Rest y los métodos HTTP.

De seguro si, alguna vez al escuchado el típico método POST o Método GET, de los cuales haremos uso en este post, además de los no tan mencionados pero ya bastante conocidos método PUT y DELETE.


Ahora, que necesitamos saber antes de empezar? Presentamos esta información sumamente importante antes de continuar creando el Web Services.

Métodos HTTP

Método Aplicaron Descripción
GET GET /api/usuario Obtiene todos los elementos de la entidad Usuario
GET /api/usuario/1 Obtiene el elemento con Id 1 de la entidad Usuario
POST POST api/usuario Publica un nuevo elemento de la entidad Usuario
PUT PUT api/usuario/1 Modifica el elemento con Id 1 de la entidad Usuario
DELETE DELETE api/usuario/1 Elimina el elemento con Id 1 de la entidad Usuario

Códigos de Cabecera HTTP

Los códigos de cabecera HTTP definen el status actual de una pagina o documento con respecto a la solicitud realizada, de seguro que hemos visto el tipico error 404, en este tutorial haremos uso de los mostrados con posterioridad, sin embargo, te dejo este articulo de Wikipedia que te ayudara a comprender mejor de que trata.

Código Definición Uso o Aplicaron
200 OK Lo usaremos para cuando la solicitud se realiza correctamente, sin importar si su estatus es verdadero o falso
201 Created Se aplicara cuando para cada entidad se cree un nuevo elemento
204 No Content Se usara para cuando la entidad no tiene elementos
404 Not Found Se usara para cuando se solicita un elemento que no existe en la base de datos
405 Method Not Allowed Se usara por defecto para cuando el método solicitado no coincida con la URL o sea un método distinto a GET, POST, PUT y DELETE

Postman o Insomnia

Interfaz principal de Insomnia
Para efectos de prueba haremos uso de una extensión para Chrome como aplicacion, entre ellas podemos mencionar dos herramientas realmente muy amigables como Postman o Insomnia, yo en lo particular, sugiero el uso de Insomia. Recuerden que necesitamos hacer uso de los mismos ya que el navegador por defecto ejecuta solamente el metodo GET, para firefox, puedes hacer uso de la extensión Firebug.

JSON y XML

Los estándares JSON y XML son con peculiaridad los lenguajes usados para servir datos desde un Web Services. Los lenguajes son equivalente, con la única diferencia de que su sintaxis tienen su particularidad. He aqui algunos ejemplos.
Ejemplo de JSON (JavaScript Object Notation)

{
    "statusCode": 200,
    "statusMessage": "OK",
    "data": [
        {
            "Id": "1",
            "Usuario": "admin",
            "Clave": "21232f297a57a5a74389",
            "Status": "1"
        }
    ]
}

Ejemplo de XML (eXtended Markup Language)

<?xml version="1.0" encoding="UTF-8" ?>
<statusCode>200</statusCode>
<statusMessage>OK</statusMessage>
<data>
    <Id>1</Id>
    <Usuario>admin</Usuario>
    <Clave>21232f297a57a5a74389</Clave>
    <Status>1</Status>
</data>

Como podemos ver, de ambas formas se sirve la información correctamente, aunque para efectos del tutorial, haremos uso de JSON específicamente, ya que en resumen, es mucho mas legible que XML, aunque el uso del uno u otro es irrelevante.

No haré mucho énfasis en explicar el código para crear el Web Services ya que el código mismo esta documentado, esta sera el directorio de la aplicaron.

Crea el directorio tal cual ves en la imagen en tu servidor y agrega los siguientes códigos proporcionados a continuación

Antes de empezar, vamos a nuestro PhpMyAdmin para crear nuestra base de datos, en mi caso, la llame Api, y ejecuta el siguiente código






api.sql

CREATE TABLE IF NOT EXISTS `usuario` (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Usuario` varchar(20) NOT NULL,
  `Clave` varchar(20) NOT NULL,
  `Status` tinyint(20) NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

core/config.php

<?php 

 /* 
  * En este archivo se definiran la información de configuracion del API,
  * variables, constantes y funciones requeridas para el resto de los archivos
 */

 // Dirección del servidor de Base de datos
 define("DB_HOST", "localhost");

 // Nombre de usuario de Base de datos
 define("DB_USER", "root");

 // Clave de usuario de Base de datos
 define("DB_PASS", "root");

 // Nombre de la tabla sobre la cual se trabajara
 define("DB_NAME", "api");
?>

core/iModel.php

<?php 
 // Declarar la interfaz 'iModel'
 // Define cada una de las funciones que el model.php debe especificar
 interface iModel
 {
     // GET : Solicitar un elemento
     public function get();
     // POST : Publicar un nuevo elemento
     public function post();
     // PUT: Modificar un elemento
     public function put();
     // DELETE: Eliminar un elemento
     public function delete();
 }
?>

core/db_model.php

<?php 
  // Incluimos el archivo de configuración el cual posee las credenciales de conexión
  include 'config.php';

  // Se crea la clase de conexión y ejecución de consultas
  class db_model {

    // Variable de conexion
    public $conn;

    // La función constructora crea y abre la conexión al momento de instanciar esta clase
    function __construct() {
      $this->conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); // Los parametros de la funcion mysqli() son las constantes previamente declaradas en el archivo config.php
    }

    // Funcion para obtener un array de resultados
    // Solo se usara para las consultas de tipo SELECT
    function get_query($sql) {
      // Lee la cadena SQL recibida y ejecuta la consulta
      $result = $this->conn->query($sql);

      // Hace el rrecorrido por el array de datos y lo guarda en la variable $rows
      while ($rows[] = $result->fetch_assoc());

      // Cierra la consulta
      $result->close();

      // Retorna el resultado obtenido
      return $rows;
    }

    // Funcion para hacer cambios dentro de la base de datos
    // Solo se usara para las consultas de tipo INSERT, UPDATE Y DELETE
    function set_query($sql) {
      // Lee la cadena SQL recibida y ejecuta la consulta
      $result = $this->conn->query($sql);

      // Retorna el resultado
      return $result;

    }

    // La función destructora cierra la conexión previamente abierta en el constructor
    function __destruct() {
      $this->conn->close();
    }
  }
?>

model.php

<?php 
 // Se incluye el archivo de conexion de base de datos
 include 'core/db_model.php';
 // Se incluye la interfaz de Modelo
 include 'core/iModel.php';

 // Se crea la clase que ejecuta llama a las funciones de ejecución para interactuar con la Base de datos
 // Esta clase extiende a la clase db_model en el archivo db_model.php (hereda sus propiedades y metodos)
 // Esta clase implementa la interfaz iModel (Enmascara cada una de las funciones declaradas)
 class generic_class extends db_model implements iModel {
  // Ya que la clase es generica, es importante poseer una variable que permitira identificar con que tabla se trabaja
  public $entity;
  // Almacena la informacion que sera enviada a la Base de datos
  public $data;
  
  // Esta funcion se activara al utilizar el metodo GET
  // Envia por defecto el parametro Id cuyo valor sera 0 hasta que se modifique
  function get($id = 0) {
   /* 
    * Si el valor del parametro Id es igual a 0, se solicitaran todos los elementos
    * ya que no se ha solicitado un elemento especifico 
    */
   if($id == 0) {
    return $this->get_query(sprintf("
     SELECT 
      * 
     FROM 
      %s", 
      $this->entity
      )
     );
   // Si el valor del parametro Id es diferente a 0, se solicitara solo y unicamente el elemento cuyo Id sea igual al parametro recibido
   } else {
    return $this->get_query(sprintf("
     SELECT 
      * 
     FROM 
      %s 
     WHERE 
      Id = %d", 
      $this->entity, 
      $id
      )
     );
   }
  }

  // Esta funcion sera llamada al momento de usar el metodo POST
  function post() {

   return $this->set_query(sprintf("
    INSERT INTO 
     %s
     %s",
     $this->entity,
     $this->data
     
    )
   );

   
  }

  // Esta funcion sera llamada al momento de usar el metodo PUT
  function put() {
   return $this->set_query(sprintf("
    UPDATE 
     %s 
    SET 
     %s 
    WHERE 
     Id = %d", 
     $this->entity,
     $this->data, 
     $this->Id
    )
   );

  }

  // Esta funcion sera llamada al momento de usar el metodo DELETE
  function delete() {
   return $this->set_query(sprintf("
    DELETE FROM 
     %s 
    WHERE 
     Id = %d", 

     $this->entity,
     $this->Id
    )
   );

  }
 }
?>

controller.php

<?php 
 // Permite la conexion desde cualquier origen
 header("Access-Control-Allow-Origin: *");
 // Permite la ejecucion de los metodos
 header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE");  
 // Se incluye el archivo que contiene la clase generica
 include 'model.php';

 // Se toma la URL solicitada y se guarda en un array de datos
 // Por ejemplo si la URL solicitada es http://localhost/api/usuario
 // $_SERVER['REQUEST_URI'] imprime "/api/usuario"
 // La funcion explode() crea un array de la URL de la siguiente forma
 /*
  Array
  (
      [0] => 
      [1] => api
      [2] => usuario
  )
 */
 // Por ejemplo si la URL solicitada es http://localhost/api/usuario/1
 // $_SERVER['REQUEST_URI'] imprime "/api/usuario/1"
 // La funcion explode() crea un array de la URL de la siguiente forma
 /*
  Array
  (
      [0] => 
      [1] => api
      [2] => usuario
      [3] => 1
  )
 */
 // Esto nos ayuda a identificar cuando se esta solicitando la URL general o un elemento especifico
 $array = explode("/", $_SERVER['REQUEST_URI']);

 // Obtener el cuerpo de la solicitud HTTP
 // En nuestro caso, el cuerpo solo sera enviado en peticiones de tipo POST y PUT, en el cual enviaremos el objeto JSON a registrar o modificar
 $bodyRequest = file_get_contents("php://input");

 /* Este ciclo rrecorre el array previamente creado y si hay algun valor en blanco lo elimina del array
    Esto con el fin de controlar cuando la URL se enviar en estilo http://localhost/api/usuario/
    Si bien, se esta haciendo uso del "/" al final, no se esta enviando ningun parametro de Id
    Sin embargo, el array se crea de la siguiente forma
 
  Array
  (
      [0] => 
      [1] => api
      [2] => usuario
      [3] => 
  )

  Ya que la ultima pocision esta vacia, si lo permitieramos asi, nos arrojaria un error ya que no haria la
  Solicitud de manera correcta con un dato que esta vacio, por lo que si la URL es enviada del forma, se asume
  que se esta realizando una solicitud general al estilo http://localhost/api/usuario
 */
 foreach ($array as $key => $value) {
  if(empty($value)) {
   unset($array[$key]);
  }
 }

 /* Analiza la ultima pocision del array creado previamente, si el valor analizado es mayor que 0
    significa que el caracter enviado es un numero, por lo tanto, reconocemos que la solicitud se esta 
    haciendo a un Id especifico de tipo http://localhost/api/usuario/1, pero de no ser mayor que 0, reconocemos que el ultimo elemento del array
    es solo el nombre de la entidad, por lo tanto, reconocemos que se esta haciendo una solicitud general
    de tipo http://localhost/api/usuario
 */
 if(end($array)>0) {
  // De ser el valor numerico, crea dos variables que contienen el Id solicitado y la entidad solicitada
  $id = $array[count($array)];
  $entity = $array[count($array) - 1];
 } else {
  // De ser el valor de tipo string, solo crea la variable de la entidad solicitada
  $entity = $array[count($array)];
 }

 // Variable que guarda la instancia de la clase generica
 $obj = get_obj();

 // Se pasa a la entidad el valor de la entidad con la que actualmente se esta trabajando
 $obj->entity = $entity;

 // Analiza el metodo usado actualmente de los cuatro disponibles: GET, POST, PUT, DELETE
 switch ($_SERVER['REQUEST_METHOD']) {
  case 'GET':
   // Acciones del Metodo GET
   // Si la variable Id existe, solicita al modelo el elemento especifico
   if(isset($id)) {
    $data = $obj->get($id);
   // Si no existe, solicita todos los elementos
   } else {
    $data = $obj->get();
   }
   
   // Elimina el ultimo elemento del array $data, ya que usualmente, suele traer dos elementos, uno con la informacion, y otro NULL el cual no necesitamos
   array_pop($data);

   // Si la cantidad de elementos que trae el array de $data es igual a 0 entra en este condicional
   if(count($data)==0) {
    // Si la variable Id existe pero el array de $data no arroja resultado, significa que elemento no existe
    if(isset($id)) {
     print_json(404, "Not Found", null);
    // Pero si la variable Id existe y no trae $data, ya que no buscamos un elemento especifico, significa que la entidad no tiene elementos que msotrar
    } else {
     print_json(204, "Not Content", null);
    }
   // Si la cantidad de elementos del array de $data es mayor que 0 entra en este condicional
   } else {
    // Imprime la informacion solicitada
    print_json(200, "OK", $data);
   }
   
   break;
  case 'POST':
   // Acciones del Metodo POST
   
   /* Analiza si existe la variable Id, ya que la URL solicita por POST solo puede ser de estilo
      http://localhost/api/usuario no habria por que existir un Id ya que se esta registrando un 
      nuevo elemento y el Id es autogenerado, si el Id no existe, entra en esta condicional */
   if(!isset($id)) {
    // Decodifica el cuerpo de la solicitud y lo guarda en un array de PHP
    $array = json_decode($bodyRequest, true);

    // Renderiza la informacion obtenida que luego sera guardada en la Base de datos
    $obj->data = renderizeData(array_keys($array), array_values($array));

    // Ejecuta la funcion post() que se encuentra en la clase generica
    $data = $obj->post();

    // Si la respuesta es correcta o es igual a true entra en este condicional
    if($data) {
     // Si la Id generada es diferente de 0 se creo el elemento y entra aqui
     if($obj->conn->insert_id != 0) {
      // Se consulta la Id autogenerada para hacer un callBack
      $data = $obj->get($obj->conn->insert_id);

      // Si la variable $data es igual a 0, significa que el elemento no ha sido creado como se suponia
      if(count($data)==0) {
       
       print_json(201, false, null);
      // Si la variable $data es diferente de 0, el elemento ha sido creado y manda la siguiente respuesta
      } else {
       array_pop($data);
       print_json(201, "Created", $data);
      }
      
     // Si el Id generada es igual a 0, el elemento no ha sido creado y manda la siguiente respuesta
     } else {
      print_json(201, false, null);

     }
    // Si la respuesta es false, se supone que el elemento no ha sido registrado, y entra en este condicional
    } else {
     print_json(201, false, null);
    }
   // En tal caso de que exista la variable Id, imprimira el mensaje del que el metodo solicitado no es correcto
   } else {
    print_json(405, "Method Not Allowed", null);
   }
   


   break;
  case 'PUT':
   // Acciones del Metodo PUT
   if(isset($id)) {
    // Consulta primeramente que en realidad exista un elemeto con el Id antes de modificar
    $info = $obj->get($id);
    array_pop($info);

    // Si la info recibida es diferente de 0, el elemento existe, por lo tanto procede a modificar 
    if(count($info)!=0) {
     $array = json_decode($bodyRequest, true);

     $obj->data = renderizeData(array_keys($array), array_values($array));

     $obj->Id = $id;
     $data = $obj->put();

     if($data) {
      $data = $obj->get($id);

      if(count($data)==0) {
       print_json(200, false, null);
      } else {
       array_pop($data);
       print_json(200, "OK", $data);
      }

     } else {
      print_json(200, false, null);
     }
    // Si la info recibida es igual a 0, el elemento no existe y no hay nada para modificar
    } else {
     print_json(404, "Not Found", null);
    }
    
   } else {
    print_json(405, "Method Not Allowed", null);
   }

   break;
  case 'DELETE':
   if(isset($id)) {

    $info = $obj->get($id);

    if(count($info)==0) {
     print_json(404, "Not Found", null);
    } else {
     $obj->Id = $id;
     $data = $obj->delete();

     if($data) {
      array_pop($info);
      if(count($info)==0) {
       print_json(404, "Not Found", null);
      } else {
       print_json(200, "OK", $info);
      }
      
     } else {
      print_json(200, false, null);
     }
    }

   } else {
    print_json(405, "Method Not Allowed", null);
   }
   break;
  
  default:
   // Acciones cuando el metodo no se permite
   // En caso de que el Metodo Solicitado no sea ninguno de los cuatro disponible, envia la siguiente respuesta
   print_json(405, "Method Not Allowed", null);
   break;
 }

 // ---------------------- Funciones controladoras ------------------------------- //

 // Esta funcion crea la instancia de la clase generica y la retorna
 function get_obj() {
  $object = new generic_class;
  return $object;
 }

 // Esta funcion renderiza la informacion que sera enviada a la base de datos
 function renderizeData($keys, $values) { 
  $str = "";
  switch ($_SERVER['REQUEST_METHOD']) {
   case 'POST':
    # code...
     foreach ($keys as $key => $value) {
      if($key == count($keys) - 1) {
       $str = $str . $value . ") VALUES (";

       foreach ($values as $key => $value) {
        if($key == count($values) - 1) {
         $str = $str . "'" . $value . "')";
        } else {
         $str = $str . "'" . $value . "',";
        }
        
       }
      } else {
       if($key == 0) {
        $str = $str . "(" . $value . ",";
       } else {
        $str = $str . $value . ",";
       }
       
      }
     }

     return $str;
    break;
   case 'PUT':
    foreach ($keys as $key => $value) {
     if($key == count($keys) - 1) {
      $str = $str . $value . "='" . $values[$key] . "'"; 
     } else {
      $str = $str . $value . "='" . $values[$key] . "',"; 
     }
    }
    return $str;
    break;
  }
  


 }

 // Esta funcion imprime las respuesta en estilo JSON y establece los estatus de la cebeceras HTTP
 function print_json($status, $mensaje, $data) {
  header("HTTP/1.1 $status $mensaje");
  header("Content-Type: application/json; charset=UTF-8");

  $response['statusCode'] = $status;
  $response['statusMessage'] = $mensaje;
  $response['data'] = $data;

  echo json_encode($response, JSON_PRETTY_PRINT);
 }
?>

.htaccess
Ahora, solo nos queda modificar el archivo .htaccess para poder acceder a las URL sin errores, recuerden que para que esto funcione, el mod_rewrite de Apache debe estar activado, de otra manera, todo el trabajo que has hecho, no serviría de nada.

# Se establece la ruta como el archivo principal o pagina principal
DirectoryIndex public_html/index.html

RewriteEngine On

# Para metodo GET, POST, PUT
RewriteRule ^api/([a-zA-Z]+)$ controller.php
RewriteRule ^api/([a-zA-Z]+)/$ controller.php

# Para metodo GET por Id y metodo DELETE
RewriteRule ^api/([a-zA-Z]+)/([0-9]+)$ controller.php
RewriteRule ^api/([a-zA-Z]+)/([0-9]+)/$ controller.php

# Expresiones regulares
## Alfanumericos | ([a-zA-Z0-9]+)
## Numericos     | ([0-9]+)
## Caracteres    | ([a-zA-Z]+)

Probando el Web Service

Como ya habiamos mencionado, probaremos el Web Services con la Extension Insomnia de Chrome, lo cual nos otorgara los siguiente resultados

GET api/usuario
POST api/usuario
GET api/usuario
GET api/usuario/7
PUT api/usuario/7
DELETE api/usuario/1
Luego de crear este Web Services, puedes crear cuantas tablas en la base de datos desees, y en la URL solo debes cambiar el nombre de la entidad por el nombre de la tabla en la base de datos. Olvidaba mencionarle que el archivo index.html dentro de la carpeta public_html sera la pagina principal del proyecto, en donde crearemos en front-end del mismo y donde se haran las solicitudes HTTP mediante el uso de la Librería jQuery o AngularJS. Estén atentos que hare una segunda parte de este tutorial explicando como consumir un API RestFul desde el cliente con JavaScript.

Demo

Segunda Parte: Consumir un Web Services API Rest con AngularJS

Leer completo

02 enero 2017

Incluir contenido de un archivo JSON con Ajax de JQuery

  7 comentarios
19:18


Ahora que esta de moda las API Rest en todo el medio de la Internet, es necesario innovar, y es que en eso pasamos nuestra vida los programadores, innovando e innovando, y como no ser así, si nuestra carrera en eso consiste. Las API Rest si no han escuchado antes el termino son un método de programacion que despeja por completo la relacion que hay entre un servidor y el cliente, llamese navegador.

Aunque este es un tema mas profundo y del cual hablare mas completo en una entrada futura, hoy solo quiero mencionar y enseñarles como mostrar datos de un Archivo JSON en una pagina común, mediante el uso de Ajax de jQuery para la funcionalidad asincrona que requiere este estilo de programacion.

A continuación, vamos a crear esta simple estructura para empezar nuestro proyecto.

<!DOCTYPE html>
<html lang="es">
<head>
 <meta charset="UTF-8">
 <title>Post</title>
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
 <div class="row">
  <div class="row col s12">
   <div id="post">
   
   </div>
  </div>
 </div>
 <!-- Agregamos la libreria de jQuery -->
 <script   src="https://code.jquery.com/jquery-3.1.1.min.js"   integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="   crossorigin="anonymous"></script>
</body>
</html>

Este es el código básico que necesitamos para empezar, pero aun no tenemos nada, antes que nada debemos añadir la función que cargara las entradas, pero de donde?
Crearemos un archivo JSON, yo lo voy a llamar file.json y tendrá la siguiente estructura

[
 {
  "Titulo" : "Conociendo sobre los efectos de transiciones CSS3",
  "Categoria" : "CSS",
  "Fecha" : "03-01-2017",
  "Hora" : "14:55",
  "Permalink" : "https://frikibloggeo.blogspot.com/2017/01/conociendo-sobre-los-efectos-de.html",
  "Autor" : "Anthony Medina",
  "Contenido" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Temporibus est ipsam nesciunt? Inventore distinctio hic iusto totam officia sed, veniam eum corporis, repellendus repudiandae, debitis voluptas quos, alias neque porro?"
 },
 {
  "Titulo" : "Menu contextual sencillo con HTML5, CSS3 y JavaScript (jQuery)",
  "Categoria" : "JavaScript",
  "Fecha" : "25-12-2015",
  "Hora" : "17:06",
  "Permalink" : "https://frikibloggeo.blogspot.com/2015/12/menu-contextual-sencillo-con-html5-css3.html",
  "Autor" : "Anthony Medina",
  "Contenido" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptas illo esse animi adipisci quia non tempore vero. Voluptatibus modi nesciunt ducimus quibusdam rerum, fugiat quo mollitia facilis, qui quidem veritatis."
 },
 {
  "Titulo" : "Crear tu propio sistema de comentarios con MySQL y PHP",
  "Categoria" : "PHP",
  "Fecha" : "09-06-2016",
  "Hora" : "14:14",
  "Permalink" : "https://frikibloggeo.blogspot.com/2014/06/crear-tu-propio-sistema-de-comentarios.html",
  "Autor" : "Anthony Medina",
  "Contenido" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est quas, ea, recusandae laudantium non dolor ad repellat commodi sint? Fugit, voluptas minus aspernatur iste eius atque aliquid recusandae magnam, sunt."
 }
]

Como ya habrás podido notar, un JSON no es mas que un array estructurado mediante un estandar establecido que se puede leer de una manera mas sencilla que cualquier array convencional en cualquier lenguaje, particularmente, en el ejemplo, tenemos un array de JSON cuya longitud presenta 3 objetos, los cuales contienen cierta información sobre entradas de un blog cualquiera como son el titulo, fecha, hora, etc.

Si bien, mirar y leer datos de un archivo JSON ya es humanamente legible por decirlo de alguna manera, no es todavía suficientemente humanamente legible, por el uso de corchetes, comas, llaves, puntos, comillas, etc. Y es que JSON no ha sido creado para ser leído por humanos, ha sido creado para ser consumido por humanos mediante el uso de herramientas con jQuery, AngularJS, PHP, entre muchos otros frameworks y lenguajes.

Ahora, que hacemos con este pedazo de JSON?

Bien, a la estructura HTML anteriormente creada, justo debajo de la CDN de jQuery, pegamos el siguiente codigo

<script type="text/javascript">
            $(document).ready(function() {
                $.ajax({
                    url: "file.json",
                    type: "GET",
                    async: true,
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    beforeSend: function() {
                        $('#post').html("Cargando post, por favor, espere");
                    },
                    success: function(response) {
                        $('#post').html("");
                        $.each(response, function(i, post) {
                            $('#post').append(
                                  "<h5 class='row col s12'><a href='"+post.Permalink+"'>"+post.Titulo+"</a></h5>"
                                + "<div class='meta row col s6'>"
                                +      "<span class='chip'>Autor: <b>"+post.Autor+"</b></span>"
                                +      "<span class='chip'>Fecha: <b>"+post.Fecha+"</b></span>"
                                +      "<span class='chip'>Hora: <b>"+post.Hora+"</b></span>"
                                +      "<span class='chip'>Categoria: <b>"+post.Categoria+"</b></span>"
                                + "</div>"
                                + "<p class='row col s12'>"+post.Contenido+"</p>"
                            );
                        });
                    }
                });
            });
    </script> 

Donde podemos observar, tenemos un script cuyo ejecución es realizada al iniciar la pagina mediante el uso de la función $('document').ready(); de jQuery, dentro de ella llamamos a la función $.ajax();, la cual nos hace un llamado a nuestro archivo JSON que anteriormente creamos, enviando una serie de parámetros necesarios para la lectura del mismo, una vez hecho el llamado, la función success de jQuery imprime el callback de la manera mas humanamente legible dentro de nuestro documento HTML.

Que tenemos hasta ahora?


Bien, pasamos de tener un documento en blanco a tener un pagina cargada con post, pero un poco aburrido esos estilos predeterminados?

Que tal si añadimos un poco de magia con materialize? Simplemente, agrega este código de la CDN justo antes de cerrar la etiqueta </head>

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">

Eso es todo, mira que bonito se ve ahora, aqui te dejo el codigo completo y una demo del mismo en Plunkr

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Post</title>
    <!-- Agregamos la hoja de Estilos de Materialize (Opcional) -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
    <div class="row">
        <div class="row col s12">
            <div id="post">
            
            </div>
        </div>
    </div>
    <!-- Agregamos la libreria de jQuery -->
    <script   src="https://code.jquery.com/jquery-3.1.1.min.js"   integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="   crossorigin="anonymous"></script>
    <!-- Agregamos la libreria de Materialize (Opcional) -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/js/materialize.min.js"></script>
    <!-- Script que se ejecuta al iniciar la pagina y carga los posts a la misma -->
    <script type="text/javascript">
            $(document).ready(function() {
                $.ajax({
                    url: "file.json",
                    type: "GET",
                    async: true,
                    contentType: "application/json; charset=utf-8",
                    dataType: "json",
                    beforeSend: function() {
                        $('#post').html("Cargando post, por favor, espere");
                    },
                    success: function(response) {
                        $('#post').html("");
                        $.each(response, function(i, post) {
                            $('#post').append(
                                  "<h5 class='row col s12'><a href='"+post.Permalink+"'>"+post.Titulo+"</a></h5>"
                                + "<div class='meta row col s6'>"
                                +      "<span class='chip waves-effect'>Autor: <b>"+post.Autor+"</b></span>"
                                +      "<span class='chip waves-effect'>Fecha: <b>"+post.Fecha+"</b></span>"
                                +      "<span class='chip waves-effect'>Hora: <b>"+post.Hora+"</b></span>"
                                +      "<span class='chip waves-effect'>Categoria: <b>"+post.Categoria+"</b></span>"
                                + "</div>"
                                + "<p class='row col s12'>"+post.Contenido+"</p>"
                            );
                        });
                    }
                });
            });
    </script>
</body>
</html>

DEMO

Leer completo

Conociendo sobre los efectos de transiciones CSS3

  4 comentarios
13:57


Las transiciones CSS proporcionan una forma de animar los cambios de las propiedades CSS, en lugar de que los cambios surtan efecto de manera instantánea. Por ejemplo, si cambias el color de un elemento de blanco a negro, normalmente el cambio es instantáneo. Al habilitar las transiciones CSS, el cambio sucede en un intervalo de tiempo que puedes especificar, siguiendo una curva de aceleración que puedes personalizar.

Para aplicar una transición a un elemento tenemos que utilizar la propiedad transition. Aunque está propiedad ya la podemos considerar un estándar, es recomendable el uso de prefijos para que funcione en versiones antiguas de los navegadores. Antes de comenzar, explicaremos brevemenete los navegadores que sorportan este efecto actualmente


Propiedad
transition
26.0 4.0 -webkit- 10.0 16.0 4.0 -moz- 6.1 3.1 -webkit- 12.1 10.5 -o-
transition-delay
26.0 4.0 -webkit- 10.0 16.0 4.0 -moz- 6.1 3.1 -webkit- 12.1 10.5 -o-
transition-duration
26.0 4.0 -webkit- 10.0 16.0 4.0 -moz- 6.1 3.1 -webkit- 12.1 10.5 -o-
transition-property
26.0 4.0 -webkit- 10.0 16.0 4.0 -moz- 6.1 3.1 -webkit- 12.1 10.5 -o-
transition-timing-function
26.0 4.0 -webkit- 10.0 16.0 4.0 -moz- 6.1 3.1 -webkit- 12.1 10.5 -o-


Como usar las transiciones de CSS3

Para crear un efecto de transición de CSS3 debes especificar dos cosas
  • La propiedad CSS
  • La duración de efecto
Nota: Si no agregas la duración, el efecto no tendrá lugar ya que el valor por defecto es 0

Ejemplo:

Tenemos un pequeño div color rojo cuyo tamaño original es 100px y al poner el mouse sobre el, el evento hover amplia su propiedad width a 300px



#box {
    width: 100px;
    height: 100px;
    background: red;
    transition: width 2s;
   -o-transition: width 2s; /* Opera */
   -moz-transition: width 2s; /* Mozilla */
   -webkit-transition: width 2s; /* Safari y Chrome */
}
#box:hover {
    width: 300px;
}


Usar una sola propiedad no es limitante, por lo que en el siguiente ejemplo, se puede observar como se aplica dos efectos por separado a un mismo div.



#box {
    width: 100px;
    height: 100px;
    background: red;
    transition: width 2s;
   -o-transition: width 2s, height 4s; /* Opera */
   -moz-transition: width 2s, height 4s; /* Mozilla */
   -webkit-transition: width 2s, height 4s; /* Safari y Chrome */
}
#box:hover {
    width: 300px;
    height:300px;
}


Curva de Transición


Además de la propiedad y la duración, podemos añadir la curva que tendrá la transición al momento de hacer el efecto, para esto contamos con la propiedad transition-timing-function

Dicha propiedad cuenta con los siguientes valores:
  • ease - Transición lenta al inicio, un poco mas rápido a mediados de la misma, y finaliza con lentitud
  • #box {
        width: 100px;
        height: 100px;
        background: red;
        transition: width ease 2s;
       -o-transition: width ease 2s; /* Opera */
       -moz-transition: width ease 2s; /* Mozilla */
       -webkit-transition: width ease 2s;
        
    }
    #box:hover {
        width: 300px;
    }
  • linear - Transición con efecto unifome
  • #box {
        width: 100px;
        height: 100px;
        background: red;
        transition: width linear 2s;
       -o-transition: width linear 2s; /* Opera */
       -moz-transition: width linear 2s; /* Mozilla */
       -webkit-transition: width linear 2s;
        
    }
    #box:hover {
        width: 300px;
    }
  • ease-in - Transición lenta solo al inicio 
  • #box {
        width: 100px;
        height: 100px;
        background: red;
        transition: width ease-in 2s;
       -o-transition: width ease-in 2s; /* Opera */
       -moz-transition: width ease-in 2s; /* Mozilla */
       -webkit-transition: width ease-in 2s;
        
    }
    #box:hover {
        width: 300px;
    }
  • ease-out - Transición lenta solo al final 
  • #box {
        width: 100px;
        height: 100px;
        background: red;
        transition: width ease-out 2s;
       -o-transition: width ease-out 2s; /* Opera */
       -moz-transition: width ease-out 2s; /* Mozilla */
       -webkit-transition: width ease-out 2s;
        
    }
    #box:hover {
        width: 300px;
    }
  • ease-in-out - Transición con inicio y final lento 
  • #box {
        width: 100px;
        height: 100px;
        background: red;
        transition: width ease-in-out 2s;
       -o-transition: width ease-in-out 2s; /* Opera */
       -moz-transition: width ease-in-out 2s; /* Mozilla */
       -webkit-transition: width ease-in-out 2s;
        
    }
    #box:hover {
        width: 300px;
    }
  • cubic-bezier(n,n,n,n) - El usuario define los valores de la curva


Transición + Transformación (transition + tranform)


#box {
    width: 100px;
    height: 100px;
    background: red;
    transition: width 2s, height 2s, transform 2s;
   -o-transition: width 2s, height 2s, -o-transform 2s; /* Opera */
   -moz-transition: width 2s, height 2s, -moz-transform 2s; /* Mozilla */
   -webkit-transition: width 2s, height 2s, -webkit-transform 2s; /* Safari y Chorme */
    
}
#box:hover {
    width: 300px;
    height: 300px;
    -o-transform: rotate(180deg); /* Opera */
    -moz-transform: rotate(180deg); /* Safari y Chrome */
    -webkit-transform: rotate(180deg); /* Safari y Chrome */
    transform: rotate(180deg);
}

Dar un útil uso de estas transiciones que nos otorga CSS3 depende de ti, esto es tan solo un preámbulo para obtener conocimientos de como usar la misma, pero tu creatividad te puede llevar al limite de lo imposible.

Ref: w3School.com

Leer completo

24 julio 2016

El Patrón Modelo-Vista-Controlador y Programación Orientada a Objetos en PHP Profesional

  4 comentarios
11:19


Para los que constantemente han visitado este blog entrada tras entrada sabrán del gran esfuerzo que hago por volver aquí, quizás no con tanta frecuencia como quisiera porque entre el trabajo y los estudios, no me queda mucho tiempo al día, mas sin embargo, no me olvido de lo que me gusta hacer realmente y de compartir mis conocimientos, y todo lo que por este medio se pueda expresar para los seguidores de Friki.

Disculpen lo confianzudo, puesto a que sigo otros blog de la misma temática que el mio y son un poco mas técnicos y mas al punto de lo que desean exponer, mas sin embargo, a mi persona, le aburre ser técnico y todo cuadriculado, y solo espero crear un 'clima' de confianza usuario-administrador si se pudiera llamar de alguna manera con el fin de que al entrar al blog se sientan motivados a seguir aprendiendo de una manera como tal, explicativa, comprensible usando el tipico metodo de explicar las cosas con manzanas y peras


Continuando con lo que vengo a exponer hoy, que ya veniamos hablando en anteriores entradas las cuales invito a ver, para que tengan una idea de lo que va a tratar este tutorial


-Programacion Orientada a Objetos y la estructura Modelo-Vista-Controlador en PHP Básico - Introducción
-Programacion Orientada a Objetos y la estructura Modelo-Vista-Controlador en PHP Básico - Programando

No hace falta adentrarnos mas en explicaciones de lo que es el patrón MVC y la POO en PHP, así que vamos al grano, en los tutoriales anteriores explique los mismos, y realice un ejercicio práctico más sin embargo, fue algo sumamente básico y que aun contiene ciertos elementos que pueden ser mejorados separando aun mas la lógica del negocio de la interfaz, y optimizando el uso de los códigos de cada lenguaje de programación, el tutorial a continuación, representa la programación en capas con MVC y POO en PHP.

Como ya sabemos, el patrón MVC (Modelo-Vista-Controlador) es una arquitectura de software, un modo de programar cualquier tipo de programa y páginas web que separa la lógica del negocio de la interfaz del usuario, cumpliendo cada capa una función específica

- Modelo: representa la lógica de negocios. Es el encargado de acceder de forma directa a los datos actuando como “intermediario” con la base de datos.

- Vista: es la encargada de mostrar la información al usuario de forma gráfica y “humanamente legible”.

- Controlador: es el intermediario entre la vista y el modelo. Es quien controla las interacciones del usuario solicitando los datos al modelo y entregándolos a la vista para que ésta, lo presente al usuario, de forma “humanamente legible”.

¿Cómo funciona el MVC?



El funcionamiento básico del patrón MVC, puede resumirse en:

• El usuario realiza una petición
• El controlador captura el evento (puede hacerlo mediante un manejador de eventos – handler -, por ejemplo)
• Hace la llamada al modelo/modelos correspondientes (por ejemplo, mediante una llamada de retorno – callback -) efectuando las modificaciones pertinentes sobre el modelo
• El modelo será el encargado de interactuar con la base de datos, ya sea en forma directa, con una capa de abstracción para ello, un Web Service, etc. y retornará esta información al controlador
• El controlador recibe la información y la envía a la vista
• La vista, procesa esta información pudiendo hacerlo desde el enfoque que veremos en este libro, creando una capa de abstracción para la lógica (quien se encargará de procesar los datos) y otra para el diseño de la interfaz gráfica o GUI. La lógica de la vista, una vez procesados los datos, los “acomodará” en base al diseño de la GUI - layout – y los entregará al usuario de forma “humanamente legible”.

Ahora que ya sabemos esto, es el momento de programar. Se creara un registro de usuarios que contara con el registro de datos así como la modificación, consulta y eliminacion del usuario, en pocas palabras, la gestión de usuario.

Para comenzar, vamos a crear en nuestro servidor, ya sea local o externo, una carpeta llamada 'mvc' en el cual, crearemos los siguientes directorios

core: el propósito de este directorio, será almacenar todas aquellas clases, helpers, utils, decorators, etc, que puedan ser reutilizados en otros módulos de la aplicación (aquí solo crearemos el de usuarios), es decir, todo aquello que pueda ser considerado “núcleo” del sistema.

site_media: será el directorio exclusivo para nuestros diseñadores. En él, organizadamente, nuestros diseñadores podrán trabajar libremente, creando los archivos estáticos que la aplicación requiera.

usuarios: este, será el directorio de nuestro módulo de usuarios. En él, almacenaremos los archivos correspondientes al modelo, la lógica de la vista y el controlador, exclusivos del módulo de usuarios.

El Modelo


Archivo ./core/db_abstract_model.php

Este archivo contiene todos los métodos de consulta los cuales nos permitirán conectarnos con la base de datos y extraer los datos, valga la redundancia que necesitemos según nuestra consulta SQL

<?php
abstract class DBAbstractModel {
  private static $db_host = 'localhost';
  private static $db_user = 'root';
  private static $db_pass = 'root';
  protected $db_name = 'mvc';
  protected $query;
  protected $rows = array();
  private $conn;
  public $mensaje = 'Hecho';

  # métodos abstractos para ABM de clases que hereden
  abstract protected function get();
  abstract protected function set();
  abstract protected function edit();
  abstract protected function delete();

  # los siguientes métodos pueden definirse con exactitud y
  # no son abstractos

  # Conectar a la base de datos
 private function open_connection() {
   $this->conn = new mysqli(self::$db_host, self::$db_user,
   self::$db_pass, $this->db_name);
 }

 # Desconectar la base de datos
 private function close_connection() {
  $this->conn->close();
 }

 # Ejecutar un query simple del tipo INSERT, DELETE, UPDATE
 protected function execute_single_query() {
   if($_POST) {
    $this->open_connection();
    $this->conn->query($this->query);
    $this->close_connection();
   } else {
    $this->mensaje = 'Metodo no permitido';
   }
 }

 # Traer resultados de una consulta en un Array
 protected function get_results_from_query() {
   $this->open_connection();
   $result = $this->conn->query($this->query);
   while ($this->rows[] = $result->fetch_assoc());
   $result->close();
   $this->close_connection();
   array_pop($this->rows);
 }
}
?>


Archivo ./usuarios/model.php

Este archivo representa la clase de la entidad usuario, contiene todos los métodos y propiedades del usuario así como las consultas SQL que nos servirán para gestionar sus datos.

<?php
# Importar modelo de abstracción de base de datos
require_once('../core/db_abstract_model.php');
class Usuario extends DBAbstractModel {
############################### PROPIEDADES ################################
 public $nombre;
 public $apellido;
 public $email;
 private $clave;
 protected $id;

################################# MÉTODOS ##################################
 # Traer datos de un usuario
 public function get($user_email='') {
  if($user_email != '') {
   $this->query = "
   SELECT id, nombre, apellido, email, clave
   FROM usuarios
   WHERE email = '$user_email'
   ";
   $this->get_results_from_query();
  }
  if(count($this->rows) == 1) {
   foreach ($this->rows[0] as $propiedad=>$valor) {
    $this->$propiedad = $valor;
   }
   $this->mensaje = 'Usuario encontrado';
  } else {
   $this->mensaje = 'Usuario no encontrado';
  }
 }

 # Crear un nuevo usuario
 public function set($user_data=array()) {
  if(array_key_exists('email', $user_data)) {

   $this->get($user_data['email']);

   if($user_data['email'] != $this->email) {
    foreach ($user_data as $campo=>$valor) {
     $$campo = $valor;
     }

   $this->query = "
    INSERT INTO usuarios
    (nombre, apellido, email, clave)
    VALUES
    ('$nombre', '$apellido', '$email', '$clave')
    ";
    $this->execute_single_query();
    $this->mensaje = 'Usuario agregado exitosamente';
   } else {
    $this->mensaje = 'El usuario ya existe';
   }
  } else {
   $this->mensaje = 'No se ha agregado al usuario';
  }
 }


 # Modificar un usuario
 public function edit($user_data=array()) {
  foreach ($user_data as $campo=>$valor) {
   $$campo = $valor;
  }

  $this->query = "
  UPDATE usuarios
  SET nombre='$nombre',
  apellido='$apellido'
  WHERE email = '$email'
  ";
  $this->execute_single_query();
  $this->mensaje = 'Usuario modificado';
 }


 # Eliminar un usuario
 public function delete($user_email='') {
  $this->query = "
  DELETE FROM usuarios
  WHERE email = '$user_email'
  ";
  $this->execute_single_query();
  $this->mensaje = 'Usuario eliminado';
 }

 # Método constructor
 function __construct() {
  $this->db_name = 'mvc';
 }

 # Método destructor del objeto
 function __destruct() {
  unset($this);
 }
}
?>


La Vista

Archivo ./site_media/html/usuario_template.html

Esta representa la plantilla HTML general donde se mostraran cada uno de los formularios solicitados por el usuario así como mensajes de confirmacion, esta sera la interfaz mostrada al usuario, lo unico con lo que el usuario podrá interactuar en este caso.


<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html lang="es">

 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <link rel="stylesheet" type="text/css" href="/mvc/site_media/css/base_template.css">
  <title>ABM de Usuarios: {subtitulo}</title>
 </head>

 <body>

  <div id="cabecera">
    <h1>Administrador de usuarios</h1>
    <h2>{subtitulo}</h2>
  </div>

  <div id="menu">
    <a href="/mvc/{VIEW_SET_USER}" title="Nuevo usuario">Agregar usuario</a>
    <a href="/mvc/{VIEW_GET_USER}" title="Buscar usuario">Buscar/editar usuario</a>
    <a href="/mvc/{VIEW_DELETE_USER}" title="Borrar usuario">Borrar usuario</a>
  </div>

  <div id="mensaje">
    {mensaje}
  </div>

  <div id="formulario">
   {formulario}
  </div>
  
 </body>
</html>


Archivo ./site_media/html/usuario_agregar.html

Este y los siguientes 3 archivos corresponden a los formularios que solicitara el usuario para realizar una función cualquiera, estos formularios serán mostrados en la plantilla general sustituyendo la linea {formulario} por el código presente en el archivo según sea el caso

<form id="alta_usuario" action="{SET}" method="POST">

  <div class="item_requerid">E-mail</div>
  <div class="form_requerid"><input type="text" name="email" id="email"></div>

  <div class="item_requerid">Clave</div>
  <div class="form_requerid"><input type="password" name="clave" id="clave"></div>

  <div class="item_requerid">Nombre</div>
  <div class="form_requerid"><input type="text" name="nombre" id="nombre"></div>

  <div class="item_requerid">Apellido</div>
  <div class="form_requerid"><input type="text" name="apellido" id="apellido"></div>
  
  <div class="form_button"><input type="submit" name="enviar" id="enviar" value="Agregar"></div>
  
</form>


Archivo ./site_media/html/usuario_borrar.html
(formulario para eliminar un usuario identificado por su e-mail)

<form id="alta_usuario" action="{DELETE}" method="POST">

  <div class="item_requerid">E-mail</div>

  <div class="form_requerid"><input type="text" name="email" id="email"></div>

  <div class="form_button"><input type="submit" name="enviar" id="enviar" value="Eliminar"></div>

</form>


Archivo ./site_media/html/usuario_buscar.html
(archivo que permite buscar un usuario por su e-mail para luego mostrarlo en el formulario de edición, ya sea para editarlo o solo para ver sus datos)

<form id="alta_usuario" action="{GET}" method="GET">

  <div class="item_requerid">E-mail</div>
  <div class="form_requerid"><input type="text" name="email" id="email"></div>
 
  <div class="form_button"><input type="submit" id="enviar" value="Buscar"></div>
  
</form>


Archivo ./site_media/html/usuario_modificar.html
(archivo para visualizar los datos de un usuario y modificarlos)

<form id="alta_usuario" action="{EDIT}" method="POST">

  <div class="item_requerid">E-mail</div>
  <div class="form_requerid"><input type="text" name="email" id="email" value="{email}" readonly></div>
  
  <div class="item_requerid">Nombre</div>
  <div class="form_requerid"><input type="text" name="nombre" id="nombre" value="{nombre}"></div>
  
  <div class="item_requerid">Apellido</div>
  <div class="form_requerid"><input type="text" name="apellido" id="apellido" value="{apellido}"></div>
  
  <div class="form_button"><input type="submit" name="enviar" id="enviar" value="Guardar"></div>

</form>


Archivo ./site_media/css/base_template.css
(hoja de estilos en cascada)

body {
 margin: 0px 0px 0px 0px;
 background-color: #ffffff;
 color: #666666;
 font-family: sans-serif;
 font-size: 12px;

}
#cabecera {
 padding: 6px 6px 8px 6px;
 background-color: #6699FF;
 color: #ffffff;
}
#cabecera h1, h2 {
 margin: 0px 0px 0px 0px;
}
#menu {
 background-color: #000000;
 color: #ffffff;
 padding: 4px 0px 4px 0px;
}
#menu a {
 width: 100px;
 background-color: #000000;
 padding: 4px 8px 4px 8px;
 color: #f4f4f4;
 text-decoration: none;
 font-size: 13px;
 font-weight: bold;
}
#menu a:hover {
 background-color: #6699FF;
 color: #ffffff;
}
#mensaje {
 margin: 20px;
 border: 1px solid #990033;
 background-color: #f4f4f4;
 padding: 8px;
 color: #990033;
 font-size: 15px;
 font-weight: bold;
 text-align: justify;
}
#formulario {
 margin: 0px 20px 10px 20px;
 border: 1px solid #c0c0c0;
 background-color: #f9f9f9;
 padding: 8px;
 text-align: left;
}
.item_requerid {
 width: 150px;
 height: 22px;
 padding-right: 4px;
 font-weight: bold;
 float: left;
 clear: left;
 text-align: right;
}

.form_requerid {
 height: 22px;
 float: left;
 clear: right;
}
input {
 border: 1px solid #c0c0c0;
}
.form_button {
 padding-top: 15px;
 margin-left: 154px;
 clear: both;
}
#enviar {
 padding: 5px 10px 5px 10px;
 border: 2px solid #ffffff;
 background-color: #000000;
 color: #ffffff;
 font-family: sans-serif;
 font-size: 14px;
 font-weight: bold;
 cursor: pointer;
}
#enviar:hover {
 background-color: #6699FF;
}

Archivo ./usuarios/view.php
(Lógica de la vista)

<?php
$diccionario = array(

 'subtitle'=>array(
  VIEW_SET_USER=>'Crear un nuevo usuario',
  VIEW_GET_USER=>'Buscar usuario',
  VIEW_DELETE_USER=>'Eliminar un usuario',
  VIEW_EDIT_USER=>'Modificar usuario'
 ),

 'links_menu'=>array(
  'VIEW_SET_USER'=>MODULO.VIEW_SET_USER.'/',
  'VIEW_GET_USER'=>MODULO.VIEW_GET_USER.'/',
  'VIEW_EDIT_USER'=>MODULO.VIEW_EDIT_USER.'/',
  'VIEW_DELETE_USER'=>MODULO.VIEW_DELETE_USER.'/'
 ),

 'form_actions'=>array(
  'SET'=>'/mvc/'.MODULO.SET_USER.'/',
  'GET'=>'/mvc/'.MODULO.GET_USER.'/',
  'DELETE'=>'/mvc/'.MODULO.DELETE_USER.'/',
  'EDIT'=>'/mvc/'.MODULO.EDIT_USER.'/'
 )
);

function get_template($form='get') {
 $file = '../site_media/html/usuario_'.$form.'.html';
  $template = file_get_contents($file);
  return $template;
}

function render_dinamic_data($html, $data) {
  foreach ($data as $clave=>$valor) {
   $html = str_replace('{'.$clave.'}', $valor, $html);
  }
  return $html;
}

function retornar_vista($vista, $data=array()) {
 global $diccionario;
 $html = get_template('template');
 $html = str_replace('{subtitulo}', $diccionario['subtitle'][$vista],
 $html);
 $html = str_replace('{formulario}', get_template($vista), $html);
 $html = render_dinamic_data($html, $diccionario['form_actions']);
 $html = render_dinamic_data($html, $diccionario['links_menu']);
 $html = render_dinamic_data($html, $data);
 // render {mensaje}
 if(array_key_exists('nombre', $data) && array_key_exists('apellido', $data) && $vista==VIEW_EDIT_USER) {
  $mensaje = 'Editar usuario '.$data['nombre'].' '.$data['apellido'];
 } else {
  if(array_key_exists('mensaje', $data)) {
   $mensaje = $data['mensaje'];
  } else {
   $mensaje = 'Datos del usuario:';
  }
 }
 $html = str_replace('{mensaje}', $mensaje, $html);
 print $html;
}
?>

El Controlador


Archivo ./usuarios/controller.php


<?php
require_once('constants.php');
require_once('model.php');
require_once('view.php');


function handler() {
  $event = VIEW_GET_USER;
  $uri = $_SERVER['REQUEST_URI'];

  $peticiones = array(SET_USER, GET_USER, DELETE_USER, EDIT_USER,VIEW_SET_USER, VIEW_GET_USER, VIEW_DELETE_USER,VIEW_EDIT_USER);

  foreach ($peticiones as $peticion) {
   $uri_peticion = MODULO.$peticion.'/';

   if(strpos($uri, $uri_peticion) == true ) {
    $event = $peticion;
   }
  }

  $user_data = helper_user_data();

  $usuario = set_obj();

  switch ($event) {
   case SET_USER:
    $usuario->set($user_data);
    $data = array('mensaje'=>$usuario->mensaje);
    retornar_vista(VIEW_SET_USER, $data);
   break;
   case GET_USER:
    $usuario->get($user_data);
    $data = array(
     'nombre'=>$usuario->nombre,
     'apellido'=>$usuario->apellido,
     'email'=>$usuario->email
    );
    retornar_vista(VIEW_EDIT_USER, $data);
   break;
   case DELETE_USER:
    $usuario->delete($user_data['email']);
    $data = array('mensaje'=>$usuario->mensaje);
    retornar_vista(VIEW_DELETE_USER, $data);
   break;
   case EDIT_USER:
    $usuario->edit($user_data);
    $data = array('mensaje'=>$usuario->mensaje);
    retornar_vista(VIEW_GET_USER, $data);
   break;
   default:
    retornar_vista($event);
  }
}


function set_obj() {
  $obj = new Usuario();
  return $obj;
}


function helper_user_data() {
  $user_data = array();
  if($_POST) {
   if(array_key_exists('nombre', $_POST)) {
    $user_data['nombre'] = $_POST['nombre'];
   }
   if(array_key_exists('apellido', $_POST)) {
    $user_data['apellido'] = $_POST['apellido'];
   }
   if(array_key_exists('email', $_POST)) {
    $user_data['email'] = $_POST['email'];
   }
   if(array_key_exists('clave', $_POST)) {
    $user_data['clave'] = $_POST['clave'];
   }
  } else if($_GET) {
   if(array_key_exists('email', $_GET)) {
    $user_data = $_GET['email'];
   }

  }
  return $user_data;
}

handler();

?>

Archivo ./usuarios/constants.php

Este archivo contiene todas las constantes del módulo y no se le debería agregar ningún dato que no fuese una constante.

<?php
const MODULO = 'usuarios/';

# controladores
const SET_USER = 'set';
const GET_USER = 'get';
const DELETE_USER = 'delete';
const EDIT_USER = 'edit';

# vistas
const VIEW_SET_USER = 'agregar';
const VIEW_GET_USER = 'buscar';
const VIEW_DELETE_USER = 'borrar';
const VIEW_EDIT_USER = 'modificar';
?>

Archivo Adiciones

Script SQL

En el DBMS phpMyAdmin o cualquier otro, vamos a crear una base de datos cuyo nombre será 'mvc' en la cual crearemos la siguiente tabla

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";

CREATE TABLE IF NOT EXISTS `usuarios` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `nombre` varchar(20) NOT NULL,
  `apellido` varchar(20) NOT NULL,
  `email` varchar(50) NOT NULL,
  `clave` varchar(8) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;


Archivo ./.htaccess

 Este archivo debe estar ubicado en la raíz del sistema, es decir, dentro de la carpeta 'mvc' directamente, vas a crear dicho archivo el cual contendrá la siguiente lineas

RewriteEngine on
RewriteRule ^usuarios/ usuarios/controller.php

Es importante tener el mod_rewrite de apache activo para que pueda funcionar el archivo .htaccess y las direcciones URL del sistema como tal, pues, si este no esta activo, no va a funcionar correctamente el flujo de los archivos ni de la información

Aqui dejo un tutorial bastante bueno en tal caso de que no lo tengas activo

Activar módulo mod_rewrite de Apache en Linux y Windows

Este tema aun tiene mucha tela que cortar, mas sin embargo, me limitare a responder solamente las dudas en los comentarios con el fin de no hacer este tutorial mas largo. A programar chicos!

 ref: Bahit, Eugenia. MVC y POO en PHP.

Leer completo