Laravel SQL obtaining a group of records using latestOfMany - php

I'm working inside a Laravel 8 application used as a backend to a front-end. I have 3 models:
Domain
Dns (linked to Domain using belongsTo)
DnsCheck (linked to Dns using belongsTo)
The general idea is that the Domain model holds a website such as example.com, and Dns simply contains metadata about how the DnsCheck's should be performed, and, because the number of DNS types per domain is unknown, DnsCheck has a type column for instance: a or aaaa as there may be more than one record per domain.
Ultimatly, there could be as many as 15 DnsCheck entries, and the Dns model contains an interval column which dictactes how often the DNS of a domain should be checked, so there could be 13 entries generated every hour for instance.
My issue I'm finding now is I'd like to show the end user all of the latest "checks" performed on their domain's DNS and return it as an array that I can loop over, but to the best of my knowledge, on my Dns model the latestOfMany() method would simply give me one result?
How can I get the latest records, I have a checked_at column on my DnsCheck model, I'll attach a screenshot.
Dns model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Dns extends Model
{
use HasFactory;
/**
* Indicates if the model's ID is auto-incrementing.
*
* #var bool
*/
public $incrementing = false;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'dns';
/**
* The attributes that should be cast.
*
* #var array
*/
protected $casts = [
'last_notified_at' => 'datetime',
'last_checked_at' => 'datetime',
'disabled_at' => 'datetime',
// record types that we can monitor
'a_record_is_enabled' => 'boolean',
'aaaa_record_is_enabled' => 'boolean',
'caa_record_is_enabled' => 'boolean',
'cname_record_is_enabled' => 'boolean',
'mx_record_is_enabled' => 'boolean',
'ns_record_is_enabled' => 'boolean',
'soa_record_is_enabled' => 'boolean',
'srv_record_is_enabled' => 'boolean',
'txt_record_is_enabled' => 'boolean',
'ptr_record_is_enabled' => 'boolean'
];
/**
* Get the dns checks that this dns has
*/
public function dns_checks()
{
return $this->hasMany(DnsCheck::class);
}
/**
* Get the latest dns check
*/
public function latest_dns_check()
{
return $this->hasOne(DnsCheck::class)->latestOfMany();
}
/**
* Get the domain that owns this dns
*/
public function domain()
{
return $this->belongsTo(Domain::class);
}
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::deleted(function ($model) {
$model->dns_checks()->delete();
});
}
}
DnsCheck model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class DnsCheck extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'dns_checks';
/**
* Get the dns that owns this dns check
*/
public function certificate()
{
return $this->belongsTo(Dns::class);
}
}

If i understand correctly, it's a quite easy.
Just order the relation (by id or checked_at, i'm ordering by id):
$dns = Dns::with(['dns_checks', => function($q) {
$q->orderBy('id', 'DESC');
}])->find(30); //example
// output example
[
'id' => 30,
'a' => 'google.com',
'dns_checks' => [
[
'id' => 4,
'checked_at' => '2022-07-28 16:46:00',
],
[
'id' => 3,
'checked_at' => '2022-07-27 16:46:00',
],
],
]
To Limit the dns_checks, you can do after orderBy the relation a
$q->limit(YOURLIMITNUMBER);

Related

Laravel saving polymorphic model doesn't have a default value

I'm working on a Laravel 9 project. I'm new to Polymorphic relationships and believe I need to define one.
In my application, I have a parent model called Application that has a single ApplicationGBPayday model, but it could have other models in the future for different products/countries.
I've defined my Application model schema to contain a modelable morph fieldset, and in my controller where I create an Application I then need to create and link the ApplicationGBPayday, but I'm getting an error right now:
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1364 Field 'modelable_type' doesn't have a default value
My controller I'm doing:
// create application
$application = Application::create([
'user_id' => $affiliate->user_id,
'company_id' => $affiliate->company_id,
'country_id' => $affiliate->affiliate_product->country->id,
'product_id' => $affiliate->affiliate_product->product->id,
'serve_method_id' => $affiliate->affiliate_product->serve_method->id,
'application_form_id' => $affiliate->affiliate_product->application_form->id,
'pingtree_group_id' => $affiliate->affiliate_product->pingtree_group->id,
'affiliate_id' => $affiliate->id,
'thread_uuid' => $threadUUID,
'status' => 'pending',
'submitted_at' => $submittedAt
]);
$payday = ApplicationGBPayday::create($request->all());
And here's my model for Application:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Casts\Json;
class Application extends Model
{
use HasFactory, SoftDeletes;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'applications';
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = [];
/**
* The attributes that should be cast.
*
* #var array<string, string>
*/
protected $casts = [
'submitted_at' => 'datetime',
];
/**
* Get the parent modelable model.
*/
public function modelable()
{
return $this->morphTo();
}
}
And ApplicationGBPayday:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use App\Casts\Json;
class ApplicationGBPayday extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'application_gb_paydays';
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = [];
/**
* The attributes that should be cast.
*
* #var array<string, string>
*/
protected $casts = [
'buyer_list' => Json::class,
'buyer_list_pending' => Json::class,
'buyer_list_accepted' => Json::class,
'buyer_list_declined' => Json::class,
'buyer_list_invalid' => Json::class,
'buyer_list_skipped' => Json::class,
'other_data_json' => Json::class,
'response_json' => Json::class,
];
/**
* Get the model's application
*/
public function application()
{
return $this->morphOne(Application::class, 'modelable');
}
}
I'm struggling to figure out how to save the morph fields since they can't be null here, yet I need the application created first and then the payday? What am I missing?
UPDATE
I've tried:
// create application
$application = Application::create([
'user_id' => $affiliate->user_id,
'company_id' => $affiliate->company_id,
'country_id' => $affiliate->affiliate_product->country->id,
'product_id' => $affiliate->affiliate_product->product->id,
'serve_method_id' => $affiliate->affiliate_product->serve_method->id,
'application_form_id' => $affiliate->affiliate_product->application_form->id,
'pingtree_group_id' => $affiliate->affiliate_product->pingtree_group->id,
'affiliate_id' => $affiliate->id,
'thread_uuid' => $threadUUID,
'status' => 'pending',
'submitted_at' => $submittedAt
]);
$payday = new ApplicationGBPayday($request->all());
$application->modelable()->save($payday);
Which gives me:
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1364 Field 'modelable_type' doesn't have a default value (SQL: insert into applications

How to fix Call to undefined method App\Models\TableName::factory?

I have created a seeder that populates data in Laravel but was having a BadMethodCallException Call to undefined method App\Models\Project::factory(). Why is this happening? Below is my seeder code.
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\{
Project, User
};
class ProjectSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
Project::factory()->count(10)->create();
foreach(Project::all() as $project) {
$users = User::inRandomOrder()->take(rand(1, 3))->pluck('id');
$project->users()->attach($users);
}
}
}
First check the project factory class inside path
database/factories/ProjectFactory.php
If it not exist create it
<?php
namespace Database\Factories;
use App\Models\Project;
use Illuminate\Database\Eloquent\Factories\Factory;
class ProjectFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* #var string
*/
protected $model = Project::class;
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
//add your custom seeder data
return [
"project_tittle" => $this->faker->catchPhrase,
"client_name" => $this->faker->name(),
"days" => rand(45, 60),
"description" => $this->faker->text,
"start_date" => $this->faker->date('Y-m-d'),
"end_date" => $this->faker->date('Y-m-d'),
"current_status" => 1,
"completion_percentage" => 0
];
}
}
If your are using different namespace on model you need to add model like this in your factoryclass
protected $model = Project::class;
I hope it works for you
I've solved it temporarily by using DB facade instead of a factory.
use Illuminate\Support\Facades\DB;
DB::table('projects')->insert([
'name' => Str::random(10),
'created_at' => now(),
'updated_at' => now(),
]);

My storage fake doesn't store the image inside the test directory

I'm writing a test for my Item model, it has images to be uploaded and I expect it should be stored inside the testing directory to be cleared each time I ran the test. But it wasn't. Instead, it creates a new directory named item-images in both storage and testing/disks but it uses the actual storage/item-images directory instead of storage/disks/item-images.
Storage::fake('item-images') is already defined on top of the test case, I'm a bit confused by this, please help.
tests/Feature/ItemTest.php
public function test_user_can_store_new_item()
{
Storage::fake('item-images');
Sanctum::actingAs(User::factory()->create());
$images = [
UploadedFile::fake()->image('image1.png'),
UploadedFile::fake()->image('image2.png'),
UploadedFile::fake()->image('image3.png'),
];
$item = Item::factory()->make(['images' => $images]);
$this->postJson(route('items.store'), $item->toArray())
->dump()
->assertCreated()
->assertJsonFragment($item->toArray());
foreach ($images as $image) {
Storage::disk('item-images')->assertExists($image->hashName());
Storage::disk('item-images')->assertMissing('missing.png');
}
}
app/Models/Item.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Item extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'category_id',
'name',
'description',
'history',
];
protected $with = ['category'];
/**
* Get the category that owns the Item
*
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class);
}
}
app/Http/Controllers/ItemController.php
/**
* Store a newly created resource in storage.
*
* #param \App\Http\Requests\RequestItem $request
* #return \Illuminate\Http\Response
*/
public function store(RequestItem $request)
{
$data = $request->validated();
$item = Item::create(Arr::except($data, 'images'));
if ($request->has('images')) {
foreach ($request->file('images') as $image) {
$path = $image->store('item-images');
Image::create(['item_id' => $item->id, 'url' => $path]);
}
}
return response()->json($item, 201);
}
app/Http/Requests/RequestItem.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class RequestItem extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'category_id' => [
'required',
'integer',
],
'name' => [
'required',
'string',
'max:255',
Rule::unique('items', 'name'),
],
'description' => [
'required',
'string',
'max:255',
],
'history' => [
'string',
'max:2500',
],
'images' => [
'required',
'array',
'min:1',
'max:10',
],
];
}
}
I believe the confusing is you are creating a fake file, but actually properly saving it. $image->store('item-images'); doesn't specify the store you are saving it in, but the 'item-images' is the path and it will save it to the default storage. So either Storage::fake() the default storage option.
// assuming default storage is local
Storage::fake('local');
Or change your logic to specify the correct faked storage disk.
Storage::disk('item-images')->putFile('path/yourfile', $image);

Laravel Searchable Belongs To

I'm using the package searchable by nicolaslopezj https://github.com/nicolaslopezj/searchable and I'm trying to do a relation of belongs to, however I'm having a bit of trouble with it.
protected $searchable = [
/**
* Columns and their priority in search results.
* Columns with higher values are more important.
* Columns with equal values have equal importance.
*
* #var array
*/
'columns' => [
'products.title' => 10,
'products.description' => 5,
],
'joins' => [
"brand" => ['products.brand_id', 'brands.id']
],
];
public function brand()
{
return $this->belongsTo(Brand::class);
}
The problem here, is that it tries to get the database table 'brand' when its called 'brands'. However if I change this to brands I get a relationship error with laravel. So I'm really unsure what to do. The documentation also doesn't explain it very well. Would love some help, thanks!
The solution to your original problem as discussed with consideration of what was discussed in the comments, is to update your Brand model, by setting the protected $table variable to 'brand' and then adjusting the above to reflect that.
This should resolve your original problem of the relationship error and keep your code consistent as you've mentioned in the comments.
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Brand extends Model
{
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'brand';
}
By default, Laravel will always assume that the database table is the plural form of the model name. And so, if you model is singular, you should assume it expects the database table to be the plural form unless explicitly set as above!
Adjusting your other code as follows, to reference the brand.id rather than brands.id:
protected $searchable = [
/**
* Columns and their priority in search results.
* Columns with higher values are more important.
* Columns with equal values have equal importance.
*
* #var array
*/
'columns' => [
'products.title' => 10,
'products.description' => 5,
],
'joins' => [
"brand" => ['products.brand_id', 'brand.id']
],
];
public function brand()
{
return $this->belongsTo(Brand::class);
}

Laravel registration: Insert two rows into separated tables

I have a problem with inserting rows in Laravel.
Theory: I use simple Laravel authentication and have two tables.
users: id, name, password.
user_details: id, userID, email
After the registration it would be useful to insert rows into both tables, and the userID = id (in users table).
RegisterController.php
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after registration.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
//$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return User
*/
protected function create(array $data)
{
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
'e_r' => $data['e_r'],
]);
$details = UserDetails::create([
'firstname' => 'joco',
'lastname' => 'nagy',
'email' =>$data['email'],
'position' => 'cleaner',
'salary' => '250000',
'amount_holiday' => '40'
]);
return $user;
}
}
(I have just tried to insert fake datas. There are default values in migration files.)
Models:
User.php
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password', 'e_r',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function user_detail(){
return $this->hasOne("App\UserDetails");
}
}
Error:
FatalThrowableError in RegisterController.php line 74: Class
'App\Http\Controllers\Auth\UserDetails' not found
I do not understand why should be my model in Auth directory.
Have you include your model UserDetails?
Include it on top:
use App\User;
use App\UserDetails;
or
Change UserDetails to App\UserDetails.
$details = App\UserDetails::create([
'firstname' => 'joco',
'lastname' => 'nagy',
'email' =>$data['email'],
'position' => 'cleaner',
'salary' => '250000',
'amount_holiday' => '40'
]);
You should use use statement eg. use Your\Name\Space\UserDetails;
Without this declaration PHP is looking for UserDetails class in your current namespace, in your case App\Http\Controllers\Auth. That's why you get
'App\Http\Controllers\Auth\UserDetails' not found

Categories