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);
}
Related
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);
I'm trying the 4.1 new feature "Inline create", but I can't seem to associate the ids of the items created. Let me explain what I'm doing / what I want:
I have "Folders" that have "Chapters" inside (so 1-n relation).
My code:
CRUD::addField([ //Folder crud
'name' => 'chapters',
'type' => 'relationship',
'label' => 'Unidad',
'model' => "App\Models\Chapter",
'inline_create' => [
'entity' => 'chapter',
'modal_class' => 'modal-dialog modal-xl',
'modal_route' => route('chapter-inline-create'),
'create_route' => route('chapter-inline-create-save'),
]
]);
protected function setupCreateOperation() //Chapter crud
{
CRUD::setValidation(ChapterRequest::class);
CRUD::addField([
'name' => 'name',
'type' => 'text',
'label' => 'Nombre'
]);
}
public function chapters() //Folder model
{
return $this->hasMany(Chapter::class);
}
public function folder() //Chapter model
{
return $this->belongsTo(Folder::class);
}
It creates the main item and the related items no problem, but it doesn't actually relate them in the database at any point.
Any clue of what I might be doing wrong? Followed the docs but can't seem to make it work.
Thank you.
Do you have the right column names in the db ? The columns that are making the relationship possible, i.e in the folder table you should have a column named something like chapter_name or chapter_id, to identify the chapter where the folder belongs to.
Moreover, if those columns do not follow laravel conventions you need to add them as the second and third parameter when you are implementing the relationship in the models
More details here https://laravel.com/docs/8.x/eloquent-relationships#one-to-many
One note on this... I was running into this issue and realized that I forgot to make the parent_id fillable on my child model.
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'parent_id',
]
I'm trying to run a faker factory for relationships, but the field always returns NULL. How do fake a model relationship without hitting the database?
I have a Map factory with a one-to-one relationship to a parent Event table. I need to fake this relationship for unit testing:
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'event' => 'faker.' . join('_', $this->faker->words),
'category' => $this->faker->word,
'sub_category' => $this->faker->word,
'priority' => $this->faker->randomElement(['normal', 'high']),
'event' => Event::factory()->makeOne(),
];
}
This returns a fake model, but event is null, from the debugger:
result = {array} [5]
event = "faker.eum_voluptatibus_aut"
category = "libero"
sub_category = "aut"
priority = "high"
event = null
I tried using states, but the same thing happens:
public function disabled()
{
return $this->state([
'event' => Event::factory()->makeOne(['enabled' => false]),
]);
}
The object is returned with an empty event value. I need a faker object I can transverse down into: if ($object->event->enabled) [...]. How do I generate fake model relationships?
If you are using Laravel 8.x you must consider using methods used on the docs, it must look like that :
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'event' => 'faker.' . join('_', $this->faker->words),
'category' => $this->faker->word,
'sub_category' => $this->faker->word,
'priority' => $this->faker->randomElement(['normal', 'high']),
'event_id' => Event::factory(),
];
}
/**
* Indicate that the map is disabled.
*
* #return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function disabled()
{
return $this->state([
'event_id' => Event::factory()->create(['enabled' => false]),
]);
}
The only solution I found, so far, is to manually set the event key myself in my tests. It's not the ideal or elegant solution.
$fieldMap = Map::factory()->makeOne();
$fieldMap->event = Event::factory(['enabled' => false])->makeOne();
I don't like this approach. Why can't I define factories within factories?
I have written like this
/**
* itemRepository
*
* #var \KRT\KrtEmployee\Domain\Repository\ItemRepository
* #inject
*/
protected $itemRepository = null;
/**
* action list
*
* #return void
*/
public function listAction()
{
$arguments =$this->request->getArguments();
$employees = $this->itemRepository->findAll();
$this->view->assign('employees',$employees);
}
In my $employees result I have
Employee ID (Default uid)
Name
Designation
Department
Salary
Now, How can I Sort the result array in ascending order based on
Name
Department and Salary
Is there any default function to sort inside the repository queries?
Every repository has a $defaultOrderings property where you can specify the default orderings applied to all query methods. In your case it could look like this:
protected $defaultOrderings = [
'name' => QueryInterface::ORDER_ASCENDING,
'department.name' => QueryInterface::ORDER_ASCENDING,
'salary' => QueryInterface::ORDER_ASCENDING,
];
As you can see with department.name you can also sort by properties of relations. Notice that this only works for 1:1 and n:1 relations.
In case of custom query methods in your repository you can manually set the orderings directly on the query:
$query->setOrderings([
'name' => QueryInterface::ORDER_ASCENDING,
'department.name' => QueryInterface::ORDER_ASCENDING,
'salary' => QueryInterface::ORDER_ASCENDING,
]);
You have multiple options depending on what you would like to achieve:
Setting a default order for the entire repository
Add the following to your repository class
protected $defaultOrderings =
array(
'department' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
'salary' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
);
This will apply to all queries made in this repository.
see: https://wiki.typo3.org/Default_Orderings_and_Query_Settings_in_Repository
Setting an order for a single query
Add this to a query you define in your repository
$query->setOrderings(
array(
'department' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
'salary' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
)
);
In this way you could (and would have to) implement a different access method for each sort order you would like to have returned.
see: https://docs.typo3.org/typo3cms/ExtbaseFluidBook/6-Persistence/3-implement-individual-database-queries.html
Sort the result
You can always use PHP sorting methods to sort the query result (possibly converting it to an array with ->toArray() first.
In general and complete:
<?php
namespace <yourVendor>\<yourExtensionkey>\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\Repository;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
class <yourDomain>Repository extends \TYPO3\CMS\Extbase\Persistence\Repository {
protected $defaultOrderings = [
'<yourProperty1>' => QueryInterface::ORDER_ASCENDING,
'<yourProperty2>' => QueryInterface::ORDER_DESCENDING
];
}
Further explanation in docs.typo3.org
I see that there's a 'last changed'-property in my table created with the extension builder called 'tstamp' but I can't figure out how to display it in the front-end.
In the front-end I'm using fluid like this, but it stays empty:
<f:format.date format="d.m.Y - H:i">{appointment.tstamp}</f:format.date>
I can see the property in the TCA also:
'ctrl' => array(
'title' => 'LLL:EXT:extTest/Resources/Private/Language/locallang_db.xlf:tx_extTest_domain_model_appointment',
'label' => 'start_date',
'tstamp' => 'tstamp',
I tried adding this in my php class file but it didn't change anything
/**
* #var DateTime
*/
protected $tstamp;
/**
* Get Tstamp
*
* #return DateTime
*/
public function getTstamp() {
return $this->tstamp;
}
I think the problem is that I don't understand the connection from the TCA to the PHP Class, can someone help?
The issue is that you also need a TCA configuration for every field as extbase gets there required information (especially for relations). Therefore add something like this into the TCA of your table:
'tstamp' => [
'label' => 'tstamp',
'config' => [
'type' => 'passthrough',
]
],
You don't need to add the field to a actual type.
The annotation in the model should be
/**
* #var \DateTime
*/
protected $tstamp;
So don't forget the \before the DateTime.
Clear caches and you are fine