Preparación previa

Una vez configurado nuestro entorno de desarrollo de acuerdo a las especificaciones de la web oficial de Laravel, ya podemos abrir nuestra línea de comandos y crear una nueva copia del framework:

laravel new porn-site-demo

Este comando creará una nueva copia de la última versión del framework en la carpeta porn-site-demo. A continuación abre dicha carpeta en tu explorador de ficheros o IDE favorito y pasemos a analizar lo que se cuece ahí dentro.

La estructura de directorios de Laravel

Echemos un vistazo y aprendamos un poco sobre la estructura de carpetas de nuestro nuevo proyecto Laravel. Es posible que tu estructura sea un poco distinta a la mía si estás leyendo esto muchos meses o años después de que lo escribiese. Asumiendo que estás trabajando con una versión de Laravel entorno a la 5.2, deberías encontrar los siguientes directorios en el interior de tu carpeta raíz porn-site-demo (las carpetas importantes van en negrita):

Carpeta Descripción
/ La raíz de nuestro proyecto, aquí encontraremos algunos ficheros importantes, como por ejemplo: el fichero .env (de environment variables) que nos permite asignar valores a algunas de las variables de configuración más importantes, o el fichero composer.json que nos permite, entre otras cosas, instalar nuevas dependencias (algo así como plugins) para nuestro proyecto
app/ Posiblemente la carpeta donde pasaremos la mayor parte de nuestro tiempo escribiendo código. En app/ escribiremos nuestros models, routes, middleware, controllers… que, como veremos más adelante, son la quintaesencia del back end.
bootstrap/ Aquí encontramos procedimientos relacionados con el inicio e instanciación de nuestra aplicación. Cuestiones que es improbable que toquemos durante nuestros primeros proyectos.
config/ Como su nombre indica, aquí encontramos la mayor parte de los arrays que contienen información sobre la configuración de nuestra aplicación y sus dependencias.
database/ Dentro de esta carpeta escribiremos bastante durante los primeros días de desarrollo del back end, puesto que aquí es donde crearemos las migrations y los seeders (explicados más abajo), es decir, donde se cocina la base de datos de nuestra aplicación.
public/ Como su nombre indica, esta carpeta está destinada a aquel contenido que será accesible al “público”, lo que en el mundo web significa material gráfico, hojas de estilo, robots.txt, etc. Si prestas atención verás que aquí dentro hay un fichero index.php, gracias al cual ocurre gran parte de la magia de Laravel. De hecho, de cara a servir tu web o aplicación, ésta es la carpeta raíz de tu proyecto y, en cierto modo, la única a la que han de tener acceso libre tus usuarios (hopefully).
resources/ Aquí es donde un desarrollador de front end se tiene que pasar la mitad mejor de su vida. En resources se encuentran los subdirectorios relacionadas con las views (lo que ve el usuario), el subdirectorio lang con los ficheros que contienen el texto de nuestra web y sus traducciones, así como el subdirectorio assets, donde puedes escribir los ficheros less o sass que, para quien no lo sepa, se emplean para generar las hojas de estilo CSS de toda la vida (aunque, si eres un troglodita, las puedes escribir a pelo en public/).
storage/ En esta carpeta ocurre parte de la magia del framework y, si todo va bien, no la visitaremos demasiado. Si algo no va tan bien como esperamos, en cambio, es bueno saber que aquí dentro está el subdirectorio con los logs donde se guardan los errores y otros volcados útiles de información. Muchos laravelitas utilizan este directorio para almacenar datos que quieren servir en el futuro pero cuyo acceso quieren restringir.
tests/ ¿Eres o te sientes un software engineer por casualidad? Pues ya sabes dónde hay que escribir y documentar el testing de tu código. Como esto es un ejercicio, un mero proyecto educativo, vamos a pasar olímpicamente de la parte aburri… digo, profesional y necesaria, de tu trabajo como programador.
vendor/ Aquí es donde se instalan y guardan todas las dependencias de Laravel y de terceros. En principio no deberías modificar nunca el código que hay por aquí dentro, puesto que hay formas alternativas y mucho más transparentes de alterar el uso que le da tu aplicación a estas dependencias. Eso sí, ten muy presente esta carpeta a la hora de buscar referencias (“¿qué hace tal o cual cosa?”)” o, como mínimo, que lo tenga presente tu IDE.

¿Qué es eso de las migrations, models y seeders?

Cuando se trabaja con equipos de desarrolladores de varias personas es necesario que en todo momento todos tengan la misma estructura de base de datos. Para lograrlo Laravel utiliza un sistema de migrations, que no es más que una forma de crear y modificar tablas de forma secuencial. Una migration es, por tanto, una acción sobre la base de datos: crear, eliminar, corregir, modificar algo… que una vez implementada no debería ser rectificada, salvo por una nueva migration. Por ejemplo: si yo creo una tabla dummy con una columna useless y tras poner los cambios en producción me doy cuenta de que esa columna no es necesaria, no debería modificar la migration que creó la columna, sino crear otra migration para eliminarla.

Los modelos o models son clases (como las de cualquier otro lenguaje orientado a objetos) que están relacionadas con una tabla de la base de datos. Por ejemplo: si tengo una tabla bicicletas y otra tabla pedales, normalmente crearemos a continuación los modelos Bicicleta y Pedal, que harán referencia a las tablas y a las relaciones que existan entre ellas. Estos modelos nos permitirán trabajar con las bases de datos de forma muy ágil.

Finalmente, los seeders no son más que acciones que nos permiten rellenar las tablas con datos “falsos”. No es estrictamente necesario crear seeders para trabajar sobre un proyecto, pero es extremadamente recomendable tanto para ahorrarte de introducir los datos manualmente, como para prever y emular toda la casuística que probablemente nos encontraremos en un entorno de producción.

Nuestra base de datos

Hagamos un pequeño esbozo en forma de diagrama de cómo debería ser nuestra base de datos, teniendo en cuenta la planificación que hicimos en la primera parte de este curso:

Diagrama aproximado de nuestra base de datos

Creación de migrations

Antes de hacer las migrations es necesario decirle a nuestra aplicación cómo acceder a nuestra base de datos. Para ello crea una base de datos, así como un usuario con los permisos pertinentes. Abre ahora el fichero .env en la carpeta raíz de nuestra aplicación y modifica las siguientes líneas introduciendo los datos apropiados:

DB_DATABASE=nombre_de_tu_nueva_base_de_datos
DB_USERNAME=nombre_del_nuevo_usuario_con_permisos
DB_PASSWORD=password_del_nuevo_usuario

A modo de referencia, a mí la cosa me ha quedado tal que así:

DB_DATABASE=my-site
DB_USERNAME=my-site
DB_PASSWORD=uVYB5vbN38UXmdQ4

Ahora ya podemos crear las migrations y ejecutarlas sin ningún miedo. Vayamos por faena. Ve a la carpeta database/migrations y observa que Laravel viene, ya por defecto, con dos migrations hechas: una para crear las tablas users y la otra para crear la tabla password_resets, ambas útiles y necesarias para gestionar el registro de nuestros futuros usuarios.

Si observas el diagrama anterior verás que nosotros necesitamos crear las siguientes tablas:

  • tags
  • girls
  • videos
  • subscriptions
  • users

Además, existe una relación many-to-many entre los tags y los videos y entre las girls y los videos. Esto es así porque en un vídeo pueden aparecer una o más actrices, así como una actriz puede aparecer en muchos vídeos, a este tipo de relación se le llama many-to-many en el mundillo de las bases de datos relacionales y normalmente requieren de la creación de una tabla adicional cuyas filas representarán “relaciones” entre elementos de las dos tablas. Así pues crearemos dos tablas adicionales, siguiendo la convención laraveliana de crear sus nombres a partir de los nombres de las tablas que relacionan en orden alfabético, en singular y separadas por un underscore _ . En definitiva, también crearemos las tablas:

  • girl_video
  • tag_video

La forma más rápida de generar las migrations necesarias para crear tablas es escribir, por cada tabla que queremos crear, el siguiente comando:

php artisan make:migration create_table-name_table --create=table-name

A modo de referencia, yo he escrito y ejecutado los siguientes comandos:

php artisan make:migration create_tags_table --create=tags
php artisan make:migration create_girls_table --create=girls
php artisan make:migration create_videos_table --create=videos
php artisan make:migration create_girl_video_table --create=girl_video
php artisan make:migration create_tag_video_table --create=tag_video

Laravel tiene una dependencia que nos va a venir que ni pintada para gestionar nuestras suscripciones, se llama Cashier y se encargará de hacernos la vida más fácil a la hora de gestionar las suscripciones. Instalemos esta dependencia antes de continuar:

composer require laravel/cashier:"~6.0"  

Nótese que al ejecutar ese comando Composer nos bajará la dependencia y la guardará en la carpeta vendor/.

También es necesario añadir la siguiente línea al final del array providers del fichero config/app.php:

Laravel\Cashier\CashierServiceProvider::class,

A esto que acabamos de hacer se le llama “registrar un Service Provider”, que es una forma muy pro de decir que hemos ampliado la funcionalidad de nuestra aplicación. Por ejemplo, el CashierServiceProvider que acabamos de registrar se encarga de registrar, a su vez, un nuevo namespace (con nuevas clases) y de asociar al comando publish la publicación o copia de ciertos ficheros que nos acabamos de bajar, desde el directorio vendor/ a cualquier otro direcotorio, en este caso de vendor/ a resources/views/. ¿Por qué no lo comprobamos? Escribe el siguiente comando:

php artisan vendor:publish

¿Ves cómo se han copiado ciertos ficheros de vendor/ a resources/views/? Más adelante ya hablaremos de qué va eso de las views.

Ahora, tal como indica la documentación oficial de Cashier, debemos modificar nuestra tabla users y crear una nueva tabla subscriptions. Como aún no hemos ejecutado las migrations, podríamos modificar directamente el fichero de migration de la tabla user, sin embargo será más pedagógico irnos acostumbrando a realizar modificaciones de nuestras tablas mediante la creación de nuevas migrations. Así pues, crearemos una migration para modificar la tabla users y otra migration para crear nuestra tabla subscriptions. Nótese que el comando para crear una migration que modifica una tabla es un poco diferente del que usamos para las migrations que crean tablas. Los comandos respectivos, para modificar users y crear subscriptions, son:

php artisan make:migration add_cashier_columns_to_users_table --table=users
php artisan make:migration create_subscriptions_table --create=subscriptions

Si todo está en orden deberíamos tener ahora nueve migrations en nuestra carpeta database/migrations/. Si no es así, asegúrate de haber seguido todos los pasos que hemos descrito hasta ahora.

Ahora es necesario que rellenemos cada migration que hemos creado con las columnas que deseamos agregar o modificar en cada tabla. Echemos un vistazo a la migration para crear la tabla tags. Si te fijas, el comando que escribimos anteriormente no sólo ha creado el fichero de migration, sino que también le ha escrito las funciones necesarias para crear una tabla con las columnas id y las columnas updated_at y created_at (timestamps).

Fíjate también que aparece una clase CreateTagsTable con los métodos up() y down(). up() es el método que se ejecuta cada vez que ejecutamos el comando migrate, mientras que el método down() se ejecuta cuando queremos dar marcha atrás en el proceso, por ejemplo mediante el comando rollback. Para que haya coherencia al ir hacia delante o hacia atrás en el proceso, si en up() hacemos una cosa, en down() debemos hacer su contraria.

Completemos ahora la migration para crear la tabla tags agregando el resto de columnas necesarias de acuerdo a nuestro diagrama:

Schema::create('tags', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('slug');
    $table->timestamps();
});

Análogamente, agreguemos las columnas que falta a la migration para crear la tabla girls:

Schema::create('girls', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('slug');
    $table->integer('age');
    $table->string('picture');
    $table->text('description');
    $table->timestamps();
});

¿Vas intuyendo lo que hace cada método de Blueprint? Aquí encontrarás una referencia detallada de cada uno. Si te surge alguna duda, no te cortes y pregunta. Sigamos con la tabla videos:

Schema::create('videos', function (Blueprint $table) {
    $table->increments('id');
    $table->string('name');
    $table->string('title');
    $table->string('slug');
    $table->string('filename');
    $table->string('thumbnail');
    $table->boolean('featured');
    $table->string('trailer');
    $table->timestamps();
});

Completemos ahora la migration para crear la tabla subscription:

Schema::create('subscriptions', function ($table) {
    $table->increments('id');
    $table->integer('user_id');
    $table->string('name');
    $table->string('stripe_id');
    $table->string('stripe_plan');
    $table->integer('quantity');
    $table->timestamp('trial_ends_at')->nullable();
    $table->timestamp('ends_at')->nullable();
    $table->timestamps();
});

Ahora completemos las migrations para las tablas que relacionan girls con videos y tags con videos. A estas tablas normalmente se les llama de algún modo especial: asociativas, de cross-reference, junction, de enlace… Llámalas como tú quieras, en cualquier caso su función es resolver una relación many-to-many. Estas migrations deberían quedarnos de la siguiente manera:

Schema::create('girl_video', function ($table) {
    $table->increments('id');
    $table->integer('girl_id')->unsigned();
    $table->foreign('girl_id')->references('id')->on('girls');
    $table->integer('video_id')->unsigned();
    $table->foreign('video_id')->references('id')->on('videos');
});
Schema::create('tag_video', function ($table) {
    $table->increments('id');
    $table->integer('tag_id')->unsigned();
    $table->foreign('tag_id')->references('id')->on('tags');
    $table->integer('video_id')->unsigned();
    $table->foreign('video_id')->references('id')->on('videos');
});

¡Ya sólo nos falta editar una migration para terminar! ¿Recuerdas aquella migration que creamos para modificar la tabla users? ¿Recuerdas que usamos un comando distinto para generarla? Si la abres y la comparas con el resto de migrations te darás cuenta de que ésta última utiliza el método table() en lugar de create() . Dentro de la función table() podemos crear columnas del mismo modo que hacíamos dentro de create(), con la diferencia de que con table() modificamos columnas en tablas ya existentes.

Puesto que dentro del método up() crearemos una serie de columnas en una tabla existente, dentro del método down() haremos lo contrario: eliminar las columnas de la tabla. Esto se hace de la siguiente manera:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('card_last_four')->after('password')->nullable();
        $table->string('card_brand')->after('password')->nullable();
        $table->string('stripe_id')->after('password')->nullable();
    });
}

public function down()
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('stripe_id');
        $table->dropColumn('card_brand');
        $table->dropColumn('card_last_four');
    });
}

¡Parece que hemos terminado de editar nuestras migrations! Ahora viene la hora de la verdad. Abre una línea de comandos y escribe:

php artisan migrate

Si todo ha ido bien, no deberían aparecernos mensajes de error. Si comprobamos nuestra base de datos deberíamos ver ahora las ocho nuevas tablas que hemos creado mediante las migrations más una tabla adicional que Laravel utiliza para llevar la cuenta de las migrations que ya se han ejecutado. La próxima vez que ejecutes el comando migrate Laravel sólo ejecutará las migrations que no hayan sido realizadas anteriormente, de ahí que tengas que hacer nuevas migrations para modificar las anteriores si estás trabajando con más gente o modificando una base de datos que ya está en producción. Si trabajas solo en fase de desarrollo y aún no has compartido tu trabajo con el resto del equipo siempre puedes ir hacia atrás en el proceso con el comando rollback, modificar las migrations y volver a ejecutarlas.

A modo de referencia dejo aquí apuntados los comandos más usados en relación con las migrations:

php artisan migrate           # migrates any new migration(s)
php artisan migrate:rollback  # rollback the latest "batch" of migrations
php artisan migrate:reset     # rollback all the migrations
php artisan migrate:refresh   # rollback all the migrations and then migrate them all again

Creación de models

Si has llegado hasta aquí es que ya tienes tu base de datos lista. Si es la primera vez que usas Laravel ¡te felicito! Ahora eso de las migrations ya no te asusta tanto, ¿a que no? Pues lo mismo te va a pasar con esto de los models. De hecho, si estás familiarizado con las Clases de cualquier lenguaje orientado a objetos (Java, PHP…), esto de los modelos te resultará más familiar que las migrations.

En términos técnicos un Model no es más que una clase que extiende la clase Model, que es una clase que implementa un montonazo de métodos que son la mar de útiles. Ya lo iremos viendo. Si no sabes de OOP ignora lo que acabo de decir y presta atención a estas palabras: en términos prácticos, un Model no es más que una forma de representar en el código de nuestra aplicación las tablas que hemos creado anteriormente, así como las relaciones que existen entre ellas. ¿Te he aclarado algo? Si no es así, no te preocupes. Practicando es como verdaderamente se aprende, así que vamos allá:

Salgamos de la carpeta database y vayamos ahora a la carpeta app/. Como ya dije al principio del post, esta carpeta es la leche de importante para el back end developer. Fíjate que en la raíz de la carpeta app/ encontrarás el fichero User.php . ¡Este fichero resulta que es un model! Si lo abres, verás que se trata de una clase que extiende la clase Authenticatable, que a su vez extiende la clase Model.

Pues nada, vamos a crear nuestros modelos. En concreto, vamos a crear los siguientes modelos:

  • Tag
  • Video
  • Girl

Además, realizaremos ciertas modificaciones al modelo User.php ya existente.

Pero antes… vamos a instalar otra dependencia. ¡Ya tenemos experiencia de antes con Cashier! Esta vez instalaremos la dependencia Sluggable, que básicamente nos permite crear slugs de forma automática, a partir de cualquier campo existente en cualquier tabla. Un slug es algo así como un identificador sin caracteres raros, que podemos usar sin problemas como parte de una URL. ¿Recuerdas que en nuestras tablas creamos aquellas columnas slug? Pues ahora ya sabes para qué servirán. Ejecuta el siguiente comando:

composer require cviebrock/eloquent-sluggable

Registra el Service Provider de Sluggable, es decir, añade la siguiente línea al final del array providers en el fichero config/app.php:

Cviebrock\EloquentSluggable\SluggableServiceProvider::class,

Finalmente, ¿lo adivinas? Ejecuta el comando publish:

php artisan vendor:publish

A diferecia que Cashier, la dependencia Sluggable no publica views desde la carpeta vendor, sino que publica un fichero de configuración dentro de nuestra carpeta config/ . En principio no hay razón para cambiar la configuración que aparece ahí por defecto, pero no está de más que le eches un vistazo.

Muy bien, ya podemos escribir nuestros models. Empecemos por Tag. Para crear este modelo básicamente crearemos un nuevo fichero Tag.php en la raíz del directorio app/ , es decir, en el mismo sitio donde se encuentra User.php . Una vez creado el fichero, edítalo de la siguiente manera:

Tag.php

 1 <?php
 2 
 3 namespace App;
 4 
 5 use Cviebrock\EloquentSluggable\SluggableInterface;
 6 use Cviebrock\EloquentSluggable\SluggableTrait;
 7 use Illuminate\Database\Eloquent\Model;
 8 
 9 class Tag extends Model implements SluggableInterface
10 {
11     use SluggableTrait;
12     
13     /**
14     * The field(s) we use to build the unique slug
15     * and the field where we store it
16     *
17     * @var array
18     */
19     protected $sluggable = [
20         'build_from' => 'name',
21         'save_to'    => 'slug',
22     ];
23     
24     /**
25     * The database table used by the model.
26     *
27     * @var string
28     */
29     protected $table = 'tags';
30     
31     /**
32     * The attributes that are mass assignable.
33     *
34     * @var array
35     */
36     protected $fillable = [];
37     
38     /**
39     * The attributes excluded from the model's JSON form.
40     *
41     * @var array
42     */
43     protected $hidden = [];
44     
45     /*
46     |-----------------------------------------------
47     | Relationships (with other models)
48     |-----------------------------------------------
49     */
50     public function videos()
51     {
52         return $this->belongsToMany('App\Video');
53     }
54     
55     /*
56     |-----------------------------------------------
57     | Accessors & Mutators
58     |-----------------------------------------------
59     */
60     
61     /*
62     |-----------------------------------------------
63     | Scopes
64     |-----------------------------------------------
65     */
66 }

Date cuenta que he dejado en blanco el apartado para accessors, mutators y scopes. Más adelante (cuando los utilicemos) explicaremos de qué van, por ahora basta con que te suene lo de que están relacionadas con los models.

Lo que sí es importante entender ahora es la cuestión de las relaciones (relationships) entre modelos. Estas relaciones no son más que representaciones de las relaciones que existen entre las tablas. Al respeto, te dejo a continuación una pequeña guía.

Existen, básicamente, tres tipos de relaciones entre tablas de una base de datos relacional:

  1. One to one: una relación uno a uno es, por ejemplo, la relación que existe entre tú y tu nariz; tú sólo tienes una nariz y tu nariz es poseída sólo por ti. En una base de datos este tipo de relación se revuelve creando una foreign key en una de las tablas de la relación, normalmente en la del objeto que es poseído por el otro objeto.
  2. One to many: una relación uno a muchos sería, por ejemplo, la relación que existe entre tú y tus pelos; tú tienes unos cuantos pelos (de cero a muchos) pero tus pelos sólo te tienen a ti como dueño. Este relación se resuelve en una base de datos del mismo modo que la one to one, sólo que la foreign key se ha de crear necesariamente en la tabla “pelos” del ejemplo, es decir, en la del objeto que es poseído por el otro objeto.
  3. Many to many: esta sería la relación entre tú y tus características como persona; puedes tener muchas características y, al mismo tiempo, una de esas características (por ejemplo: ser awesome) puede ser poseída por más de una persona. Este caso es el que hemos resuelto anteriormente creando una tabla asociativa entre dos tablas.

Ahora vamos a ver en qué nos afectan las relaciones de cara a editar nuestras tables y models:

Relación Dirección Method returns… ¿Tabla con foreign key?
One to one This has one… $this->hasOne() No
One to one Inversa: this belongs to one… $this->belongsTo()
One to many This has many… $this->hasMany() No
One to many Inversa: this belongs to one… $this->belongsTo()
Many to many En las dos direcciones $this->belongsToMany() Junction table

Fíjate que en nuestro model Tag hemos escrito la función videos(), que devuelve el resultado de utilizar el método belongToMany() porque la relación entre Tag y Video era de many to many (tal como indico en la tabla). A partir de ahora, podremos escribir en cualquier parte de nuestro código:

$tag->videos()->get()

O aún mejor:

$tag->videos

Obtendremos así todos los vídeos relacionados con una tag específica. A este sistema para obtener información de nuestra base de datos, es decir, hacer consultas a nuestra base de datos, a través los models y la concatenación de métodos se le conoce como eloquent en lenguaje laravelita.

Muy bien, ¡basta ya de aprender y sigamos trabajando! Tenemos que crear el resto de modelos: Video y Girl, ¿te atreves a hacerlos por tu cuenta? Un truco para terminar rápido es copiar lo que has escrito para Tag.php y hacer las modificaciones necesarias, es decir: cambiar el nombre de la clase, modificar el build_from que pasamos dentro del array $sluggable y, muy importante, reescribir las relationships adecuadamente (acuérdate que Video tiene dos relaciones en lugar de una).

Video.php

 1 <?php
 2 
 3 namespace App;
 4 
 5 use Cviebrock\EloquentSluggable\SluggableInterface;
 6 use Cviebrock\EloquentSluggable\SluggableTrait;
 7 use Illuminate\Database\Eloquent\Model;
 8 
 9 class Video extends Model implements SluggableInterface
10 {
11     use SluggableTrait;
12 
13     /**
14      * The attribute(s) we use to build the slug
15      * and the field name where we store it
16      *
17      * @var array
18      */
19     protected $sluggable = [
20         'build_from' => 'title',
21         'save_to'    => 'slug',
22     ];
23 
24     /**
25      * The database table used by the model.
26      *
27      * @var string
28      */
29     protected $table = 'videos';
30 
31     /**
32      * The attributes that are mass assignable.
33      *
34      * @var array
35      */
36     protected $fillable = [];
37 
38     /**
39      * The attributes excluded from the model's JSON form.
40      *
41      * @var array
42      */
43     protected $hidden = [];
44 
45     /*
46     |-----------------------------------------------
47     | Relationships
48     |-----------------------------------------------
49     */
50     public function tags()
51     {
52         return $this->belongsToMany('App\Tag');
53     }
54 
55     public function girls()
56     {
57         return $this->belongsToMany('App\Girl');
58     }
59 
60     /*
61     |-----------------------------------------------
62     | Accessors & Mutators
63     |-----------------------------------------------
64     */
65 
66     /*
67     |-----------------------------------------------
68     | Scopes
69     |-----------------------------------------------
70     */
71 }

Girl.php

 1 <?php
 2 
 3 namespace App;
 4 
 5 use Cviebrock\EloquentSluggable\SluggableInterface;
 6 use Cviebrock\EloquentSluggable\SluggableTrait;
 7 use Illuminate\Database\Eloquent\Model;
 8 
 9 class Girl extends Model implements SluggableInterface
10 {
11     use SluggableTrait;
12 
13     /**
14      * The attribute(s) we use to build the slug
15      * and the field name where we store it
16      *
17      * @var array
18      */
19     protected $sluggable = [
20         'build_from' => 'name',
21         'save_to'    => 'slug',
22     ];
23 
24     /**
25      * The database table used by the model.
26      *
27      * @var string
28      */
29     protected $table = 'girls';
30 
31     /**
32      * The attributes that are mass assignable.
33      *
34      * @var array
35      */
36     protected $fillable = [];
37 
38     /**
39      * The attributes excluded from the model's JSON form.
40      *
41      * @var array
42      */
43     protected $hidden = [];
44 
45     /*
46     |-----------------------------------------------
47     | Relationships
48     |-----------------------------------------------
49     */
50     public function videos()
51     {
52         return $this->belongsToMany('App\Video');
53     }
54 
55     /*
56     |-----------------------------------------------
57     | Accessors & Mutators
58     |-----------------------------------------------
59     */
60 
61     /*
62     |-----------------------------------------------
63     | Scopes
64     |-----------------------------------------------
65     */
66 }

No es necesario que escribamos un model Subscription, ya que la dependencia Cashier que instalamos anteriormente se encargará de manipular las filas de la tabla subscriptions. En su lugar, lo que sí debemos hacer es modificar el model User para que podamos acceder desde el propio model a los métodos de Cashier. Para ello lo que tenemos que hacer es agregarle el trait Billing al model User… ¿What? No te alarmes, en términos técnico-familiares un trait es una colección de métodos envueltos en una clase “especial” llamada trait (no kidding!), pensados para poder ser empleados por otras clases.

En verdad todo este rollo que te he soltado se resuelve, en nuestro caso, abriendo User.php y agregando las siguientes líneas de código:

...
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Cashier\Billable;

class User extends Authenticatable
{
    use Billable;
    ...

Pues me parece que ya hemos terminado con nuestros models. Para comprobar que todo funciona qué mejor manera que poblar nuestra base de datos con eloquent, es decir, a través de los models. Pasemos, por tanto, a la siguiente sección del curso.

Creación de seeders

Desde hace poco Laravel viene de serie con una dependencia muy útil llamada Faker que básicamente nos ayuda a generar datos falsos: nombres propios, direcciones de calles, fechas, combinaciones de números y letras, etc. Más adelante verás ejemplos de cómo usarla.

Salgamos de la carpeta app/ y volvamos a la carpeta database/, por razones obvias esta vez abriremos la subcarpeta seeders/ en lugar de migrations/.

Fíjate que aquí dentro podemos encontrar un fichero llamado DatabaseSeeder.php . Échale un vistazo. Se trata de una clase que extiende la clase Seeder y que tiene un método run(), no hay mucho más que ver. Pues bien, este fichero es un seeder, ni más ni menos. Todos nuestros seeders van a extender la clase Seeder y tendrán el mismo método run().

Para ser ordenados y limpios escribiremos un Seeder por cada tabla que queremos poblar y a continuación “enlazaremos” la ejecución de todos ellos escribiendo una llamada a cada seeder en el fichero DatabaseSeeder.php . Manos a la obra. Hagamos en primer lugar una recapitulación de las tablas que necesitamos poblar:

  • tags
  • girls
  • videos
  • tag_video
  • girl_video
  • users
  • subscriptions

Una estrategia sólida para poblar nuestra web con contenidos de prueba será poblar primero las tags y las girls y a continuación los videos, para finalmente asociar, aleatoriamente, girls y tags con videos. Por otro lado, poblaremos la tabla users con datos de prueba y crearemos diversos tipos de suscripción también con datos falsos.

Antes de escribir el primer seeder, vamos a crear un fichero de configuración seeders.php en la carpeta config/ para tener en un mismo sitio, bien accesibles, los datos sobre la configuración de nuestros seeders, como por ejemplo: el número de filas que queremos crear en cada tabla, ciertos datos específicos para poblar algunas de las celdas, etc. Así de paso también aprenderemos cómo se trabaja con config/ .

No te lo pienses dos veces, crea tu fichero seeders.php dentro de la carpeta config/ . Edítalo para que se parezca al mío y siéntete libre de modificarlo a tu gusto:

seeders.php

  1 <?php
  2 
  3 return [
  4 
  5     // Seed each of these tables with this numbers of rows
  6     'nGirls'    => 20,
  7     'nVideos'   => 100,
  8     'nUsers'    => 50,
  9 
 10     // How many tags can be associated to each video?
 11     'minTags'   => 1,
 12     'maxTags'   => 10,
 13 
 14     // How many girls can be associated to each video?
 15     'minGirls'  => 1,
 16     'maxGirls'  => 4,
 17 
 18     // Fake information locale (en_US, es_ES, etc.)
 19     'locale'    => 'es_ES',
 20 
 21     // Subscription name
 22     'subscriptionName'  => 'main',
 23 
 24     // Subscription plan types
 25     'subscriptionPlans' => [
 26         'monthly',
 27         'every-three-months',
 28         'yearly',
 29     ],
 30 
 31     // Porn categories (courtesy of one random porn site)
 32     'pornCategories' => [
 33         '3D Hentai',
 34         '3D Stereoscopic',
 35         '3D Toons',
 36         'Amateur',
 37         'Anal',
 38         'Arab',
 39         'Asian',
 40         'Babes',
 41         'Babysitters',
 42         'Ballbusting',
 43         'BBW',
 44         'BDSM',
 45         'Beach',
 46         'Big Butt',
 47         'Big Dick',
 48         'Big Tits',
 49         'Bisexual',
 50         'Black',
 51         'Blonde',
 52         'Blowjob',
 53         'Brazilian',
 54         'British',
 55         'Brunette',
 56         'Bukkake',
 57         'Cartoon',
 58         'Casting',
 59         'Cat Fights',
 60         'Celebrities',
 61         'CFNM',
 62         'Changing Room',
 63         'Chaturbate',
 64         'Cheerleaders',
 65         'Chinese',
 66         'Close-up',
 67         'College',
 68         'Compilation',
 69         'Cosplay',
 70         'Cougar',
 71         'Couple',
 72         'Creampie',
 73         'Cuckold',
 74         'Cumshot',
 75         'Cunnilingus',
 76         'Czech',
 77         'Danish',
 78         'Deep Throat',
 79         'Dildos/Toys',
 80         'Doggy Style',
 81         'Double Penetration',
 82         'Downblouse',
 83         'Ebony',
 84         'Emo',
 85         'European',
 86         'Face Sitting',
 87         'Facial',
 88         'Femdom',
 89         'Fetish',
 90         'Fingering',
 91         'Fisting',
 92         'Flashing',
 93         'Foot Fetish',
 94         'French',
 95         'Fucking Machines',
 96         'Funny',
 97         'Gangbang',
 98         'Gaping',
 99         'German',
100         'Girlfriend',
101         'Glory Holes',
102         'Gothic',
103         'Grannies',
104         'Group Sex',
105         'Hairy',
106         'Handjob',
107         'Hardcore',
108         'HD',
109         'Hentai',
110         'Hidden Cams',
111         'Indian',
112         'Interracial',
113         'Italian',
114         'Japanese',
115         'JOI',
116         'Korean',
117         'Latex',
118         'Latina',
119         'Lesbian',
120         'Lingerie',
121         'Live Show',
122         'Massage',
123         'Masturbation',
124         'Mature',
125         'Medical',
126         'Midgets',
127         'MILF',
128         'Military',
129         'Natural Tits',
130         'Nipples',
131         'Oldy',
132         'Orgasm',
133         'Outdoor',
134         'Panties',
135         'Philippines',
136         'Phone',
137         'Piercing',
138         'Pissing',
139         'Pornstars',
140         'POV',
141         'Pregnant',
142         'Public',
143         'Reality',
144         'Redhead',
145         'Retro',
146         'Rimming',
147         'Romantic',
148         'Russian',
149         'Selfshot',
150         'Sharking',
151         'Shaved',
152         'Shower',
153         'Skinny',
154         'Small Tits',
155         'Smoking',
156         'Softcore',
157         'Solo Girl',
158         'Spanish',
159         'Spanking',
160         'Sports',
161         'Squirting',
162         'Stockings',
163         'Strapon',
164         'Strip',
165         'Swallow',
166         'Swedish',
167         'Swingers',
168         'Tattoos',
169         'Thai',
170         'Threesomes',
171         'Turkish',
172         'Underwater',
173         'Uniform',
174         'Upskirt',
175         'Vintage',
176         'Voyeur',
177         'Webcam',
178         'Wife',
179         'Window',
180         'Yoga'
181     ],
182 
183 ];

Ya tenemos nuestro fichero de configuración listo. Pasemos a escribir cada uno de los seeders. Empecemos por el de la tabla tags. Crea un fichero TagTableSeeder.php en la carpeta database/seeders/ tal como éste:

TagTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class TagTableSeeder extends Seeder
 6 {
 7     /**
 8      * Run the database seeds.
 9      *
10      * @return void
11      */
12     public function run()
13     {
14         foreach(config('seeders.pornCategories') as $categoryName)
15             \App\Tag::create([
16                 'name' => $categoryName,
17             ]);
18     }
19 }

Presta atención a eso de config(‘seeders.pornCategories’) . config() es una helper function (¿función ayudante?) de Laravel que nos permite acceder a los valores que hay en los ficheros de la carpeta config/ . Por ejemplo, nosotros acabamos de acceder a la lista de categorías de vídeos que teníamos en seeders.php .

Fíjate en el resto del seeder. Nuestro nuevo seeder básicamente recorre el array con las categorías que hemos escrito en el fichero de configuración y, para cada una de ellas, ejecuta \App\Tag::create() .

\App\Tag no es más que el model que escribimos anteriormente (Tag está en el namespace App, de ahí que lo escribamos de esa manera). Todos los models tienen un método create() al que podemos pasar, como primer argumento, un array de datos que serán utilizados para crear una nueva fila de la tabla de la base de datos relacionada. Básicamente es una forma muy rápida de poblar la base de datos, ya que nos evitamos el tener que escribir la típica query INSERT. Esta es, precisamente, la razón de ser de eloquent y uno de los motivos por los que deberías darle cariño a Laravel.

Muy bien, vamos a terminar de escribir el resto de seeders. En el siguiente seeder vamos a tener la ocasión de utilizar la librería Faker, que nos permite generar datos falsos y aleatorios para poblar la base de datos. Veámoslo en acción editando un nuevo fichero GirlTableSeeder.php :

GirlTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class GirlTableSeeder extends Seeder
 6 {
 7 
 8     /**
 9      * Run the database seeds.
10      *
11      * @return void
12      */
13     public function run()
14     {
15         $faker = Faker\Factory::create(config('seeders.locale'));
16 
17         foreach(range(1,config('seeders.nGirls')) as $i)
18             \App\Girl::create([
19                 'name'          => $faker->name('female'),
20                 'age'           => $faker->numberBetween(18,90),
21                 'picture'       => 'example-profile-picture.jpg',
22                 'description'   => $faker->text(300),
23             ]);
24     }
25 
26 }

Si tienes curiosidad sobre cómo funciona Faker y los muchos tipos de datos que podemos generar (nombres, números, direcciones, bloques de texto…), échale un vistazo a la documentación de Faker. Si te surgen dudas, puedes preguntar en el área de comentarios.

Con el seeder para los vídeos VideoTableSeeder.php hemos hecho más de lo mismo, te lo dejo por aquí como ejemplo:

VideoTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class VideoTableSeeder extends Seeder
 6 {
 7     /**
 8      * Run the database seeds.
 9      *
10      * @return void
11      */
12     public function run()
13     {
14         $faker = Faker\Factory::create(config('seeders.locale'));
15 
16         foreach(range(1, config('seeders.nVideos')) as $i)
17             \App\Video::create([
18                 'title'         => $faker->sentence(5),
19                 'filename'      => 'example-video.flv',
20                 'thumbnail'     => 'example-thumbnail.jpg',
21                 'featured'      => $faker->boolean(5),
22                 'trailer'       => 'example-trailer.flv'
23             ]);
24     }
25 }

Ahora vamos a escribir el seeder para crear relaciones entre tags y videos y entre girls y videos, yo les he llamado TagVideoTableSeeder.php y GirlVideoTableSeeder.php :

TagVideoTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class TagVideoTableSeeder extends Seeder
 6 {
 7     /**
 8      * Run the database seeds.
 9      *
10      * @return void
11      */
12     public function run()
13     {
14         // First we gather all tags and videos previously created
15         $tags = \App\Tag::all()->pluck('id')->toArray();
16         $videos = \App\Video::all();
17       
18         // Now we iterate through all the videos in our DB
19         foreach($videos as $video) {
20       
21             // We select some random tag keys out of the array of tag's ids
22             $selectedTagKeys = array_rand(
23                 $tags,
24                 mt_rand(
25                     config('seeders.minTags'),
26                     config('seeders.maxTags')
27                 )
28             );
29             
30             // Finally, we associate the selected tags to the current video
31             if(! is_array($selectedTagKeys))
32                 $video->tags()->attach($tags[$selectedTagKeys]);
33             else
34                 foreach($selectedTagKeys as $tagKey)
35                     $video->tags()->attach($tags[$tagKey]);
36         }
37     }
38 }

GirlVideoTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class GirlVideoTableSeeder extends Seeder
 6 {
 7     /**
 8      * Run the database seeds.
 9      *
10      * @return void
11      */
12     public function run()
13     {
14         // First we gather all girls and videos previously created
15         $girls = \App\Girl::all()->pluck('id')->toArray();
16         $videos = \App\Video::all();
17 
18         // Now we iterate through all the videos
19         foreach($videos as $video) {
20 
21             // We select some random keys from the girl's ids array
22             $selectedGirlKeys = array_rand(
23                 $girls,
24                 mt_rand(
25                     config('seeders.minGirls'),
26                     config('seeders.maxGirls')
27                 )
28             );
29 
30             // Finally, we associate the selected girls to the current video
31             if(! is_array($selectedGirlKeys))
32                 $video->girls()->attach($girls[$selectedGirlKeys]);
33             else
34                 foreach($selectedGirlKeys as $tagKey)
35                     $video->girls()->attach($girls[$tagKey]);
36         }
37     }
38 }

Estos seeders quizá te parezcan algo más complicados que los anteriores, pero en realidad no son nada especial. Fíjate que empleando el método all() desde los models respectivos, podemos obtener todos los tags, girls y videos que ya existen en la base de datos. Siempre que hagamos \App\Modelo::all() obtendremos una Collection con todos los elementos de la base de datos relacionada con el Model.

Con el método pluck(‘columna’) obtendremos una Collection sólo con los datos de una de las columnas de la tabla, mientras que el método toArray() nos sirve para transformar una Collection en un array de toda la vida.

Una Collection, como su nombre indica, es una colección de elementos (normalmente representados por nuestros Models), técnicamente es una especie de “abrigo” para un array que nos permite, por ejemplo, concatenar métodos para manipular el array que contiene. Cuando usamos toArray() en una Collection básicamente estamos quitándole ese abrigo, transformando la Collection en un array normal y corriente.

El resto del código consiste en elegir entre uno y muchos ids de las tablas tags y girls y relacionarlos con los videos usando el método attach(), que es el método indicado para hacer asociaciones entre objetos con relación del tipo many to many.

Lo que acabamos de hacer es, en definitiva, poblar las tablas asociativas tag_video y girl_video.

Nos quedan un par más de seeders para terminar. Editaremos un nuevo fichero UserTableSeeder.php para poblar nuestra tabla users y un nuevo fichero SubscriptionsSeeder.php para poblar nuestra tabla subscriptions con datos más falsos que un euro con la cara de Popeye.

Aquí te dejo los que he escrito yo como referencia:

UserTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class UserTableSeeder extends Seeder
 6 {
 7     /**
 8      * Run the database seeds.
 9      *
10      * @return void
11      */
12     public function run()
13     {
14         $faker = Faker\Factory::create(config('seeders.locale'));
15     
16         foreach(range(1,config('seeders.nUsers')) as $i)
17             \App\User::create([
18                 'name'              => $faker->name,
19                 'email'             => $faker->email,
20                 'password'          => bcrypt('wanker'),
21                 'stripe_id'         => 'cus_'.$i,
22                 'card_brand'        => 'Visa',
23                 'card_last_four'    => '4242',
24             ]);
25     }
26 }

SubscriptionTableSeeder.php

 1 <?php
 2 
 3 use Illuminate\Database\Seeder;
 4 
 5 class SubscriptionsSeeder extends Seeder
 6 {
 7     /**
 8      * Run the database seeds.
 9      *
10      * @return void
11      */
12     public function run()
13     {
14         $users = \App\User::all();
15         $subscriptionPlans = config('seeders.subscriptionPlans');
16         $faker = Faker\Factory::create(config('seeders.locale'));
17     
18         foreach($users as $user)
19             DB::table('subscriptions')->insert([
20                 'user_id'       => $user->id,
21                 'name'          => config('seeders.subscriptionName'),
22                 'stripe_id'     => '',
23                 'stripe_plan'   => $subscriptionPlans[array_rand($subscriptionPlans)],
24                 'quantity'      => '1',
25                 'ends_at'       => $faker->dateTimeBetween('-3 months','1 year'),
26             ]);
27     }
28 }

La única diferencia significativa con respecto a los seeders anteriores es que en el seeder para la tabla subscriptions no hemos utilizado ningún modelo \App\Subscription para manipular la base de datos, sencillamente porque no existe ningún modelo Subscripcion. En su lugar hemos utilizado la Facade DB, que nos permite ejecutar queries a nuestra base de datos sin necesidad de ningún modelo de por medio.

Una Facade no es más que una forma de acceder a ciertas clases y a su respectiva familia de métodos sin dar muchas vueltas. Si echas un vistazo a config/app.php verás, abajo del todo, toda una lista de Facades a las que puedes acceder desde cualquier punto de tu código con tan sólo escribir el “alias” o nombre de la Facade.

Para terminar de una vez por todas con nuestros seeders vamos a asegurarnos de que se ejecutarán todos una vez que escribamos el comando seed. Para ello tenemos que editar aquel fichero DatabaseSeeder.php que encontramos inicialmente en nuestra carpeta database/seeder/ , ¿lo recuerdas?

Agrégale las siguientes líneas:

    public function run()
    {
        $this->call(TagTableSeeder::class);
        $this->call(GirlTableSeeder::class);
        $this->call(VideoTableSeeder::class);
        $this->call(TagVideoTableSeeder::class);
        $this->call(GirlVideoTableSeeder::class);
        $this->call(UserTableSeeder::class);
        $this->call(SubscriptionTableSeeder::class);
    }

¡Ya hemos terminado con los seeders! Crucemos los dedos y pasemos a escribir el comando para ejecutarlos:

php artisan db:seed

Si todo va bien deberíamos tener todas nuestras tablas llenitas de datos, compruébalo y congratúlate.

Podría ocurrir que algún seeder nos diese error y se quedase la historia a medio hacer, en estos casos, una vez corregido nuestro código, un comando muy útil que resetea la base de datos y vuelve a intentar poblarla es el siguiente:

php artisan migrate:refresh --seed  # Si lo usas en el entorno de producción irás al infierno

Anótalo bien, porque yo he perdido la cuenta de las veces que lo he tenido que usar en la fase de desarrollo de los proyectos.

Pues nada, ya tenemos lista nuestra base de datos, espero que los ejemplos que estamos escribiendo te sirvan para hacerte con una idea lo suficientemente general para que tú mismo puedas crear y poblar todo tipo de tablas que se te ocurran para cualquier tipo de proyecto en el que trabajes en el futuro.

Recuerda: si te surgen dudas o detectas errores puedes escribirme un comentario más abajo.

Próximamente
Tu web porno con Laravel - III: Routes, resources y controllers