laravel5.8 GraphQL validaciones argumentos de queries

para CityQuery.php

public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
'rules' => ['required','numeric','exists:cities,id']
],
];
}

en el codigo la validacion numeric no es necesaria, si el valor del argumento no lo fuera,
por ejemplo en:

{
  city(id:hola) {
    id
    name
  }
}

el resultado seria:

{
  "errors": [
    {
      "message": "Field \"city\" argument \"id\" requires type Int, found hola.",
      "extensions": {
        "category": "graphql"
      },
      "locations": [
        {
          "line": 2,
          "column": 11
        }
      ]
    }
  ]
}


 sdsad

laravel5.8 GraphQL crear/actualizar eliminar un registro mutation

Mutation sirven para realizar alguna accion sobre una instancia del modelo

creamos la mutation

php artisan make:graphql:mutation CreateCityMutation

editamos el archivo que CreateCityMutation.php que se genera:

public function type()
    {
        return GraphQL::type('city');
    }

    public function args()
    {
        return [
            'name' => [
                'name' => 'name',
                'type' =>  Type::nonNull(Type::string()),
                'rules' => ['required', 'min:3', 'max:50','unique:cities,name']
            ],
        ];
    }

   

    public function resolve($root, $args)
    {
        $city = new City();
        $city->fill($args);
        $city->save();

        return $city;
    }

en este caso el -unico parametro y obligatorio que le enviamos a la funcion es el name de la city
por que en el modelo este campo debe estar como parte de la lista de campos que se pueden asignar de modo masivo:

en City.php

protected $fillable = [
        'name'
    ];


notar que por simplicidad pusimos las validaciones dentro de la funcion de args, esto tambien se
 puede hacer en una funcion separada, en nuestro caso hubiese quedado asi:

public function rules(array $args = [])
    {
        return [
            'name' => [
                'required', 'min:3', 'max:50','unique:cities,name'
            ],
        ];
    }

ahora registramos la mutation en grapql.php dentro de la seccion de Schemas:

'schemas' => [
        'default' => [
            'query' => [
               ..
            ],
            'mutation' => [
                 'createCity' => App\GraphQL\Mutation\CreateCityMutation::class,
            ],
        ],
    ],

ahora ya podemos probar la query de creacion

mutation createCity {
  createCity(name : "barcelona"){
    id,name
  }
}

en donde id, name serian los campos que queremos que nos muestre el resultado de la operacion (es necesario indicarlos)

lo que nos resultaria en algo como:

{
  "data": {
    "createCity": {
      "id": 6,
      "name": "barcelona"
    }
  }
}


para actualizar seria :

en UpdateCityMutation.php

public function rules(array $args = [])
    {
        return [
            'id' => [
                'required', 'numeric', 'min:1', 'exists:cities,id'
            ]
        ];
    }
    public function args()
    {
        return [
            'id' => [
                'name' => 'id',
                'type' =>  Type::nonNull(Type::int()),
            ],
            'name' => [
                'name' => 'name',
                'type' =>  Type::nonNull(Type::string()),
                'rules' => ['required', 'min:3', 'max:50','unique:cities,name']
            ],
        ];
    }

    public function resolve($root, $args)
    {
        $city = City::findOrFail($args['id']);

       $city->fill($args);
        $city->save();

        return $city;
    }

y al igual que con el create, la llamada seria de esta manera:

mutation updateCity {
  updateCity(id:6,name: "ciudad de barcelona"){
    id,name
  }
}

para eliminar un registro:

DeleteCityMutation.php

public function rules(array $args = [])
    {
        return [
            'id' => [
                'required', 'numeric', 'min:1', 'exists:cities,id'
            ]
        ];
    }
   
    public function type()
    {
        return  Type::boolean();
    }

    public function args()
    {
        return [
            'id' => [
                'name' => 'id',
                'type' => Type::int()
            ]
        ];
    }

    public function resolve($root, $args)
    {
       $city = City::findOrFail($args['id']);
       $city->delete() ? true : false;
    }



mutation deleteCity {
  deleteCity(id:5)
}

resultando:

{
  "data": {
    "deleteCity": null
  }
}


laravel5.8 GraphQL obtener un simple registro (3)

en CityQuery.php
public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int(),
'rules' => ['required']
],
];
}
public function resolve($root, $args)
{
return City::findOrFail($args['id']);
}


lo que podemos llamar:

{
  city (id: 22) {
        name
}


si no existe el id de la city, dará el sgt error:

{
  "errors": [
    {
      "debugMessage": "No query results for model [App\\City] 22",
      "message": "Internal server error",
...
}

Si se quiere customizar el mensaje de "debugMessage" se puede implementar
la funcion resolve asi:

public function resolve($root, $args)
{
if (!$city = City::find($args['id'])) {
throw new \Exception('La Ciudad no existe');
}

return $city;
}

laravel5.8 GraphQL entidades relacionadas (2)

del ejemplo anterior,
podemos agregar la sgt funcion para indicar los Events que se dan en una City.

entonces en App\City.php agregamos:

public function events()
{
return $this->hasMany(Event::class);
}
}

y en su type editamos asi: app/GraphQL/Type/CityType.php

public function fields()
{
return [
...
'events' => [
'type' => Type::listOf(GraphQL::type('event')),
'description' => 'los eventos realizados en esa ciudad'
]
];
}


luego podemos hacer la siguiente consulta:

{
    cities (id: 1) {
        name,
        events{
          id,title
       }
   }
}

obteniendo una respuesta como esta:

{
  "data": {
    "cities": [
      {
        "name": "Lima",
        "events": [
          {
            "id": 1,
            "title": "Feria de tecnologia"
          }
        ]
      }
    ]
  }
}




laravel5.8 GraphQL entidades relacionadas

creamos 2 modelos con sus respectivas migraciones:
-City
-Event (que se realiza en una city)

php artisan make:model City -m

php artisan make:model Event -m

editar el archivo de migracion de City:

public function up()
{
Schema::create('cities', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name', 50);
$table->timestamps();
});
}

en el archivo de migracion de Event:

public function up()
{
Schema::create('events', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title', 50);
$table->string('description', 200);
$table->unsignedBigInteger('city_id');
$table->timestamps();

$table->foreign('city_id')->references('id')->on('cities');
});
}

en el modelo de City, lo podemos dejar como está.

al modelo de Event le agregamos la funcion de relacion con city:

public function city(){
return $this->belongsTo(City::class);
}

crear el Type de City:

php artisan make:graphql:type CityType


use Rebing\GraphQL\Support\Type as GraphQLType;
use App\City;
use GraphQL\Type\Definition\Type;

class CityType extends GraphQLType
{
protected $attributes = [
'name' => 'city',
'description' => 'A type',
'model' => City::class
];

public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'Id de la city',
],
'name' => [
'type' => Type::nonNull(Type::string()),
'description' => 'nombre de la city',
],
];
}
}


creamos la query para City:

php artisan make:graphql:query CitiesQuery


use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\SelectFields;
use Rebing\GraphQL\Support\Query;
use GraphQL;
use App\City;

class CitiesQuery extends Query
{
protected $attributes = [
'name' => 'cities',
'description' => 'A query'
];

public function type()
{
return Type::listOf(GraphQL::type('city'));
}

public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int()
],
];
}

public function resolve($root, $args, SelectFields $fields, ResolveInfo $info)
{
$select = $fields->getSelect();
$with = $fields->getRelations();

if(isset($args['id']))
{
return City::where('id','=',$args['id'])->get();
}

$cities = City::with($with)->select($select)->get();

return $cities;
}
}

del mismo modo, creamos el type de Event:

php artisan make:graphql:type EventType

use Rebing\GraphQL\Support\Type as GraphQLType;
use App\Event;
use GraphQL\Type\Definition\Type;
use GraphQL;

class EventType extends GraphQLType
{
protected $attributes = [
'name' => 'event',
'description' => 'A type',
'model' => Event::class
];

public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'Id del Evento',
],
'title' => [
'type' => Type::string(),
'description' => 'title del Evento',
],
'description' => [
'type' => Type::string(),
'description' => 'description del Evento',
],
'city' => [
'type' => GraphQL::type('city'),
'description' => 'ciudad del Evento',
]
];
}
}

creamos la query de Event:

php artisan make:graphql:query EventsQuery

use GraphQL\Type\Definition\Type;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\SelectFields;
use Rebing\GraphQL\Support\Query;
use App\Event;
use GraphQL;

class EventsQuery extends Query
{
protected $attributes = [
'name' => 'events',
'description' => 'A query'
];

public function type()
{
return Type::listOf(GraphQL::type('event'));
}

public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int()
],
];
}

public function resolve($root, $args, SelectFields $fields, ResolveInfo $info)
{
$select = $fields->getSelect();
$with = $fields->getRelations();

if(isset($args['id']))
{
return Event::where('id','=',$args['id'])->get();
}

$events = Event::with($with)->select($select)->get();

return $events;
}
}

registramos los types y las querys,
en el archivo graphql.php:

'schemas' => [
'default' => [
'query' => [
'cities' => App\GraphQL\Query\CitiesQuery::class,
'events' => App\GraphQL\Query\EventsQuery::class
]
],
],
'types' => [
'city' => App\GraphQL\Type\CityType::class,
'event' => App\GraphQL\Type\EventType::class
],

probando la api:

{ events{ title city{ name } } }

resultado:

{ "data": { "events": [ { "title": "Feria de tecnologia", "city": { "name": "Lima" } }, { "title": "Muestra regional de material radiactivo", "city": { "name": "Tokio" } }, { "title": "Encuentro de emprendedores naturistas", "city": { "name": "Buenos Aires" } } ] } }


laravel5.8 GraphQL

crear la aplicacion

composer create-project --prefer-dist laravel/laravel winestore

crear el modelo

php artisan make:model Wine -m

editamos el archivo de migraciones generado correspondiente al modelo:

public function up() { Schema::create('wines', function (Blueprint $table) { $table->increments('id'); $table->string('name', 50); $table->text('description'); $table->string('color', 10); $table->string('grape_variety', 50); $table->string('country', 50); $table->timestamps(); }); }

por facilidad creamos un seeder:

php artisan make:seeder WinesTableSeeder

y editamos el archivo generado WinesTableSeeder.php:


public function run()
{
Wine::create([
'name' => 'Classic Chianti',
'description' => 'Un vino de cuerpo medio caracterizado por una frescura maravillosa con un final persistente y afrutado',
'color' => 'red',
'grape_variety' => 'Sangiovese',
'country' => 'Italy'
]);

Wine::create([
'name' => 'Bordeaux',
'description' => 'Un vino con aromas frutales y sabores de mora, cereza oscura, vainilla, grano de café y regaliz. Los vinos suelen ser concentrados, potentes, firmes y tánicos',
'color' => 'red',
'grape_variety' => 'Merlot',
'country' => 'France'
]);

Wine::create([
'name' => 'White Zinfandel',
'description' => 'Abreviado a menudo como White Zin, es un vino seco a dulce, rosado',
'color' => 'rosé',
'grape_variety' => 'Zinfandel',
'country' => 'USA'
]);

Wine::create([
'name' => 'Port',
'description' => 'Un vino tinto dulce fortificado, a menudo servido como un vino de postre',
'color' => 'red',
'grape_variety' => 'Touriga Nacional',
'country' => 'Portugal'
]);

Wine::create([
'name' => 'Prosecco',
'description' => 'Es un vino blanco seco (brut), a veces con un sabor dulce a manzana verde, melón dulce, pera y madreselva',
'color' => 'white',
'grape_variety' => 'Glera',
'country' => 'Italy'
]);
}


en el archivo DatabaseSeeder.php
invocamos al seeder:

public function run()
{
$this->call(WinesTableSeeder::class);
}

instalamos una libreria que nos permita crear facilmente Schemas y Types:

composer require rebing/graphql-laravel

luego debemos ejecutar este comando para copiar el archivo graphql.php de la carpeta del vendor a la carpeta config de la aplicacion:

php artisan vendor:publish --provider="Rebing\GraphQL\GraphQLServiceProvider"

Crear los Types:

php artisan make:graphql:type WineType

namespace App\GraphQL\Type;

use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Type as GraphQLType;

class WineType extends GraphQLType
{
protected $attributes = [
'name' => 'Wine',
'description' => 'Detalles sobre un vino',
'model' => Wine::class
];

public function fields()
{
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'Id del vino',
],
'name' => [
'type' => Type::nonNull(Type::string()),
'description' => 'nombre del vino',
],
'description' => [
'type' => Type::nonNull(Type::string()),
'description' => 'descripcion corta del vino',
],
'color' => [
'type' => Type::nonNull(Type::string()),
'description' => 'color del vino',
],
'grape_variety' => [
'type' => Type::nonNull(Type::string()),
'description' => 'variedad de uva del vino',
],
'country' => [
'type' => Type::nonNull(Type::string()),
'description' => 'pais de origen del vino',
]
];
}
}

Crear las queries GraphQl

php artisan make:graphql:query WinesQuery

namespace App\GraphQL\Query;

use App\Wine;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use GraphQL;
use Rebing\GraphQL\Support\SelectFields;

class WinesQuery extends Query
{
protected $attributes = [
'name' => 'wines',
];

public function type()
{
return Type::listOf(GraphQL::type('Wine'));
}

public function args()
{
return [
'id' => [
'name' => 'id',
'type' => Type::int()
],
];
}
public function resolve($root, $args)
{
if(isset($args['id']))
{
return Wine::where('id','=',$args['id'])->get();
}
return Wine::all();

}
}

donde,
Método type lo que hace es definir que es lo que devolveremos, le decimos que es una lista (array) de vinos, que previamente creamos y añadimos a la config de graphql.
Método args podemos pasarle atributos a una consulta, por ejemplo, orderby, order, numero de pagina, etc. Nosotros le pasamos el id para que tenga la posibilidad de solo obtener un vino
Método resolve: Básicamente aquí devolvemos el array con los datos que se solicitaron, la librería ya sabe asociar las relaciones y lo que requiere por tanto solo hay que llamar a Eloquent como hacemos habitualmente. También aquí podemos hacer filtros o alguna necesidad especial que tengamos.

registramos esta Query en graphql.php

'schemas' => [
'default' => [
'query' => [
'wines' => App\GraphQL\Query\WinesQuery::class,
]
],
],




 y tambien el Type:

'types' => [
'Wine' => App\GraphQL\Types\WineType::class,
],


Creamos las tablas y las poblamos con el seed:

php artisan migrate
php artisan db:seed # run the server
Levantamos el servidor interno:

php artisan serve

para probar nuestra queries podemos usar una extension de Chrome:

https://chrome.google.com/webstore/detail/chromeiql/fkkiamalmpiidkljmicmjfbieiclmeij?source=post_page---------------------------

y escribimos nuestra url :

http://127.0.0.1:8000/graphql

{
  wines {
    name, color
    }
}

traeria todos los vinos mostrando su nombre y color


{
  wines(id:1){
    id,name
  }
}

traeria el id y el nombre del vino con id=1

{
  "data": {
    "wines": [
      {
        "id": 1,
        "name": "Classic Chianti"
      }
    ]
  }
}

fuente:

https://auth0.com/blog/developing-and-securing-graphql-apis-with-laravel/

https://medium.com/@devfelipe.mansilla/usando-graphql-con-laravel-446f0de02424

laravel5.8 crud

antes que nada:

Añadí las siguiente líneas de código en app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Schema;

public function boot()
{
    Schema::defaultStringLength(191);
}
antes de hacer

php artisan migrate

( para rehacer todas las tablas: php artisan migrate:fresh )

-----------------------

crear el modelo (para nuestro ejemplo se creara uno llamado "Share")

php artisan make:model Share -m

editar el archivo de migracion creado para Share

y agregarle los campos del modelo:

$table->string('share_name'); 
$table->integer('share_price'); 
$table->integer('share_qty');


ejecutar la migracion para crear la tabla:

php artisan migrate

en Share.php agregar los campos a la propiedad fillable:

protected $fillable = [ 'share_name', 'share_price', 'share_qty' ];

crear el Controlador de Share con su respectivo recurso que haga referencia a los endpoints
del crud

php artisan make:controller ShareController --resource

editar routes/web.php
y agregar este nuevo recurso:

Route::resource('shares', 'ShareController');

crear en resources/views/ una carpeta nueva llamada shares en donde residiran 

las vistas de create/edit/index

los cuales deben un layout
empecemos por crear uno:

layout.blade.php

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Laravel 5.7 CRUD Example Tutorial</title> <link href="{{ asset('css/app.css') }}" rel="stylesheet" type="text/css" /> </head> <body> <div class="container"> @yield('content') </div> <script src="{{ asset('js/app.js') }}" type="text/js"></script> </body> </html>

en resources/views

crear la vista para la creacion de un Share llamado

create.blade.php

la cual se llamar desde el metodo create del controlador

en ShareController.php


  public function create()
    {
         return view('shares.create');
    }

http://localhost:8000/shares/create



Para guardar los datos en ShareController.php
implementamos el metodo store


use App\Share;
public function store(Request $request)
    {
      $request->validate([
        'share_name'=>'required',
        'share_price'=> 'required|integer',
        'share_qty' => 'required|integer'
      ]);
      $share = new Share([
        'share_name' => $request->get('share_name'),
        'share_price'=> $request->get('share_price'),
        'share_qty'=> $request->get('share_qty')
      ]);
      $share->save();
      return redirect('/shares')->with('success', 'Stock has been added');
    }


el cual si es exitoso va a llamar al metodo index, por lo que vamos a implementarlo asi:

  public function index()
    {
        $shares = Share::all();

        return view('shares.index', compact('shares'));
    }

y creamos la vista index.blade.php

@extends('layout')

@section('content')
<style>
  .uper {
    margin-top: 40px;
  }
</style>
<div class="uper">
  @if(session()->get('success'))
    <div class="alert alert-success">
      {{ session()->get('success') }}  
    </div><br />
  @endif
  <table class="table table-striped">
    <thead>
        <tr>
          <td>ID</td>
          <td>Stock Name</td>
          <td>Stock Price</td>
          <td>Stock Quantity</td>
          <td colspan="2">Action</td>
        </tr>
    </thead>
    <tbody>
        @foreach($shares as $share)
        <tr>
            <td>{{$share->id}}</td>
            <td>{{$share->share_name}}</td>
            <td>{{$share->share_price}}</td>
            <td>{{$share->share_qty}}</td>
            <td><a href="{{ route('shares.edit',$share->id)}}" class="btn btn-primary">Edit</a></td>
            <td>
                <form action="{{ route('shares.destroy', $share->id)}}" method="post">
                  @csrf
                  @method('DELETE')
                  <button class="btn btn-danger" type="submit">Delete</button>
                </form>
            </td>
        </tr>
        @endforeach
    </tbody>
  </table>
<div>
@endsection

editar 
ShareController.php

public function edit($id)
    {
        $share = Share::find($id);

        return view('shares.edit', compact('share'));
    }

y creamos la vista edit.blade.php

@extends('layout')

@section('content')
<style>
  .uper {
    margin-top: 40px;
  }
</style>
<div class="card uper">
  <div class="card-header">
    Edit Share
  </div>
  <div class="card-body">
    @if ($errors->any())
      <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
            @endforeach
        </ul>
      </div><br />
    @endif
      <form method="post" action="{{ route('shares.update', $share->id) }}">
        @method('PATCH')
        @csrf
        <div class="form-group">
          <label for="name">Share Name:</label>
          <input type="text" class="form-control" name="share_name" value={{ $share->share_name }} />
        </div>
        <div class="form-group">
          <label for="price">Share Price :</label>
          <input type="text" class="form-control" name="share_price" value={{ $share->share_price }} />
        </div>
        <div class="form-group">
          <label for="quantity">Share Quantity:</label>
          <input type="text" class="form-control" name="share_qty" value={{ $share->share_qty }} />
        </div>
        <button type="submit" class="btn btn-primary">Update</button>
      </form>
  </div>
</div>
@endsection


e implementamos el metodo que actualiza el modelo editado:

public function update(Request $request, $id)
     {
       $request->validate([
         'share_name'=>'required',
         'share_price'=> 'required|integer',
         'share_qty' => 'required|integer'
       ]);

       $share = Share::find($id);
       $share->share_name = $request->get('share_name');
       $share->share_price = $request->get('share_price');
       $share->share_qty = $request->get('share_qty');
       $share->save();

       return redirect('/shares')->with('success', 'Stock has been updated');
     }


Borrar un registro


implementamos el metodo destroyde ShareController

public function destroy($id)
{
     $share = Share::find($id);
     $share->delete();

     return redirect('/shares')->with('success', 'Stock has been deleted Successfully');
}





linux ubuntu mint actualizar chrome

 desde una terminal: $ sudo apt update $ sudo apt install google-chrome-stable