Laravel Backpack - Inline create, relationship not being added on DB - php

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',
]

Related

Laravel inserting data in one to many tables

Good morning developers, iam working on ecommerce project using laravel and angular
in laravel i made two tables ( brands - products ) and i need the brand has many products
so i added this function to the Product model
public function brand()
{
return $this->belongsTo(Brand::class);
}
and i added this function to Brand model
public function products()
{
return $this->hasMany(Product::class);
}
the first question is how i could handle the relation in the products table migration file, is this right or there is better way?
$table->unsignedBigInteger('brand_id');
$table->foreign('brand_id')->references('id')->on('brands')->onDelete('cascade');
the second questions is should i define the brand_id in the $fillable products property or not
protected $fillable = ['name', 'brand_id', 'display', 'ram', 'storage', 'rear_cam', 'front_cam', 'img', 'price'];
the third question is how to handle the store product function and note iam using api calls between angular and laravel
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:100', 'display' => 'required|string|max:100', 'ram' => 'required|string|max:100', 'img' => 'required|image|mimes:jpg,png',
'storage' => 'required|string|max:100', 'rear_cam' => 'required|string|max:200', 'front_cam' => 'required|string|max:200',
'price' => 'required'
]);
if ($validator->fails()) {
$errors = $validator->errors();
return response()->json($errors);
}
$img = $request->file('img');
$ext = $img->getClientOriginalExtension();
$name = 'product-' . uniqid() . ".$ext";
$img->move(public_path('uploads/books/'), $name);
$name = $request->name;
$brand_id = $request->brand_id;
$display = $request->display;
$ram = $request->ram;
$storage = $request->storage;
$rear_cam = $request->rear_cam;
$front_cam = $request->front_cam;
$price = $request->price;
$product = Product::create(['name' => $name, 'brand_id' => $brand_id, 'display' => $display, 'img' => $name, 'ram' => $ram, 'storage' => $storage, 'rear_cam' => $rear_cam, 'front_cam' => $front_cam, 'price' => $price]);
$product->brand()->sync($request->brand_id);
$success = 'Product created successfully';
return response()->json($success);
}
when iam trying to store a product it says there is error with the sync function, what function should i use instead of it
Actually you don't need it to sync, In many-to-many relationships require an additional table called pivot table where you Managing Many-to-Many Relationships using attach-detach-sync method but in case you havnt situation like this.
Undo this Line
$product->brand()->sync($request->brand_id);
After Product created successfully. If you simply called products function to Brand model. You will seeing last created product are there.
Ex:-
Brand::with('products')->find($brand_id)
Thanks
the first question is how i could handle the relation in the products table migration file, is this right or there is better way?
It is indeed the good way to handle this.
the second questions is should i define the brand_id in the $fillable products property or not
It all depends on your use case. Typically you only define the attributes the user can modify at will, like his name. The concept of fillable is here to protect you from mass assignment.
For instance:
if I change the name attribute of a check box from accept_newsletter to is_admin
and if a column named is_admin exists
and if is_admin is in the fillable attributes
and you do something like that: $user->update($request->all());
Then I could potentially become an admin.
However, if is_admin isn't in the fillable attributes, you must explicitly do something like: $user->is_admin = $request->is_admin. Which is much more unlinkely to happen involuntarily.
In your case, the person who will add/edit a product will probably be an admin anyway so you can add brand_id to the fillable attributes.
For your third question, since you are using a hasMany relationship on the Brand model and a belongsTo in the Product model, all you have to do is $product->brand()->associate($request->brand_id);.
But keep in mind that here:
product = Product::create(['name' => $name, 'brand_id' => $brand_id, 'display' => $display, 'img' => $name, 'ram' => $ram, 'storage' => $storage, 'rear_cam' => $rear_cam, 'front_cam' => $front_cam, 'price' => $price]);
You already assign the brand_id to the product you are creating (it'll only work if you added brand_id to the fillable attributes). Think of the fillable attributes as "attributes that can be assigned within an array".

Laravel Backpack - Set extra attributes before page save

In my PageTemplates.php I have a field like this:
$this->crud->addField([
'name' => 'adres',
'label' => 'Adres',
'type' => 'address',
'fake' => true,
]);
Now I would like to save also the latitude and longitude of the address they give in (if it can be found). I've copied the PageCrudController and changed the config in config/backpack/pagemanager.php to:
return [
'admin_controller_class' => 'App\Http\Controllers\Admin\PageCrudController',
'page_model_class' => 'App\Models\Page',
];
In my store function I have:
public function store(StoreRequest $request)
{
$address = $request->request->get('adres');
$addressObj = app('geocoder')->geocode($address)->get()->first();
if($addressObj)
{
}
$this->addDefaultPageFields(\Request::input('template'));
$this->useTemplate(\Request::input('template'));
return parent::storeCrud();
}
But what do I place in the if statement? How can I add (= set) an extra field to the extras field in my database?
In backpack 4.1, I solved my issue by the following way :
Override the store method in my controller, set my extra field in request and then call the backpack store method
Don't forget to add include backpack trait
Hope the solution will help someone
use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; }
public function store()
{
$this->crud->setOperationSetting('saveAllInputsExcept', ['save_action']);
$this->crud->getRequest()->request->add(['updated_by' => backpack_user()->id]);
return $this->traitStore();
}
Fixed it by doing the following:
Add latitude and longitude as hidden fields:
$this->crud->addField([
'name' => 'latitude',
'type' => 'hidden',
'fake' => true,
]);
$this->crud->addField([
'name' => 'longitude',
'type' => 'hidden',
'fake' => true,
]);
Set attributes by doing the following:
if($addressObj)
{
$request['latitude'] = $addressObj->getCoordinates()->getLatitude();
$request['longitude'] = $addressObj->getCoordinates()->getLongitude();
}
}
Change parent::updateCrud to parent::updateCrud($request);.
For people still looking at this issue, I'd recommend you follow the advice in the note under the Callbacks section of Laravel Backpack's docs if you don't just want to observe changes made from the Backpack admin panel, you just need to create an Observable.
To do this you can do the following:
Create an Observer class: php artisan make:observer YourObserver --model=YourModel
Add your code to the generated event methods you wish to observe.
Register the Observer by calling the observe method on the model you wish to observe in your EventServiceProvider's boot method like so:
public function boot()
{
YourModel::observe(YourObserver::class);
}
Or equally you can register the Observer to the $observers property of your applications' EventServiceProvider class:
protected $observers = [
YourModel::class => [YourObserver::class],
];

Yii2 CRUD generated manually

I would like to have a CRUD table, and to be specific I don't need to edit/delete records, only the part with filtering results, which appears at the top of the CRUD generated table, is the part that I want to have. My table contains data from 1 table in database, but I have one column that is not connected to this or any other table in database (it's a comment, that is auto-generated, based on one column from the table). I generate my table manually, but I'd like to add the part with filtering. I have no idea how to do it though. Is it possible in Yii2 to do it manually, or do I have to use CRUD generator?
I don't use CRUD generator as it doesn't generate the code I want (and I think it also doesn't generate filters?). I use a basic template which fits to nearly all gridviews I need to display. Here's one example that can give you a filter:
use yii\grid\GridView;
/** #var array $userTypes All user types (classes) */
// ...
echo GridView::widget([
'dataProvider' => $modelProvider,
'filterModel' => $model,
'columns' => [
[
'attribute' => 'id',
'format' => 'raw',
'filter' => Html::input('text', 'User[id]', $model->id, ['class' => 'form-control', 'placeholder' => 'Filter ID']),
[
'attribute' => 'type',
'format' => 'raw',
'filter' => Html::activeDropDownList($model, 'type', $userTypes, ['class' => 'form-control', 'prompt' => 'All types']),
],
]);
In here I use 2 different input field types (text and dropdown).
For Html::input, first is type (text), then full attribute name (model name + attribute name), then default value and finally other options.
For Html::activeDropDownList we have model first, then attribute name (only), that items list (array) and finally other options.
I guess you are talking about GridView, if yes then you can have your own columns in it, no problem. lets call that column comment as you mentioned
If you use basic template, generated by Gii, and you also generate search class for that model, then you comment to safe attributes and add code for that code to be able to filter it.
If you could be more detailed about mentioned column, possible values or algorythm you might get more suitable answers...
Also take look at Yii 2.0: Displaying, Sorting and Filtering Model Relations on a GridView
Lets say your model is called Xyz as you did not provided any. Also I named column from your table as column_from_your_table and your virtual column as comment
In your model Xyz you will add relation (method with specific name to define it)
public function getComment()
{
$column_from_your_table = $this->column_from_your_table;
$comment = '';
// your code to specify the relation
// ...
// this is value dislpayed in column grid
return $comment;
}
and in file XyzSearch.php in \app\models\
you will have something like this (edit to your needs ofcourse)
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\db\Expression;
/**
* XyzSearch represents the model behind the search form about `app\models\Xyz`.
*/
class XyzSearch extends Xyz
{
public $comment; // your virtual column
/**
* #inheritdoc
*/
public function rules()
{
return [
// add it to safe attributes
[['comment'], 'safe'],
// you will have more rules for your other columns from DB probably
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = Xyz::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
// I dont know your how it is your comment column autogenerated
// or what is the relation, so I give you just basic idea
// your algorythm needs to be able to be rewritten in SQL
// otherwise I dont know how you could possibly sort it
$dataProvider->sort->attributes['comment'] = [
'asc' => [Xyz::tableName().'.column_from_your_table' => SORT_ASC],
'desc' => [Xyz::tableName().'.column_from_your_table' => SORT_DESC],
'default' => SORT_ASC,
];
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// again, you will have more filtering conditions from your generated code
// then you will add your custom filtering condition
// I dont know your how it is your comment column autogenerated
// or what is the relation, so I give you just basic idea
$query->andFilterWhere(['like', Xyz::tableName().'.column_from_your_table', $this->comment]);
return $dataProvider;
}
}
finaly in your view file add your virutal column
<?php echo GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
// other columns from database
// ...
'comment',
['class' => 'yii\grid\ActionColumn'],
]
]); ?>

Yii scopes with multi-table relations

I have a model Order with one-to-many relationship to model Booking. Booking model, in turn, has a Connection model reference.
I'm having trouble figuring out how to scope out Order so that I only get the order that have bookings with Connection.isDefault value set to 0.
I may have many Bookings with the Order, so I need to look through the very first booking.
I feel this may not be achievable through the scope mechanism as I cannot pass Order/Booking primary keys through helper functions that can be used in scope. What is an alternative workaround here (if any) can you suggest?
The Order has a code for scopes:
public function scopes()
{
return array(
'canView' => array(
'with' => array(
'agency' => array(
'scopes' => array('myNetwork', 'myCompany' => 'OR')
)
),
),
'canEdit' => array(
'with' => array(
'agency' => array(
'scopes' => array('myNetwork', 'myCompany' => 'OR')
),
),
),
);
}
Please try using CDbCriteria(). add relation in Bookings table and order table.
The Bookings has a code for relation:
class Bookings extends CActiveRecord{
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(
'connectionRel' => array(self::BELONGS_TO, 'connection', 'booking_id', "condition" => 'isDefault = 0'),
);
}
}
The Order has a code for relation:
class Order extends CActiveRecord{
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(
'bookingsRel' => array(self::BELONGS_TO, 'Bookings', 'booking_id', "with" => array('connectionRel')),
);
}
}
Find all order with booking relation :
Order::model()->with('bookingsRel')->findAll();
Try using CDbCriteria(). In with you can put relation and it there do again with (do some CDbCriteriaCeption). Try experimenting with other criteria params to fit your needs (like applying $c->together = true;)
$c = new CDbCriteria();
$c->with = [
'bookings' => [
'select' => false,
'with' => [
'connections' => [
'select' => false,
]
]
]
];
$c->addCondition('connections.isDefault = 0');
Order::model()->findAll($c);

Filter setup for related model in GridView

I am trying to setup the filter for related model in Yii2's GridView widget, but I am keep getting the error like the filter value must be an integer.
I have followed this question. Now, I have a two models Services.php and ServiceCharge.php.
In ServiceCharge.php the relation is setup like:
public function getServiceName()
{
return $this->hasOne(Services::className(),['id'=>'service_name']);
}
In the ServiceChargeSearch.php the code is like this:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\ServiceCharges;
/**
* ServiceChargesSearch represents the model behind the search form about `app\models\ServiceCharges`.
*/
class ServiceChargesSearch extends ServiceCharges
{
/**
* #inheritdoc
*/
public function attributes()
{
// add related fields to searchable attributes
return array_merge(parent::attributes(), ['serviceName.services']);
}
public function rules()
{
return [
[['id'], 'integer'],
[['charges_cash', 'charges_cashless'], 'number'],
[['id', 'serviceName.services', 'room_category'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = ServiceCharges::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$dataProvider->sort->attributes['serviceName.services'] = [
'asc' => ['serviceName.services' => SORT_ASC],
'desc' => ['serviceName.services' => SORT_DESC],
];
$query->joinWith(['serviceName']);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
$query->andFilterWhere([
'id' => $this->id,
// 'service_name' => $this->service_name,
'room_category' => $this->room_category,
'charges_cash' => $this->charges_cash,
'charges_cashless' => $this->charges_cashless,
])
->andFilterWhere(['LIKE', 'serviceName.services', $this->getAttribute('serviceName.services')]);
return $dataProvider;
}
}
and in my Gridview it is setup like this:
[
'attribute'=>'service_name',
'value'=>'serviceName.services',
],
Which is showing the services name from the related model correctly.
I am not able to see what I am doing wrong, but the filter field for the attribute for service is not showing at all.
Actually it is much simpler than it seems.
add the column_name to safe attribute.
Note: this should be relation Name
add the join with query - like - $query->joinWith(['serviceName','roomCategory']);
add the filter condition like:
->andFilterWhere(['like', 'services.services', $this->service_name])
->andFilterWhere(['like', 'room_category.room_category', $this->room_category]);
if like to add sorting add the code like:
$dataProvider->sort->attributes['service_name'] = [
'asc' => ['services.services' => SORT_ASC],
'desc' => ['services.services' => SORT_DESC],
];
$dataProvider->sort->attributes['room_category'] = [
'asc' => ['room_category.room_category' => SORT_ASC],
'desc' => ['room_category.room_category' => SORT_DESC],
];
5 you should also set the relation name say public $roomCategory
That's it. Both sorting and filtering for related table works perfectly.
Note: Remove default validation like integer for related column and default filtering generated by gii otherwise it will generate an error.
Update on Latest version:
Adding Public $attribute is not needed.
Adding safe attribute for relation is also not needed.
but the attribute in your current model, which you want filter is
to added to safe attribute that is a must.
and most importantly in your gridview, the related attribute has to
be in closure format.
that is example
[
'attribute=>'attribute_name',
'value=function($data){
return $data->relationname->related_table_attribute_name
}
],
remember it you are using relation_name.related_table_attribute_name filter somehow doesn't work for me.
There is a fairly comprehensive set of instructions on the Yii Framework website. The only thing to note is that the search model complains about the following lines, but everything appears to work as intended without them:
$this->addCondition(...);
For a model, PaymentEvent (table: subs_payment_event), which has a currency_id field linked to model Currency, this is the complete set of additional code (using the Basic template):
In the main model, PaymentEvent.php:
public function getCurrencyName()
{
return $this->currency->name;
}
In the search model, PaymentEventSearch.php:
public $currencyName;
In its rules:
[['currencyName'], 'safe'],
In the attributes of its setSort statement, include:
'currencyName' => [
'asc' => ['subs_currency.name' => SORT_ASC],
'desc' => ['subs_currency.name' => SORT_DESC],
'label' => 'Currency'
],
Before the grid filtering conditions:
$query->joinWith(['currency' => function ($q) {
$q->where('subs_currency.name LIKE "%' . $this->currencyName . '%"');
}]);
Finally, in the GridView columns array in the view (including my usual link across to the related model records):
[
'attribute' => 'currencyName',
'label' => 'Currency',
'format' => 'raw',
'value' => function ($data) {
return Html::a($data->currency->name, ['/currency/' . $data->currency_id]);
},
],

Categories