Yii 2: virtual fields in ActiveRecords - php

I use MySQL and Yii 2. In database exist table 'Regions' with column 'coordinates'. Type of this column is point.
In SQL I can write:
"SELECT X(coordinates) as x, Y(coordinates) as y" and "INSERT INTO Regions SET coordinates = PointFromText('POINT(".$x." ".$y.")')".
But I don't know how make ActiveRecord model. I want this (unchanged the database):
$item = Regions::findOne(1);
echo $item->x." ".$item->y;
$item->x = $new_x;
$item->y = $new_y;
$item->save(); // data saved in 'coordinates' column
Methods 'set...()' and 'get...()' not help me. I want to avoid additional queries to the database.
Please help me to do it.

You can do it with Yii2 just like that:
public $longitude, $latitude;
/**
* #inheritdoc
*/
public function beforeSave($insert)
{
$this->location = new \yii\db\Expression("GeomFromText(:point)", [
':point'=>'POINT('. $this->longitude.' '.$this->latitude.')'
]);
return parent::beforeSave($insert);
}
/**
* #inheritdoc
*/
public static function find()
{
return parent::find()->select([
'*', 'X(location) as longitude', 'Y(location) as latitude'
]);
}

Related

Laravel model attributes null when i saved it but but I have validated them in the constructor

working with Laravel PHP, I have this model with a constructor where i set the attributes:
class NutritionalPlanRow extends Model
{
use HasFactory;
private $nutritional_plan_id;
private $aliment_id;
private $nomeAlimento;
public function __construct($plan = null,
$aliment = null,
array $attributes = array()) {
parent::__construct($attributes);
if($plan){
$this->nutritional_plan()->associate($plan);
$this->nutritional_plan_id = $plan->id;
}
if($aliment){
$this->aliment()->associate($aliment);
$this->aliment_id = $aliment->id;
$this->nomeAlimento = $aliment->nome;
}
}
/**
* Get the plan that owns the row.
*/
public function nutritional_plan()
{
return $this->belongsTo('App\Models\NutritionalPlan');
}
/**
* Get the aliment record associated with the NutritionalPlanRow.
*/
public function aliment()
{
return $this->belongsTo('App\Models\Aliment');
}
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = [];
/**
* Get the value of nomeAlimento
*/
public function getNomeAlimentoAttribute()
{
return $this->nomeAlimento;
}
/**
* Get the value of plan_id
*/
public function getNutritional_Plan_IdAttribute()
{
return $this->nutritional_plan_id;
}
/**
* Get the value of aliment_id
*/
public function getAliment_IdAttribute()
{
return $this->aliment_id;
}
}
Then I have a controller where I initialize the object:
public function addAlimentToPlan(Request $request){
$planId = $request->planId;
$alimentId = $request->alimentId;
$validatedData = Validator::make($request->all(), [
'planId' => ['required'],
'alimentId' => ['required'],
]);
if ($validatedData->fails()) {
return back()->withErrors($validatedData, 'aliment');
}
$plan = NutritionalPlan::find($planId);
$aliment = Aliment::find($alimentId);
$nutritionalPlanRow = new NutritionalPlanRow($plan, $aliment);
Log::info('Nome Alimento '.$nutritionalPlanRow->getNomeAlimentoAttribute());
$nutritionalPlanRow->save(); //
Toastr::success( 'Alimento aggiunto', '',
["positionClass" => "toast-bottom-right",
"closeButton" => "true"]);
return back();
}
The save operation return this error:
SQLSTATE[23502]: Not null violation: 7 ERRORE: null value in column "nomeAlimento" of relation "nutritional_plan_rows"
but logging the $nutritionalPlanRow->getNomeAlimentoAttribute() the attribure is enhanced.
Someone can help me?
Thank you.
In your constructor you have the following line:
$this->nomeAlimento = $aliment->nome;
You believe that this will fill the attribute in the eloquent model, but that is not happening. Normally such an assignment will pass the magic __set method on the model, but not during model/object construction.
You actually assign it to a property on the object, which is later accessible by your log function, but eloquent doesn't know about it. Therefore it is not sent to the database, resulting in a null error (no default value).
You may use the following to set the values in the constructor:
$this->setAttribute('nomeAlimento', $aliment->nome);
This calls the setAttribute function on the eloquent model, the attribute this becomes part of the model.
(Make sure to change also the other line in your constructor where you assign a value to the object)

How to return the count of duplicate relationships with Laravel Eloquent

I have the following tables Orders, Lamps and Lamp_Order which is a pivot table. The Lamp_Order table stores the id of an Order and a Lamp. An Order can contain multiple of the same Lamps. So it could be that there are for example 5 Lamps with an id of 1 connected to the same Order. I want to get the count of the same Lamps within an Order. So this method or function that I want to make should return 5 in this case.
I currently have this function in my OrderController to return the Order with the related Lamps:
public function index()
{
$orders = Order::all();
// Get the Lamps for each Order.
foreach ($orders as $order) {
$order->lamps;
}
return response()->json([
'orders' => $orders
], 200);
}
The response in my vue front-end looks like this:
As you can see there are some lamps with the same ID being returned. Instead I would like to return this Lamp with the count of how many times it is related to that Order.
My Order model looks like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email'
];
/**
* Get related Image.
*
* #return void
*/
public function image()
{
return $this->hasOne(Image::class);
}
/**
* Get related Lamps.
*
* #return void
*/
public function lamps()
{
return $this->belongsToMany(Lamp::class)->withPivot('room');
}
public function countSameRelationships()
{
}
/**
* Detach related lamps when deleting Orders.
*
* #return void
*/
public static function boot()
{
parent::boot();
static::deleting(function ($order) {
$order->lamps()->detach();
});
}
}
I was thinking about creating a function in the Order model which I call in the index function in the OrderController. Can someone tell me if there is some sort of already existing function to count these "duplicate" relationships? Or what would be a good approach to tackle this problem? I prefer the solution to return the right data directly from the Laravel backend. But if it is also possible to apply some sort of filter function to remove and count duplicate relationships that would be fine as well.
Okay, at this moment I made the following solution in my Vue front-end as I didn't manage to solve the problem in my back-end:
removeAndCountDuplicates(order) {
// map to keep track of element
// key : the properties of lamp (e.g name, fitting)
// value : obj
var map = new Map();
// loop through each object in Order.
order.forEach(data => {
// loop through each properties in data.
let currKey = JSON.stringify(data.name);
let currValue = map.get(currKey);
// if key exists, increment counter.
if (currValue) {
currValue.count += 1;
map.set(currKey, currValue);
} else {
// otherwise, set new key with in new object.
let newObj = {
id: data.id,
name: data.name,
fitting: data.fitting,
light_color_code: data.light_color_code,
dimmability: data.dimmability,
shape: data.shape,
price: data.price,
watt: data.watt,
lumen: data.lumen,
type: data.type,
article_number: data.article_number,
count: 1
};
map.set(currKey, newObj);
}
});
// Make an array from map.
const res = Array.from(map).map(e => e[1]);
return res;
},
This function increments a counter and adds it to the object. If someone has a solution that works in the back-end I would like to see that answer as well.
You can use Eager Loading in laravel:
public function index()
{
$orders= User::with(['lamps' => function($query) {
$query->select('lamps.id', DB::raw("COUNT('lamps.id') AS lamp_count"));
$query->groupBy('lamps.user_id');
}])->get();
return response()->json([
'orders' => $orders
], 200);
}

Laravel get Model by Table Name

Is there any way to get a model by table name?
For example, I have a "User" model, its table is defined as protected $table = "users"
Now, what I want to do is to get the model by table name which is equal to "users".
This function is more like the reverse of Model::getTable();
I have searched everywhere but I could not find a solution, perhaps I might be missing something simple?
EDIT
I am building something like an API :
Route::get('/{table}', 'ApiController#api');
Route::get('/{table}/filter', 'ApiController#filter');
Route::get('/{table}/sort', 'ApiController#sort');
Route::get('/{table}/search', 'ApiController#search');
so in the address bar, for example when I search for the "users", I could just hit on the URL:
api/users/search?id=1
then on the controller, something like:
public function search(){
// get all the params
// get the model function
$model = //function to get model by table name
// do some filtering, then return the model
return $model;
}
Maybe something like this will help you:
$className = 'App\\' . studly_case(str_singular($tableName));
if(class_exists($className)) {
$model = new $className;
}
studly_case() and str_singular() are deprecated functions.
You can use the Illuminate\Support\Str facade.
$className = 'App\\' . Str::studly(Str::singular($tableName));
I know that it is an old question, but it can help someone:
public function getModelFromTable($table)
{
foreach( get_declared_classes() as $class ) {
if( is_subclass_of( $class, 'Illuminate\Database\Eloquent\Model' ) ) {
$model = new $class;
if ($model->getTable() === $table)
return $class;
}
}
return false;
}
It will return the class name, so you need to instantiate it.
You must determine for which table name which class to call.
I see 2 ways to do this.
Use Laravel's models naming convention as #IgorRynkovoy suggested
or
Use some kind of dictionary
public function search($tableName)
{
$dictionary = [
'table_name' => 'CLASS_NAME_WITH_NAMESPACE',
'another_table_name' => 'CLASS_NAME_WITH_NAMESPACE',
];
$className = $dictionary[$tableName];
$models = null;
if(class_exists($className)) {
$models = $className::all();
}
// do some filtering, then return the model
return $models;
}
Alternative variant.
I have my base model App\Models\Model
This model have static method getModelByTable, ofcourse you can store this method anywhere you want.
public static function getModelByTable($table)
{
if (!$table) return false;
$model = false;
switch ($table) {
case 'faq':
$model = Faq::class;
break;
case 'faq_items':
$model = FaqItems::class;
break;
}
if ($model) {
try {
$model = app()->make($model);
} catch (\Exception $e) {
}
}
return $model;
}
Inherit from the following, instead of from Model.
use Illuminate\Support\Str;
class EnhancedModel extends \Illuminate\Database\Eloquent\Model
{
/**
* The table associated with the model. Copies $table in Model
*
* #var string
*/
protected static string $tableName;
/**
* Get the table associated with the model. Copies getTable() in Model
*
* #return string
*/
public static function getTableName(): string
{
return static::$tableName ?? Str::snake(Str::pluralStudly(class_basename(static::class)));
}
/**
* Get the table associated with the model. Overrides getTable() in Model
*
* #return string
*/
public function getTable(): string
{
return $this::getTableName();
}
}
To override the auto-guessed table name, add this to your EnhancedModel descendent class:
protected static string $tableName = 'the_table_name';
Looks Laravel 6 make some changes. The following works fine for me
use Illuminate\Support\Str;
....
$className = 'App\\' . Str::studly(str::singular($table_name));
if(class_exists($className)) {
$model = new $className;
}

Yii 2.0 - Input values saved as null in database

In my practice app, I have a table called 'music' with 3 columns - id, title and artist. Whenever I try to insert the input values from the form to the database, a new record is added but only id has a value, title and artist are both null. Below is my model:
<?php namespace app\models;
/**
* This is the model class for table "music".
*
* #property integer $id
* #property string $title
* #property string $artist
*/
class MusicEntry extends \yii\db\ActiveRecord {
public $title;
public $artist;
public $id;
public static function tableName() {
return 'music';
}
public function rules() {
return [
[['title', 'artist'], 'required'],
[['id'], 'safe'],
];
}
} ?>
While my controller action looks like so:
public function actionMusicEntry() {
$model = new MusicEntry ();
if (isset ( $_POST ['MusicEntry'] )) {
$model->load($_POST);
if ($model->save()) {
Yii::$app->session->setFlash ( 'success', 'Model has been saved' );
$this->redirect ( [
'music-entry',
'id' => $model->id
] );
}
}
return $this->render ( 'music-entry', [
'model' => $model
] );
}
I've tried getting the value of artist and title after loading the model using $_POST and it has the values I inputted in the form. Given this, why is the input values saved as null in the database?
After further tweaking, I found the cause of the problem in the model. I had to remove the declaration for $artist and $title. I'm still not sure though why adding those variables caused such problem. Still looking into it.

FindBySql in yii returns null value

I am new to yii. I am facing a proble with findBySql Method. While i am trying to get a record through passing Mysql query and parameter, it returns me a null value.
Here my code looks like this..
In Model i have defined a function getCountry() to get the country name.
class StateMaster extends CActiveRecord
{
public function tableName()
{
return 'T_State_Master';
}
public function getCountry($c_id)
{
//return array(StateMaster::model()->findBySql("select C_Name from T_Country_Master where C_Id=:CountryId;",array(':CountryId'=>$c_id)));
$result = array(StateMaster::model()->findBysql("select C_Name from T_Country_Master where C_Id={$c_id}"));
return $result;
}
/**
* Returns the static model of the specified AR class.
* Please note that you should have this exact method in all your CActiveRecord descendants!
* #param string $className active record class name.
* #return StateMaster the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
}
Then in my view file trying to get the country name by providing the Country Id to it.
<?php $this->widget('zii.widgets.CDetailView', array(
'data'=>$model,
'attributes'=>array(
array(
'label'=>'State Name',
'value'=>$model->S_Name,
),
array(
'label'=>'Country Name',
'value'=>$model->getCountry($model->C_Id),
),
array(
'label'=>'Created Date',
'value'=>Yii::app()->dateFormatter->format("dd-MMM-yyyy", $model->CreatedDt),
),
array(
'label'=>'Created By',
'value'=>$model->CreatedBy,
),
),
)); ?>
whether i wonder why is it not giving me the result.
I have checked the parameter passed into it and its successfully passing.
Please give me solution.
Thanks in advance
change your function to this:
public function getCountry($c_id)
{
$query = "select C_Name from T_Country_Master where C_Id={$c_id}";
//return Yii::app()->db->createCommand($query)->queryAll(); // returns an array, so in your detail view, you must handle it first
return Yii::app()->db->createCommand($query)->queryScalar();
}
Try this way, but if i were you, i will use first one.
public function getCountry($c_id)
{
$query = "select C_Name from T_Country_Master where C_Id={$c_id}";
return Yii::app()->db->createCommand($query)->queryScalar();
}
OR
public function getCountry($c_id)
{
$criteria = new CDbCriteria;
$criteria->select="C_Name";
$criteria->addCondition('C_Id = $c_id');
$result = StateMaster::model()->find($criteria);
return $result;
}

Categories