Laravel lighthouse paginate without model - laravel

Is there a way to use the #paginate directive from lighthouse-php without querying data from a model? Let say, i using a third party library to query data using an api or so.

Fortunately, such a feature has been added very recently: https://github.com/nuwave/lighthouse/pull/2232. This PR added support for returning data in a Paginator from option resolver in #paginator directive.
You can provide your own function that resolves the field by directly returning data in a \Illuminate\Contracts\Pagination\Paginator instance.
This is mutually exclusive with builder and model. Not compatible with scopes and builder arguments such as #eq.
type Query {
posts: [Post!]! #paginate(resolver: "App\\GraphQL\\Queries\\Posts")
}
A custom resolver function may look like the following:
namespace App\GraphQL\Queries;
use Illuminate\Pagination\LengthAwarePaginator;
final class Posts
{
/**
* #param null $root Always null, since this field has no parent.
* #param array{} $args The field arguments passed by the client.
* #param \Nuwave\Lighthouse\Support\Contracts\GraphQLContext $context Shared between all fields.
* #param \GraphQL\Type\Definition\ResolveInfo $resolveInfo Metadata for advanced query resolution.
*/
public function __invoke($root, array $args, GraphQLContext $context, ResolveInfo $resolveInfo): LengthAwarePaginator
{
//...apply your logic
return new LengthAwarePaginator([
[
'id' => 1,
'title' => 'Flying teacup found in solar orbit',
],
[
'id' => 2,
'title' => 'What actually is the difference between cookies and biscuits?',
],
], 2, 15);
}
}
(The docs are currently not getting updated correctly, which is why you probably did not find out about this. I am working on restoring the deployment.)

Related

Array is returned as an object using resource

something strange is going on.
I got an array like this:
=> [
"optionalinformation" => [
"domain" => [
"type" => "string",
],
],
]
This array is used by a resource and if I use tinker to check this resource like this:
$result = App\Http\Resources\ProductResource::make(Product::find(2));
is_array($result->optionalinformation);
In this case the result is true: This is an array.
But if axios fetches the result, I am getting this:
"optionalinformation": {
"domain": {
"type": "string"
},
It's no longer an array but an object. Any ideas why this is happening?
This is my api-resource:
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
*
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'title' => $this->title,
'optionalinformation' => $this->optionalinformation,
];
}
There is a bit of confusion here, mostly caused by PHP lingo.
In PHP lingo an associative array is still an array. But an associative array is actually a dictionary.
Other programming languages don't see an associative array (dictionary) as an array and as such have a different vocabulary.
Your data structure is actually a dictionary, and not a numerical indexed array.
From a JSON perspective if your data structure has non-numerical keys then it gets translated to an object.
Your confusion stems from the fact that is_array will return true if the variable is a zero based indexed array, when in fact it returns true for associate arrays also.
It's in the definition. Laravel's resource classes allow you to expressively and easily transform your models and model collections into JSON. Check the Resource docs
If you are expecting an array in return then I suggest skip resource and pass the data directly from the controller using ->toArray(). But then again you are using axios in your vuejs then I strongly recommend to stick with json format as an expected response.

How does escaping work on DB insert in laravel?

I have a basic resource, and I'd like to know if I must escape form data or laravel does it for me behind the scenes?
public function store(ProductRequest $request, Product $product)
{
$fields = [
'owner_id' => 1,
'title' => $request -> title,
];
$product -> create($fields);
return view('product');
}
if I pass
';drop table products;
as the title, it is being stored as is, yet I still have the table, how?
in App\Http\Kernel.php I've the default middlewares
laravel -v: 2.0.1
Laravel uses parameter binding in order to protect you against SQL injection out of the box if you use Eloquent.
If you need to use the query builder than you should use this approach to bind parameters passed to the query for SQL injection protection.

How to ignore field from database operation in Laravel Nova

I am developing a Web Application using Laravel Nova. Laravel Nova is quite new. I am now having problem with database relationship and Fields. I like to ignore a field from database operations. This is my scenario.
In the Job resource, I have this fields method
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Name', 'name'),
Text::make('Email', 'email'),
Select::make('Contract Types')->options($array_of_options)//I want to ignore this field
];
}
As you can see, the last field is Contract Types.
When I create a new job from Dashboard, it is throwing error because there is no contract_types column on the Job model. I like to ignore that field from database operation. How can I get it?
The accepted answer is not completely correct. It prevents storing the value in the database but also completely hides the field in the form. In some weird cases you might want to show a field that is not stored.
My suggestion would be to add the following to the resource (or put in somewhere more re-usable if you want this in multiple resources):
public static function fill(NovaRequest $request, $model)
{
return static::fillFields(
$request, $model,
(new static($model))->creationFieldsWithoutReadonly($request)->reject(function ($field) use ($request) {
return in_array('ignoreOnSaving', $field->meta);
})
);
}
In the relevant field(s) you could than add:
->withMeta(['ignoreOnSaving'])
This will give you a field to fill without saving it in the model.
According to the docs https://nova.laravel.com/docs/1.0/resources/fields.html#showing-hiding-fields
Select::make('Contract Types')
->options($array_of_options)
->hideWhenCreating()
You can make an own custom handling of the Field data, just use fillUsing() method of the Field class. An example
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Name', 'name'),
Text::make('Email', 'email'),
Select::make('Contract Types', 'unique_key_for_model')
->options($array_of_options)
->fillUsing(function(NovaRequest $request, $model, $attribute, $requestAttribute) {
/*
$request->input('unique_key_for_model') // Value of the field
$model->unique_key_for_model // DOES NOT exists, so no errors happens
*/
// or just return null;
return null;
}),
];
}

Eloquent casting attributes to Collections unexpected behaviour

Q1. I have an Eloquent model that casts an attribute to a Collection.
Calling Collection's method on this attribute doesn't affect the model values. Eg: put()
When using Collections , iam able to do this :
$var = collect();
$var->put('ip', '127.0.0.1');
var_dump($var);
Output as expected :
object(Illuminate\Support\Collection)[191]
protected 'items' =>
array (size=1)
'ip' => string '127.0.0.1' (length=4)
But when i use with a casted attribute on a Eloquent model, this doesn't work as expected
$user = App\User::create(['email'=>'Name', 'email'=>'mail#example.com', 'password'=>bcrypt('1234')]);
$user->properties = collect();
$user->properties->put('ip', '127.0.0.1');
var_dump($user->properties);
object(Illuminate\Support\Collection)[201]
protected 'items' =>
array (size=0)
empty
This doesn't populate the field.
I think that another collection is created, so to work as expected i must assign this new collection to my field.
Like so :
$user->properties = $user->properties->put('ip', '127.0.0.1');
Q2. Is there a proper way to initialize collection of the field by default (create an empty collection if the field is null), without having to call $user->properties = collect(); "manually" every time?
User.php
class User extends Authenticatable
{
protected $casts = [
'properties' => 'collection',
];
...
}
Migration file
Schema::table('users', function($table) {
$table->text('properties')->nullable();
});
Q1: an attribute casted to collection has a getter that returns, each time, a new BaseCollection that is constructed on the value of the attribute.
As already supposed the getter returns another collection instance and every direct change on it does not change the value of the attribute but instead the newly created collection object.
As also pointed by you the only way to set a a collection casted attribute is to assign it his own original value merged with new ones.
So instead of put() you have to use:
$user->properties = $user->properties->put('ip', '127.0.0.1');
// or
$user->properties = $user->properties ->merge(['ip'=>'127.0.0.1'])
Q2: We have to think that the database representation is a text; so IMHO the proper way to initialize a Model in the migration is to give it a default empty json, i.e.:
$table->text('properties')->default('{}');
But this works only for models created without setting the property field and retrieved after.
For a newly created Model my advice is to pass a default void array, i.e.:
App\User::create([
'name'=>'Name',
'email'=>'mail#example.com',
'password'=>bcrypt('1234'),
'properties' => []
]);
In addition to dparoli's outstanding answer, it is also possible to add a default value through Laravel's boot method, which is available on every Model.
Something like the following example code
protected static function boot()
{
parent::boot(); //because we want the parent boot to be run as well
static::creating(function($model){
$model->propertyName = 'propertyValue';
});
}
You can play with this approach if you like as well.

Laravel, how cast object to new Eloquent Model?

I get via Request a Json Object.
I clearly parse this object in order to check if it may fit the destination model.
Instead of assigning property by property. Is there a quick way to populate the model with the incoming object?
If you have an array of arrays, then you can use the hydrate() method to cast it to a collection of the specified model:
$records = json_decode($apiResult, true);
SomeModel::hydrate($records);
If you just have a single record, then you can just pass that array to the model’s constructor:
$model = new SomeModel($record);
Just pass your object casted to array as Model constructor argument
$model = new Model((array) $object);
Internally this uses fill() method, so you may first need to add incoming attributes to $fillable property or first create model and then use forceFill().
You should convert that object to array and use fill($attributes) method.
As method name says, it will fill object with provided values. Keep in mind that it will not persist to database, You have to fire save() method after that.
Or if You want to fill and persist in one method - there is create($attributes) which runs fill($attributes) and save() under the hood.
You can use Mass Assignment feature of Laravel,
You model would look like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['name', 'email', 'phone'];
}
And the process of populating the Model would be like this:
// This would be your received json data converted to array
// use 'json_decode($json, true)' to convert json data to array
$json_arr = [
'name' => 'User Name',
'email' => 'email#example.com',
'phone' => '9999999999'
];
$user = new \App\User($json_arr);
Hope this helps!
Castings may fail due to several reasons. A safe way is to add a static function to the model to generate from both array or object. feels like an extension to the model.
public static function generateFromObject($object)
{
$myModel = new MyModel();
foreach($object as $k => $v)
$myModel->{$k} = $v; //for arrays $myModel[$k] = $v;
return $myModel;
}
and you can use anywhere like,
$myModel = MyModel::generateFromObject($myObjectOrArray)->save();

Resources