Get all the related fields in one json response - laravel

i want to make an api with laravel and i want to get all the fields like the following format
projects: [
{
id,
name,
logo,
thumbnail,
phases: [{
id,
name,
date,
views: [{
id,
name,
thumbnail,
images: [{
id,
path: [ 5 urls for the various images ]
date
}]
}]
}]
}
]
my database model like the following
- projects -> hasmany phases
- phases -> hasmany view
- views -> hasmany images
the model like the following
class Project extends \Eloquent {
// Add your validation rules here
public static $rules = [
'title' => 'required',
];
// Don't forget to fill this array
protected $fillable = [ 'title', 'desc' ];
public function phases() {
return $this->hasMany('Phase');
}
}
class Phase extends \Eloquent {
protected $fillable = [ 'title', 'from', 'to', 'project_id' ];
public static $rules = [
'title' => 'required',
'from' => 'required',
'to' => 'required'
];
public function views() {
return $this->hasMany( 'View' );
}
public function project(){
return $this->belongsTo('Project');
}
}
class View extends \Eloquent {
// Add your validation rules here
public static $rules = [
'title' => 'required',
'image_path' => 'required'
];
// Don't forget to fill this array
protected $fillable = [ 'title', 'image_path', 'caption', 'view_date', 'phase_id' ];
public function phase(){
return $this->belongsTo('Phase');
}
}
How can i get all the json in the index response, i use the following but not getting the same format
Project::all();

You have to eager load the datasets:
Project::with('phases.views','phases.images')->get();
Looks like you don't have the images in your model as a related model, but you do have projects. But your json example shows images and not projects, is this right?

Relationships aren't loaded by default when retrieving a collection (this is called lazy loading). But you can load them by using the with() method (this is called eager loading):
Project:all()->with('phases')->get();
You can also chain nested relationships with dot notation:
Project:all()->with('phases.views')->get();

Related

How to set default value for a field in laravel `morphMany` relationship (not database tier)

I have a model File where save files of my app, it like:
class File{
public const IMAGE_TYPE = 'image';
public const AUDIO_TYPE = 'audio';
public const VIDEO_TYPE = 'video';
public const APPLICATION_TYPE = 'application';
protected $fillable = ['path', 'type', 'description', 'order', 'filable_type', 'filable_id'];
}
Suppose I have an Post model, it like:
class Post{
public function videos(){
return $this->morphMany(File::class, 'filable')
->where('type', File::VIDEO_TYPE);
}
public function images(){
return $this->morphMany(File::class, 'filable')
->where('type', File::IMAGE_TYPE);
}
}
When I get data of above relationships it's okay
But when I create a new file of post it is repetitive and easily make mistakes
$post->images()->create([
'path' => 'my-image.jpg',
'type' => File::IMAGE_TYPE,
]);
$post->videos()->create([
'path' => 'my-image.mp3',
'type' => File::VIDEO_TYPE,
]);
I want it look like:
$post->images()->create([
'path' => 'my-image.jpg',
]);
$post->videos()->create([
'path' => 'my-image.mp3',
]);
I don't need declare type per creating videos or images of a post.
How I can accomplish this!
Modal
// Change morphMany to hasMAny
public function videos()
{
return $this->hasMany(File::class, 'fileable')
->where('type', File::IMAGE_TYPE);
}
Controller
// You can do this
$vedioToCreate = $post->videos();
$vedioToCreate->path = 'my-image.mp3';
$vedioToCreate->save();
// Or you can do this
$post->videos()->create([
'path' => 'my-image.mp3',
]);

How to use relationships in laravel 8

my question has two parts
Firstly, My if statement is not working. My if statement is as followed:
if ($request->is_published) {
$resources_page->published_at = now();
}
This is stored in my controller, I have a model for this and it is as followed:
public function is_published()
{
return $this->published_at !== null;
}
It is meant to check whether my checkbox is checked and return the timestamp, I have it cast in my model like followed:
protected $casts = [
'published_at' => 'datetime',
];
And in my view:
#include('components.form.input-checkbox', [
'label' => 'Publish?',
'form_object' => 'page',
'name' => 'is_published'
])
Could anyone elude to the answer?
Secondly, when trying to sync, it is not storing to my resources_category_resources_page table
In my controller, i have the following code
$resources_page->resources_categories()->sync(
ResourcesCategory::whereIn('slug', $request->resources_categories)->pluck('id')
);
In my model I have the relationships declared properly, so I don't know why its not storing?

Eloquent eager loading specific columns

I have two models :Product and category
which are linked by a one-to-many relationship. A category has several products. I would like to select specific columns from each model.
Here is the query I have, but I have all the columns with category_id, but I want the category name instead of id. How can I do that. Thank you in advance.
here is the method in controller
$products = Product::with('categories:id,name')->get();
if ($products) {
$response = ['api_status' => 1, 'api_message' => 'success', 'data' => $products];
return response()->json($response);
} else {
$response = ['api_status' => 0, 'api_message' => 'Error'];
return response()->json($response);
}
Here is category model
class Categorie extends Model
{
use HasFactory, SoftDeletes;
protected $fillable =['name','redirect'];
public function products()
{
return $this->hasMany(product::class);
}
}
and the product model is:
class Product extends Model
{
use HasFactory, SoftDeletes;
protected $fillable = [
'name',
'description',
'detail', 'img',
'categorie_id', 'onSale',
'costPrice', 'inStock', 'salePrice'
];
public function categories()
{
return $this->belongsTo(Categorie::class);
}
}
here is the response:
To modify the output of your model I'd suggest using an API resource. This will give you more granular control about how a resource is returned by the API. A resource is also the best point to modify certain values.
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'description' => $this->description,
'detail' => $this->detail,
'img' => $this->img,
'category_id' => $this->categorie->name,
'category_name' => $this->categorie->name,
'onSale' => $this->onSale,
'costPrice' => $this->costPrice,
'inStock' => $this->inStock,
'salePrice' => $this->salePrice,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
'deleted_at' => $this->deleted_at,
'categories' => $this->categories ?? null,
];
}
}
This way you can manually specify which values your response should have.
In your controller you can include the populated array in your response by manually filling the toArray method with the current request object or just by using the resolve method which basically does the previous task for you:
$response = [
'api_status' => 1,
'api_message' => 'success',
'data' => ProductResource::collection($products)->resolve()
];
You can select particular fields from the relationship but you always need to select any keys involved in the relationship:
$products = Product::with('categories:id,name')->get();
Now each Product has its 'categories' loaded and those Category models only have the id and name fields.
Importantly:
The relationship categories is named incorrectly, it should be categorie in this case as the foreign key on Product is categorie_id and it is a singular relationship, it does not return multiple results.
Product::with('categorie:id,name')->get()
If you want to keep the name categories you would have to define the foreign key used when defining the belongsTorelationship, the second argument.
If you need to transform the structure of any of this that is a different thing and you will be walking into transformers or an API Resource.
Not sure how you want your data to look but this is the structure you will have by eager loading records, so if you need a different structure then what you get you will have to show an example.

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

Yii2: Labels with attributeLabels() in GridView

I generated a model which got the function attributeLabels():
public function attributeLabels()
{
return [
'column1' => 'name of column1',
'column2' => 'name of column2',
];
}
I also wrote a function in my model, which creates a SQL statement:
public function getReminders()
{
$sql = Yii::$app->db->createCommand('SELECT
[[column1]], [[column2]]
FROM {{%table}}
WHERE column1 > :value')
-> bindValue(':value', '10');
return $sql;
}
Then I created a controller with an actionIndex() function:
public function actionIndex()
{
$model = new TestObject();
$testmodels= new ActiveDataProvider([
'models' => $model->getReminders()->queryAll(),
]);
return $this->render('index', [
'testmodels' => $testmodels,
]);
Finally I created a View
GridView::widget([
'dataProvider' => $testmodels,
'layout' => '{items}{pager}',
'columns' => ['column1', column2,],
]);
For some reason, the column names of the resulting page are column1 and column2. I have tried to create a listView and got the correct names for the columns. How comes that the column names are not set by attributeLabels() in this case? I really don't want to use the label property in the gridview.
Thank you in advance!
If you just need to change the label for the gridview column you can configure you column with proper value for label
GridView::widget([
'dataProvider' => $testmodels,
'layout' => '{items}{pager}',
'columns' => [
[
'attribute'=> column1',
'label' => 'Your Lable for column 1',
],
[
'attribute'=> column2',
'label' => 'Your Lable for column 2',
],
],
]);
you can take a look at thsi for a brief guide http://www.yiiframework.com/doc-2.0/guide-output-data-widgets.html or here for ref http://www.yiiframework.com/doc-2.0/yii-grid-datacolumn.html
I would do the following:
make the base class for the TestObject be ActiveRecord
Override tableName
Simplify getReminders, make it static, and let it return Query not a Command
class TestModel extends yii\db\ActiveRecord
{
...
public static function tableName()
{
return {{%table}};
}
public static function getReminders()
{
$query = self::find()->select(['column1', 'column2'])
->where(['>', 'column1', 10]);
return $query;
}
...
}
Don't set models of the ActiveDataProvier and instead set query
'models' => TestObject::getReminders(),

Resources