Laravel Backpack - Show specific attribute from relationship function - php

I have the registered Comment model which has a User reference, like this:
public function user() {
return $this->belongsTo('App\User');
}
This function returns an instance of a User, and that's correct, but I don't know how to register the User column the get que user property using Backpack. Instead, I'm get a JSON representation of my model:
So, how do I get a specific field from my relationship function?

Sounds like the select column is a perfect match for you. Just use it in your EntityCrudController's setup() method, like so:
$this->crud->addColumn([
// 1-n relationship
'label' => "User", // Table column heading
'type' => "select",
'name' => 'user_id', // the column that contains the ID of that connected entity;
'entity' => 'user', // the method that defines the relationship in your Model
'attribute' => "user", // foreign key attribute that is shown to user
'model' => "App\Models\User", // foreign key model
]);
The "attribute" tells CRUD what to show in the table cell (the name, the id, etc).

If you do:
$user = $comment->user->user;
You'll get 'test'; (from your example)
It may seem confusing because your User model has a user attribute. Maybe you can call it 'name' instead of 'user'. That way you'll call it:
$username = $comment->user->name;
Remember to check if the relationship exists before calling a property on the related model.
if(!is_null($comment->user)) {
$username = $comment->user->user;
}
Or:
$username = !is_null($comment->user) ? $comment->user->user : 'No user';

If you need to get some field from deeper relation, you can use closure column type:
$this->crud->addColumn([
'label' => trans('backend.column_names.agency_linked_service'),
'name' => 'agency_service_id',
'type' => 'closure',
'function' => function (AgencyLinkedServices $entry) {
return $entry->agencyService->agencyCategory->title;
}
]);

Related

Many-To-Many Column not found Issue in Subfield relationships

I am using Laravel BackPack Pro and I am receiving an error when trying to save a relationship in a subfield.
My Model structure:
Event Model 1-n Badge n-m Packages
The error I receive when trying to save.
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'packages' in 'field list'
update `badges` set `packages` = ["1"], `badges`.`updated_at` = 2022-06-28 22:40:36 where `id
The following is my configuration in my EventCrudController. The Idea is to attach different Badges to an event and only make it available to certain badges.
CRUD::field('badges')
->type('relationship')
->subfields([
[ // relationship
'name' => 'packages', // the method on your model that defines the relationship
'type' => "relationship",
// OPTIONALS:
// 'label' => "Category",
// 'attribute' => "title", // attribute on model that is shown to user
// 'placeholder' => "Select a category", // placeholder for the select2 input
],
]);
The weird thing is, that using laravel tinker I can successfully attach any packages. And it displays correctly in the admin but it does not let me save anything. I am unsure if this is a bug or just me configuring it wrongly.
class Badge extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
public $guarded = [];
public function packages()
{
return $this->belongsToMany(Package::class,'badge_package');
}
enter image description here
According to https://github.com/Laravel-Backpack/CRUD/issues/4142 it is currently not supported.

Can't get backpack to pass correct value when inserting/updating a many to many relationship

I have created a many to many relationship between two tables with a third pivot table.
The thing that makes the situation a little difficult is I am linking the Apps table based on name and not ID. It is because I update the App list from a third party and app name will always be consistent, where ID can possibly change if App is removed at some point, and then re-added, etc.
Apps
id
name // This is the name of the app, it will never change for a particular app and is short, all lowercase, no spaces, and unique
label // This is the user friendly name
Plans
id
name
etc
apps_plans pivot table
id
apps_name
plans_id
I've finally got everything working perfectly in Laravel itself, but I cannot figure out at all how to get this to work correctly in Backpack for my Admin portal. I've gotten it to the point where everything works perfect until I try to update or create a new plan. The Apps I select using the select2 type, it tries to insert them into the pivot table with an ID number and not with the name.
Randomizing some names, my mistake if things don't match perfectly. This aspect works fine from all tests I've done:
Plans Model:
{
use CrudTrait;
protected $table = 'plans';
protected $guarded = ['id'];
public function apps()
{
return $this->belongsToMany('App\Apps', 'apps_plans', 'plans_id', 'apps_name', 'id', 'name');
}
}
Apps Model:
class Apps extends Model
{
use CrudTrait;
protected $table = 'apps';
protected $guarded = ['id'];
protected $casts = [
'json' => 'array',
];
public function plans()
{
return $this->belongsToMany('App\Plan', 'apps_plans', 'apps_name', 'plans_id', 'name', 'id');
}
}
**Note I removed the fillable variable , I didn't want to expose all variables in my columns.
Backpack Plans CrudController:
public function setup()
{
CRUD::setModel(\App\Plan::class);
CRUD::setRoute(config('backpack.base.route_prefix') . '/plan');
CRUD::setEntityNameStrings('plan', 'plans');
$this->crud->addColumn([
'name' => 'apps',
'type' => 'relationship',
'label' => 'Apps',
'entity' => 'apps',
'attribute' => 'label',
'model' => \App\Apps::class,
]);
}
protected function setupCreateOperation()
{
CRUD::setValidation(PlanRequest::class);
CRUD::setFromDb(); // fields
$this->crud->addField('apps', [
'name' => 'apps',
'type' => 'select2_multiple',
'entity' => 'apps',
'attribute' => 'label',
'label' => 'Apps',
'pivot' => true,
]);
I removed quite a bit to keep my project details private, I hope it makes sense. I think all important details are still in. Anyone know if this is an issue with Backpack? Or did I miss an option somewhere, where you can set which column it uses for the relationship. It is clearly not taking it from the model because the models work just as intended on their own...
Thanks!
Edit: here is my migration I am using, it works flawlessly--even in phpmyadmin it gives me a drop down of items to select from
{
Schema::create('apps_plans', function (Blueprint $table) {
$table->id();
$table->string('apps_name');
$table->foreign('apps_name')->references('name')->on('apps');
$table->unsignedBigInteger('plans_id');
$table->foreign('plans_id')->references('id')->on('plans');
});
}
EDIT 2:
This is the error I am getting when trying to do a Create or Update:
{
"error": "There is a problem with your request",
"message": "SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`api`.`apps_plans`, CONSTRAINT `apps_plans_apps_name_foreign` FOREIGN KEY (`apps_name`) REFERENCES `apps` (`name`)) (SQL: insert into `apps_plans` (`apps_name`, `plans_id`) values (2, 4))"
}
Again I removed some details that were very specific to my project but I don't think I changed any of the logic in the error message. You can see everything looks great about the query except that at the very end, it is inserting the App ID instead of the App name as it should be.
I suspect that the current configuration will have the same result in Laravel directly. ie running something like $plan = Plan::find($somePlanId); $app = App::find($someAppId); $plan->apps()->attach($app); would result in the same error.
Since name is the key that matters for the apps table, consider dropping the autoincrementing id for that table and instead setting
In the migration for the apps table, do:
$table->string('name')->primary();
Then in your apps model, do:
protected $primaryKey = 'name';
public $incrementing = false;
protected $keyType = 'string';
Now, Laravel (and by proxy Backpack) should treat the relationship the way you expect.

Cakephp 3: Set virtual property with condition on belongsToMany association

I have two tables, Tickets and Paintings. A ticket can have many paintings and a painting can be used on many tickets. They have a join table called tickets_paintings with ticket_id and painting_id. Here's how the tables are set:
class TicketsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Paintings', [
'foreignKey' => 'ticket_id',
'targetForeignKey' => 'painting_id',
'joinTable' => 'tickets_paintings'
]);
}
class PaintingsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Tickets', [
'foreignKey' => 'painting_id',
'targetForeignKey' => 'ticket_id',
'joinTable' => 'tickets_paintings'
]);
}
Every Ticket has a field "active" that is boolean and tells me if the ticket is currently in use or not. In the edit function the field can be changed to true or false.
But now I also need a property for every Painting, that tells me if this painting is currently used on an active ticket. In this case it would be unavailable to be used on another ticket.
I thought I could add a boolean virtual property for a painting, that checks if it is (currently) associated with any ticket that has active => true and is therefore also set true or false.
Like: "Does this painting belong to a ticket with state active => 0".
And then set the virtual property accordingly, so I can display it in a view.
I managed to create a virtual property for a painting that checks if there is an entry for its ID inside the join table so far:
class Painting extends Entity
{
protected function _getIsAvailable(){
$TicketsTable = TableRegistry::get('TicketsPaintings');
$exists = $TicketsTable->exists(['painting_id' => $this->id]);
if($exists == true){
return 1;
} else {
return 0;
}
}
}
A) How could I add a condition that checks if any matching ticket_id in the join table has an active=> 1 in the original Tickets table?
The query would have to check the ticket_id in the join table in an DESC order and return true as soon as the first active ticket is found (so it does not check the whole thing every time)
I don't even understand how to access that "active" property on the tickets table to check it. I assume I would have to use the " has Many through" option?! But even trying to following the explanation in the book and this question exactly (for hours), I am not able to get this to work for my example because I don't understand the correct syntax I had to use for my tables.
B) Is it even possible or "advisable" to check sth like this inside the Entity?
I tried to use an extra db field in the paintings table first but it seemed much more complicated to check and set another value every time a ticket is edited (not that I managed to make that work either)...and the active value is pretty much doubled then for ticket and painting. So I thought the virtual property would be easier to handle. Or am I on the completely wrong train?
Thanks for any tips or advice on this nightmare =)!
Since nobody came to my rescue I managed to put something together and actually create the virtual property I wanted. I don't know if this is the best way to go but it works for me and I will put it here in case anyone is searching for similar case.
I created a finder method inside the Paintings Table that joins the 3 tables and filters out all paintings that are connected to an active ticket.
class PaintingsTable extends Table
{
public function findNotavailable(Query $query, array $options){
$query
->join([
'tickets_paintings' =>[
'table' => 'tickets_paintings',
'type' => 'LEFT',
'conditions' => 'tickets_paintings.painting_id = Paintings.id'
],
'tickets' => [
'table' => 'tickets',
'type' => 'LEFT',
'conditions' => 'tickets_paintings.ticket_id = Tickets.id'
],
])
->select(['Paintings.id'])
->order(['tickets_paintings.ticket_id' => 'DESC'])
->where([
'Tickets.active' => false
]);
return $query;
}
}
Then in the Entity of the Painting I check if its ID is inside this selection and set a boolean virtual property accordingly:
use Cake\ORM\TableRegistry;
class Painting extends Entity
{
protected function _getNotAvailable(){
$Paintings = TableRegistry::get('Paintings');
$notavailable = $Paintings->find('notavailable')
->where(['Paintings.id'=>$this->id]);
if($notavailable->isEmpty()){
// if no record --> painting is available
return 0;
} else {
// if record found --> painting is not available
return 1;
}
}
}
And in the view I display it like this:
In Paintings - view.ctp
<?= $painting->not_available ? __('not available') : __('available'); ?>
The only downside to it is that virtual properties can't be used for pagination as far as I understand the book. But I'm sure there's a workaround for that too.

Yii Get Related Data into a GridView

My view lets the user see a person's details, including the families they are in. This is done in the DB with a Person table, Family table, and a familyMembership link table between the two.
The relationship in the CActiveRecord model for Person is as such:
'families' => array(self::MANY_MANY, 'Family', 'familymembership(Person, Family)'),
In my person controller, I am wanting to pass a variable into the view that has the related data in a way that TbGridView (CGridView but Bootstrap) will accept (a data provider). The controller calls the person model's getFamilies() function:
public function getFamilies() {
// returns an array of Family objects related to this Person model
$familiesArray = $this->getRelated('families');
// puts this array into a CArrayDataProvider
$families = new CArrayDataProvider($familiesArray);
return $families;
}
The return value goes back to the controller, which is then handed through in the renderPartial() call. The view has a TbGridView widget initialisation like this:
$this->widget('bootstrap.widgets.TbGridView',
array(
//the CArrayDataProvider from the model function
'dataProvider' => $families,
'columns' => array(
array(
'name' => 'Family Name',
// Example field from Family model
'value' => '$data->familyName'
)
)
));
However, in doing this I am getting the following error:
'Property "Family.id" is not defined. (D:\wamp\www\yii\framework\base\CComponent.php:130)'
The Family model does not have an id property, and I don't understand why the widget is looking for such.
What's going wrong here?
Thanks.
Quoting the doc from Yii Class Reference, you have to provide a keyField:
Elements in the raw data array may be either objects (e.g. model objects) or associative arrays (e.g. query results of DAO). Make sure to set the keyField property to the name of the field that uniquely identifies a data record or false if you do not have such a field.
By default keyField will be set to "id", so you need to overwrite it with your Family model primary key :
<?php
$familyDataProvider = new CArrayDataProvider($rawData, array(
'keyField' => 'yourPkNameHere',
));

Yii Relations error Trying to get property of non-object

I have Cinema table & City table and I have relation with this tables by id.. and when I echo results i have PHP notice "Trying to get property of non-object"
What is the problem ? or I missed something ??
My Code:
Cinema model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'countryCode' => array(self::BELONGS_TO, 'TblCountry', 'country_code'),
'city' => array(self::BELONGS_TO, 'TblCity', 'city_id'),
'tblCinemaMovies' => array(self::HAS_MANY, 'TblCinemaMovies', 'cinema_id'),
'tblDays' => array(self::HAS_MANY, 'TblDay', 'cinema_id'),
'tblShowtimes' => array(self::HAS_MANY, 'TblShowtime', 'cinema_id'),
);
}
City model
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'tblCinemas' => array(self::HAS_MANY, 'TblCinema', 'city_id'),
'countryCode' => array(self::BELONGS_TO, 'TblCountry', 'country_code'),
);
}
View file:
<?php echo $model->city_id->name; ?>
I recommend you to always check if the relation does not return null or an empty array.
The following code works well with HAS_ONE and BELONGS_TO relationship types:
$cinema = Cinema::model()->find(); // get a cinema instance, assuming there is at least one row in the database.
$city = $cinema->city; // get the relation
if ($city !== null) {
// $city is a valid model
} else {
// $city is null, the corresponding row does not exist in the database
}
You may also make this check without using a new variable (in this case, $city):
if ($cinema->city!==null) {
// the relation is available
}
Checking if the relation does not return null is the best way to avoid the PHP error "trying to get property of non-object". Also, if you are stuck with similar errors, it is recommended to use functions like var_dump() or, even better, a debugger.
Also, notice that array keys in the array returned from relations() function is the property that has to be accessed to get the relation model(s):
public function relations() {
return array(
'city' => array( ... ),
// city is the property that has to be accessed
// Yii's conventions recommend to use 'city_id' for the foreign key column name
);
}
Also note that it is good to follow Yii's conventions for naming relationships and columns to avoid using the same name for both a property and a relations - in this case, the relation will be unavailable and probably an error like "trying to access property of a non-object" will pop up when playing with the relation.
The last thing, when working with HAS_MANY or MANY_TO_MANY relationships, the relation returns an array of models even if there is only one model available and an empty array if there is nothing available.
Explanation in the docs:
http://www.yiiframework.com/doc/guide/1.1/en/database.arr#performing-relational-query
<?php echo $model->city->name; ?>
You must use $model->city to get the corresponding city of the $model.
Because the city_id is a array, in your view file, you should write the code like this
<?php echo $model->city_id[0]->name; ?>
That error happens when city_id is emptied or nulled (or the value doesn't exist in the foreign table).
If you're not sure that "city_id" exists, you can check it like this:
CHtml::value($model, 'city.name');
That way you always ensure that you'll not have an Exception when the value is empty or clear

Categories