Eloquent Select Query On JSON Columns - laravel

I have a JSON column for properties table. Below is the eloquent query to pick specific fields.
Company::select('id', 'information')
->with([
'properties' => function($sql) {
return $sql->select('id', 'company_id', 'information->>status');
}
])
->get();
Is there a way to replace the response with the actual key?

The simplest method to get the key would be to to give it an alias. So something like
$sql->select('id', 'company_id', 'information->status as status');
$sql->select('id', 'company_id', 'information->status as information_status');
$sql->select('id', 'company_id', 'information->status as information.status');
Edit
If you wish to maintain the structure of the response, then its better to create a Resource and let it handle the structure. So
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Http\Resources\Json\ResourceCollection;
class Properties extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$information = ['status'=>optional($this->information)['status']];
return [
'id' => $this->id,
'company_id' => $this->company_id,
'information' => $information,
];
}
}
Similarly create a Resource for Company with the fields you want.

Related

How to customize Laravel's Collection API resources?

I tried the following but it doesn't work:
ArticleController.php:
public function index()
{
$articles = Article::latest()->paginate(10);
return ArticleCollection::collection($articles);
}
ArticleCollection.php:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class ArticleCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
*/
public function toArray($request)
{
return [
'name' => $this->title,
'subtitle' => $this->subtitle,
'cover' => $this->cover,
];
}
}
This of transforming the model seems to work for a single model, but not for a collection. How should i customize which fields will be returned when working with collections API resources?
Let your DB query define the final fields
Check this way, in your ArticleController create a query with a select() method in which you define which DB fields are sent to the ArticleCollection.
The collection will only take care of returning the that that was given to it!
public function index()
{
return new ArticleCollection(
Article::latest()->select(['name', 'subtitle', 'cover'])->paginate(10)
);
}
Result query
select
`name`,
`subtitle`,
`cover`
from
`articles`
order by
`created_at` desc
limit
10 offset 0
ArticleCollection
This file can be set as default, this allows you to dynamically set values passed from the select() method on the ArticleController.
class ArticleCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
}
You can use it like this:
return new ArticleCollection($articales);

How get Date format in notifications collection

I need to define the date format for a collection. I tried it that way, but it doesn't work:
return response()->json($request->user()->notifications()->format('d/m/Y')->limit(7)->get());
How could I set the date format for an entire collection?
I reckon you are using database notifications and you want to format the created_at field.
To return the results fast, you might do something like this:
$notifications = $user->notifications()
->limit(7)
->get()
->each(function ($notification) {
$notification->formatted_created_at = $notification->created_at->format('d/m/Y');
});
I suggest that you do it properly and create a new API resource.
use Illuminate\Http\Resources\Json\JsonResource;
class NotificationResource extends JsonResource
{
/**
* Transform the notification into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
// ...
'formatted_created_at' => $this->created_at->format('d/m/Y'),
];
}
}
// In the controller action
$notifications = $user->notifications()
->limit(7)
->get();
return NotificationResource::collection($notifications);

How to Paginate loaded relation with Laravel API resources

I need to load model relations in it's resource and paginate them.
In my case i have Category and Path models, plus CategoryResource and PathResource
The toArray method of CategoryResource is like below:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'order' => $this->order,
'paths' => PathResource::collection($this->whenLoaded('paths'))
];
}
and toArray method of PathResource is like below:
public function toArray($request)
{
return parent::toArray($request);
}
Question is how can i load and paginate related Path's in my CategoryResource?
I had same problem and solved it this way:
Prerequisites
You must have/create a resource for Path model i.e. PathResource
to create one use this command:
php artisan make:resource PathResource
Solution
The solution is to use laravel paginate on relation and use transform method on the paginated collection to convert it's items to your resource.
First Step
Create a base class for paginating any resource in your app, using this command:
php artisan make:resource PaginatedCollection -c
Edit the PaginatedCollection and add following codes:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PaginatedCollection extends ResourceCollection
{
/**
* An array to store pagination data that comes from paginate() method.
* #var array
*/
protected $pagination;
/**
* PaginatedCollection constructor.
*
* #param mixed $resource paginated resource using paginate method on models or relations.
*/
public function __construct($resource)
{
$this->pagination = [
'total' => $resource->total(), // all models count
'count' => $resource->count(), // paginated result count
'per_page' => $resource->perPage(),
'current_page' => $resource->currentPage(),
'total_pages' => $resource->lastPage()
];
$resource = $resource->getCollection();
parent::__construct($resource);
}
/**
* Transform the resource collection into an array.
* now we have data and pagination info.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
// our resources
'data' => $this->collection,
// pagination data
'pagination' => $this->pagination
];
}
}
Second Step
make a collection resource for your model and extend PaginatedCollection
instead of default ResourceCollection.
Run this command to do so:
php artisan make:resource PathCollection -c
Now edit your new collection class PathCollection and override toArray method:
/**
* Transform the resource collection into an array.
*
* In this method use your already created resources
* to avoid code duplications
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
// Here we transform any item in paginated items to a resource
'data' => $this->collection->transform(function ($path) {
return new PathResource($path);
}),
'pagination' => $this->pagination,
];
}
Final Step
In your CategoryResource use PathCollection like this:
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'order' => $this->order,
'paths' => new PathCollection(
new LengthAwarePaginator(
$this->whenLoaded('paths'),
$this->paths_count,
10
)
),
];
and make sure you import LengthAwarePaginator class:
use Illuminate\Pagination\LengthAwarePaginator;
Usage
$category = Category::with('paths')->withCount('paths')->find(1);
return new CategoryResource($category);
You should probably checkout the documentation on Resources and ResourceCollections. ResourceCollections will allow you to easily paginate your resources. Api Resource Collection Pagination Documentation

Laravel - How to dynamically resolve an instance of a model that has different Namespace?

tldr:
How do you dynamically get an instance of a model just by its DB table name?
What you get from the request:
ID of the model
table name of the model (it varies all the time!)
What you don't know:
Namespace of the model
Longer explanation:
I have a reporting system, that users can use to report something. For each reporting, the ID and the table name is sent.
Until now, every model was under the Namespace App\*. However, since my project is too big, I needed to split some code into Modules\*
Here is an example, how the report is saved in the database:
Example:
Request contains rules:
public function rules()
{
return [
'id' => 'required|string',
'type' => 'required|in:users,comments,offer_reviews, ......',
'reason' => 'required|string',
'meta' => 'nullable|array',
'meta.*' => 'string|max:300'
];
}
In the database, we save the data into :
id reportable_type ...
1 App\User ...
4 Modules\Review\OfferReview ...
How would you create an instance of a model dynamically, when you just know the database table name for example offer_reviews?
There is one solution that jumps to my mind, however, I'm not sure if it adds more security issues. What is if the user sends the full namespace + class name? With that, I know directly where to resolve an instance.
Have a look what I'm doing right now
(before I changed to modules)
//In my controller
class ReportController extends Controller
{
/**
* Stores the report in DB.
*/
public function store(StoreReportRequest $request)
{
$model = $request->getModel();
$model->report([
'reason' => $request->reason,
'meta' => $request->meta
], auth()->user());
return response()->json(['status' => 'Submitted'], 201);
}
}
//in StoreReportRequest
/**
* Gets the Model dynamically.
* If not found we throw an error
* #return \App\Model
*/
public function getModel()
{
return get_model($this->type)
->findOrFail(\Hashids::decode($this->id)[0]);
}
//in Helpers
/**
* Gets me the model of a table name.
* #param String $table Has to be the name of a table of Database
* #return Eloquent The model itself
*/
function get_model($table)
{
if (Schema::hasTable(strtolower($table))) {
return resolve('App\\' . Str::studly(Str::singular($table)));
}
throw new TableNotFound;
}
I don't know if there is a better solution, but here you go. My code is looking for a method with namespace when it's not found we are using App\ as the namespace.
Maybe this code helps someone :)
class StoreReportRequest extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'id' => 'required|string',
'type' => 'required|in:mc_messages,profile_tweets,food,users,comments,offer_reviews,user_reviews',
'reason' => 'required|string',
'meta' => 'nullable|array',
'meta.*' => 'string|max:300'
];
}
/**
* Gets the Model dynamically.
* If not found we throw an error
* #return \App\Model
*/
public function getModel()
{
$namespace = $this->getNamespace();
return $this->resolveModel($namespace);
}
protected function getNamespace(): string
{
$method = $this->typeToMethod();
if (method_exists($this, $method)) {
return $this->$method();
}
return 'App\\';
}
protected function typeToMethod(): string
{
return 'get' . \Str::studly(\Str::singular($this->type)) . 'Namespace';
}
protected function resolveModel(string $namespace)
{
return get_model($this->type, $namespace)
->findOrFail(\Hashids::decode($this->id)[0]);
}
protected function getOfferReviewNamespace(): string
{
return 'Modules\Review\Entities\\';
}
protected function getUserReviewNamespace(): string
{
return 'Modules\Review\Entities\\';
}
}

access to belongsTo method in same model laravel

I use this model but use this model show below error:
Failed calling App\User::jsonSerialize()
but remove "$this->customer->name" result is ok.
thanksssssssssssssssssssssssssssssssssssssssssssssssssssssssssss.
class User extends Authenticatable
{
/**
* Get the user's customer name.
*
* #param string $value
* #return array
*/
public function getCustomerIdAttribute($value)
{
return [
'id' => $value,
'name' => $this->customer->name
];
}
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'customer_id' => 'array',
];
/**
* Get the customer record associated with the user.
*/
public function customer()
{
return $this->belongsTo(Customer::class);
}
}
Your issue is that $this->customer is returning null, which is causing $this->customer->name to cause an error.
When you json_encode a Model, or convert it to a string, or otherwise call toJson on it, it will call the jsonSerialize() method.
At some point, this ends up calling your getCustomerIdAttribute() accessor you have defined. Inside this accessor, you have the statement $this->customer->name. However, if the current model is not related to a customer record, then $this->customer will return null, and then $this->customer->name will cause an error. When $this->customer->name causes an error, it causes jsonSerialize() to fail.
In your accessor, just make sure to check if $this->customer is valid before attempting to access the name attribute:
public function getCustomerIdAttribute($value)
{
return [
'id' => $value,
'name' => ($this->customer ? $this->customer->name : null)
];
}

Resources