How to implement join query in Yii2 - php

I've read all the Yii2 Framework documentation but it's confusing when trying to implement it.
I have a view of Customer where it shows all the fields in customer table, including the address_id field in the address table.
I want to implement a join query using MySQL in the Yii2 Framework but the generated code is the following:
CustomerSearch in the models:
class CustomerSearch extends Customer{
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['customer_id', 'store_id', 'address_id', 'active'], 'integer'],
[['first_name', 'last_name', 'email', 'create_date', 'last_update'], '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 = Customer::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$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;
}
// grid filtering conditions
$query->andFilterWhere([
'customer_id' => $this->customer_id,
'store_id' => $this->store_id,
'address_id' => $this->address_id,
'active' => $this->active,
'create_date' => $this->create_date,
'last_update' => $this->last_update,
]);
$query->andFilterWhere(['like', 'first_name', $this->first_name])
->andFilterWhere(['like', 'last_name', $this->last_name])
->andFilterWhere(['like', 'email', $this->email]);
return $dataProvider;
}
Customer class:
class Customer extends \yii\db\ActiveRecord{
/**
* {#inheritdoc}
*/
public static function tableName()
{
return 'customer';
}
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['store_id', 'first_name', 'last_name', 'address_id'], 'required'],
[['store_id', 'address_id', 'active'], 'integer'],
[['create_date', 'last_update'], 'safe'],
[['first_name', 'last_name'], 'string', 'max' => 45],
[['email'], 'string', 'max' => 50],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'customer_id' => 'Customer ID',
'store_id' => 'Store ID',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'email' => 'Email',
'address_id' => 'Address ID',
'active' => 'Active',
'create_date' => 'Create Date',
'last_update' => 'Last Update',
];
}
}

You need to declare some relations in your ActiveRecord models...
See here in the official docs for Working with Relational Data
If you are storing the address_id in your customer table then you will be tied to each customer having 1 single address (i.e. a one-to-one relationship), which is a rather bad design. Or you could use a junction table. You are better off storing the customer_id in each address record and defining a one-to-many relation, enabling each customer to store multiple addresses (more like in real life, i.e. for home, work address etc).
For example, in your Customer model, you would define a has-many relation for customer addresses:
use app\models\Address;
class Customer extends \yii\db\ActiveRecord
{
/**
* {#inheritdoc}
*/
public static function tableName()
{
return 'customer';
}
/**
* {#inheritdoc}
*/
public function rules()
{
return [
[['store_id', 'first_name', 'last_name', 'primary_address_id'], 'required'],
[['store_id', 'primary_address_id', 'active'], 'integer'],
[['create_date', 'last_update'], 'safe'],
[['first_name', 'last_name'], 'string', 'max' => 45],
[['email'], 'string', 'max' => 50],
];
}
/**
* {#inheritdoc}
*/
public function attributeLabels()
{
return [
'customer_id' => 'Customer ID',
'store_id' => 'Store ID',
'first_name' => 'First Name',
'last_name' => 'Last Name',
'email' => 'Email',
'primary_address_id' => 'Primary Address ID',
'active' => 'Active',
'create_date' => 'Create Date',
'last_update' => 'Last Update',
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAddresses()
{
return $this->hasMany(Address::className(), ['customer_id' => 'id']);
}
}
And in your Address model you would have the inverse has-one relation defined:
/**
* #return \yii\db\ActiveQuery
*/
public function getCustomer()
{
return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
}
You can then Access relational data from model instances via defined relation names, for example:
// SELECT * FROM `customer` WHERE `id` = 123
$customer = Customer::findOne(123);
// SELECT * FROM `address` WHERE `customer_id` = 123
// $addresses is an array of Address objects
$addresses = $customer->addresses;
Also note, if you define proper primary/foreign keys in your schema, the Gii Model/CRUD generators will automatically create the boilerplate relations code in your models and CRUD files.

Related

Laravel relationship is not loading in Resource?

I want to load the relation of order with customer but it is returning null in the postman
Customer Model
<?php
namespace App;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class Customer extends Authenticatable implements JWTSubject
{
use Notifiable;
protected $fillable = [
'first_name',
'last_name',
'phone_number',
'postal_code',
'email',
'preferred_method_to_contact',
'password',
'address',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function getNameAttribute()
{
$name = $this->first_name . " " . $this->last_name;
return $name;
}
public function orders()
{
return $this->hasMany('App\Order');
}
public function getJWTIdentifier()
{
return $this->getKey();
}
/**
* Return a key value array, containing any custom claims to be added to the JWT.
*
* #return array
*/
public function getJWTCustomClaims()
{
return [];
}
}
Order Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $fillable = [
'first_name',
'last_name',
'email',
'phone_number',
'post_code',
'address',
'alternative_address',
'property_type',
'property_type_other',
'contract_type',
'contract_type_other',
'no_of_bedrooms',
'no_of_bathrooms',
'customer_id',
'best_day',
'best_time',
'type_of_cleaning',
];
public function customers()
{
return $this->belongsTo('App\Customer');
}
}
Order Resource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
/*return parent::toArray($request);*/
return [
'id' => $this->id,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'phone_number' => $this->phone_number,
'post_code' => $this->post_code,
'address' => $this->address,
'alternative_address' => $this->alternative_address,
'property_type' => $this->property_type,
'property_type_other' => $this->property_type_other,
'contract_type' => $this->contract_type,
'contract_type_other' => $this->contract_type_other,
'no_of_bedrooms' => $this->no_of_bedrooms,
'phone_number' => $this->phone_number,
'no_of_bathrooms' => $this->no_of_bathrooms,
'customer' => new CustomerResource($this->whenLoaded('customers')),
];
}
}
Customer Resource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class CustomerResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
/*return parent::toArray($request);*/
return [
'id' => $this->id,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
];
}
}
Here is the function in OrderController which is responsible for sending the JSON response
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'first_name' => ['required', 'string', 'max:10'],
'last_name' => ['required', 'string', 'max:10'],
'email' => ['required', 'string', 'email', 'max:50'],
'phone_number' => ['required', 'string', 'max:30'],
'post_code' => ['required', 'string', 'max:30'],
'address' => ['required', 'string', 'max:300'],
'alternative_address' => ['required', 'string', 'max:300'],
'property_type' => ['required', 'string', 'max:30'],
'contract_type' => ['required', 'string', 'max:30'],
'no_of_bedrooms' => ['required', 'string', 'max:30'],
'type_of_cleaning' => ['required', 'string', 'max:30'],
'best_day' => ['required', 'string', 'max:30'],
'best_day' => ['required', 'string', 'max:30'],
]);
if ($validator->fails()) {
return response()->json($validator->errors(), 422);
}
// $customer = Customer::findOrFail($request->customer_id);
$order = Order::create($request->all());
// $order = Order::create();
$order->load(array('customers'));
return new OrderResource($order);
}
I want the customer details to be loaded along with the order.
Somehow I managed to fix that problem. Actually the ManyToOne relationship function was not recognizing the customers. The customers function targets the many-to-one so it should be singular instead of plural according to the standards of laravel.
public function customer()
{
return $this->belongsTo('App\Customer');
}
Similarly in the OrderController
$order->load(array('customer')); // <- make it singular as well
In the OrderResource
'customer' => new CustomerResource($this->whenLoaded('customer')), // <- make it singular

YII 2 relational data from third table

I have a database having relationship of three levels. cheque->account->customer. Now I am trying to retrieve data from all three table at same time using the following method.
$query = Cheque::find();
$query->joinWith(['account.customer']);
$query->orderBy('sr desc');
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
Cheque Model:
class Cheque extends \common\components\db\ActiveRecord {
/**
* #inheritdoc
*/
public static function tableName() {
return 'cheque';
}
/**
* #inheritdoc
*/
public function rules() {
return [
[['sr'], 'integer'],
[['ID', 'account_ID'], 'required'],
[['ID', 'account_ID', 'created_by', 'branch_ID', 'application_ID'], 'string'],
[['serial_start', 'serial_end', 'status'], 'number'],
[['created_on'], 'safe']
];
}
/**
* #inheritdoc
*/
public function attributeLabels() {
return [
'ID' => 'ID',
'account_ID' => 'Account ID',
'serial_start' => 'Serial Start',
'serial_end' => 'Serial End',
'created_on' => 'Created On',
'created_by' => 'Created By',
'branch_ID' => 'Branch ID',
'application_ID' => 'Application ID',
'status' => 'Status',
'sr' => 'ID'
];
}
/**
* #return \yii\db\ActiveQuery
*/
public function getAccount() {
return $this->hasOne(Account::className(), ['ID' => 'account_ID']);
}
public static function getActiveChequeBook($account_ID) {
return Cheque::findAll(['account_ID' => $account_ID, 'status' => array_search('Active', \common\models\Lookup::$cheque_status)]);
}
}
But executing this I get the following error:
pre>Exception 'yii\base\InvalidCallException' with message 'Setting read-only property: common\models\Account::customer'
Property customer in your common\models\Account model has no setter (only getCustomer method exists). Check you model and add appropriate property to class.

yii2 grid filtering is not working

my grid view filter is not filtering categories, I have given category_id to nesserray places but yet it is not filtering my posts according to their category_id, please help me.
here are my source codes:
post-->index.php
<?= GridView::widget([
'dataProvider' => $dataProvider,
'filterModel' => $searchModel,
'columns' => [
['class' => 'yii\grid\SerialColumn'],
'title',
[
'attribute'=>'category_id',
'value'=>function($model) {
return $model->category->name;
},
'filter'=>$category
],
[
'attribute'=>'user_id',
'value'=>function($model) {
return $model->user->fullname;
},
'filter'=>$user,
'label'=>'user'
],
'category.name',
'user.fullname',
// 'user_id',
// 'description',
// 'content:html',
'count_view',
'status',
'created_at',
['class' => 'yii\grid\ActionColumn'],
],
]); ?>
and here is model/postSearch.php
<?php
namespace app\models\search;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\post;
/**
* PostSearch represents the model behind the search form about `app\models\post`.
*/
class PostSearch extends post
{
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id', 'user_id','category_id', 'count_view'], 'integer'],
[['title', 'description', 'content', 'status', 'created_at'], '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 = post::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$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;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'category_id' => $this->category_id,
'user_id' => $this->user_id,
'count_view' => $this->count_view,
'created_at' => $this->created_at,
]);
$query->andFilterWhere(['like', 'title', $this->title])
->andFilterWhere(['like', 'description', $this->description])
->andFilterWhere(['like', 'content', $this->content])
->andFilterWhere(['like', 'status', $this->status]);
return $dataProvider;
}
}
it worked, I had to removle jQuery files

unique field validations in model based on foriegn key yii2

I have a table called menusoffered where I am storing menuname based on restaurantid which is a foreign key.
In my menusoffered model I have declared that menuname has a unique validator, but this is checking the uniqueness from the entire table.
I want it to check uniqueness based on restaurantid,
Can any one help me how to make this menuname unique for particular restaurantid?
Below is my given menu model,
class Menusoffered extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'menusoffered';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
['phase_id', 'required', 'message' => 'Please select Phase'],
['menuname', 'required'],
['price', 'required'],
[['price'],'integer' , 'message' =>'Price should be in digits'],
[['image_url'], 'file'],
[['description', 'status'], 'string'],
[['createdts','restaurantid'], 'safe'],
['master_menu_sepcality_id', 'required', 'message' => 'Please select Speciality'],
['master_menu_type_id', 'required', 'message' => 'Please select Type'],
['menuname', 'unique', 'with'=>'restaurantid'],
[['restaurantid'], 'exist', 'skipOnError' => true, 'targetClass' => Restaurant::className(), 'targetAttribute' => ['restaurantid' => 'id']],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'master_menu_sepcality_id' => 'Master Menu Sepcality ID',
'master_menu_type_id' => 'Master Menu Type ID',
'item' => 'Item',
'price' => 'Price',
'restaurantid' => 'Restaurantid',
'phase_id' => 'Phase ID',
'description' => 'Description',
'image_url' => 'Image Url',
'status' => 'Status',
'createdts' => 'Createdts',
];
} }
Try this: Yii2 Unique Validator:
// a1 and a2 need to be unique together, only a1 will receive error message
['a1', 'unique', 'targetAttribute' => ['a1', 'a2']]

Yii2 PHP Excel Error getAttributeLabel() on null

I have to implementing export excel using moonland PHP excel extension. When I implemented it, it throws the error:
"Call to a member function getAttributeLabel() on null".
Code
$dataProvider = new \app\models\Details;
\moonland\phpexcel\Excel::widget([
'models' => $dataProvider,
'mode' => 'export', //default value as 'export'
'columns' => ['id','name','address'], //without header working, because the header will be get label from attribute label.
//'header' => ['id' => 'Header Column 1','name' => 'Header Column 2', 'address' => 'Header Column 3'],
]);
My model
class Details extends \yii\db\ActiveRecord
{
/**
* #inheritdoc
*/
public static function tableName()
{
return 'details';
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id'], 'required'],
[['id'], 'integer'],
[['name', 'address'], 'string', 'max' => 50],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
/* return [
'id' => Yii::t('app', 'ID'),
'name' => Yii::t('app', 'Name'),
'address' => Yii::t('app', 'Address'),
];*/
return [
'id' => 'ID',
'name' => 'Name',
'address' =>'Address',
];
}
}
Table
May i know what is the problem in that, thanks in advance for your idea

Categories