Working with a MY_Model - php

I’m using jamie Rumbelow’s MY model as a way to better deal with my application.
https://github.com/jamierumbelow/codeigniter-base-model
The MY_model is the same except I have an added in variable for defining whether or not an item in the db is marked as being soft deleted or not.
protected $soft_delete_value = 3;
I only have that variable defined and have not altered his code yet to account for this value.
I have two things I want to do with this titles model that I need help understanding.
Titles Table - title_id, title_name, title_status_id
Title_Statuses_Table - title_status_id, title_status_name
What I want it to do is retrieve all of the rows that have a title_status_id of 1 and 2 and 3 because the soft delete value is different than the default set in the MY Model. What I would also like to have is instead of it returning the integer have it return the name of the status.
Expected results:
An array of objects that contain a title_id, title_name, title_status_name for which the titles have a status id of 1,2, or 3.
Testing
$titles = $this->titles_model->get_all();
echo "<pre>";
print_r($titles);
echo "</pre>";
Actual results:
SELECT *
FROM (`titles`)
WHERE `title_status_id` = 0
<pre>Array
(
)
My Code
class Titles_model extends MY_Model
{
/* --------------------------------------------------------------
* VARIABLES
* ------------------------------------------------------------ */
/**
* This model's default database table.
*/
public $_table = 'titles';
public $primary_key = 'title_id';
/**
* Support for soft deletes and this model's 'deleted' key
*/
public $soft_delete = TRUE;
public $soft_delete_key = 'title_status_id';
public $soft_delete_value = 4;
public $_temporary_with_deleted = FALSE;
public function __construct()
{
parent::__construct();
}
}
Anybody else have any additional ideas/suggestions?
EDIT:
I've been tryign to figure this out all day and have hit a dead end.

well here would be the function that you would need to get your expected result
$this->db->select('
titles.*,
status.*,
')
->join('status s', 'titles.title_status_id = s.title_status_id', 'LEFT')
->where('titles.title_status_id', 1)
->or_where('titles.title_status_id', 2)
->or_where('titles.title_status_id', 3)
->from('titles')
->get()
->result_object();

Related

Am I doing eager loading correctly? (Eloquent)

I have a method that needs to pull in information from three related models. I have a solution that works but I'm afraid that I'm still running into the N+1 query problem (also looking for solutions on how I can check if I'm eager loading correctly).
The three models are Challenge, Entrant, User.
Challenge Model contains:
/**
* Retrieves the Entrants object associated to the Challenge
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
Entrant Model contains:
/**
* Retrieves the Challenge object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function challenge()
{
return $this->belongsTo('App\Challenge', 'challenge_id');
}
/**
* Retrieves the User object associated to the Entrant
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo('App\User', 'user_id');
}
and User model contains:
/**
* Retrieves the Entrants object associated to the User
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function entrants()
{
return $this->hasMany('App\Entrant');
}
The method I am trying to use eager loading looks like this:
/**
* Returns an array of currently running challenges
* with associated entrants and associated users
* #return array
*/
public function liveChallenges()
{
$currentDate = Carbon::now();
$challenges = Challenge::where('end_date', '>', $currentDate)
->with('entrants.user')
->where('start_date', '<', $currentDate)
->where('active', '1')
->get();
$challengesObject = [];
foreach ($challenges as $challenge) {
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all();
$entrantsObject = [];
foreach ($entrants as $entrant) {
$user = $entrant->user;
$entrantsObject[] = [
'entrant' => $entrant,
'user' => $user
];
}
$challengesObject[] = [
'challenge' => $challenge,
'entrants' => $entrantsObject
];
}
return $challengesObject;
}
I feel like I followed what the documentation recommended: https://laravel.com/docs/5.5/eloquent-relationships#eager-loading
but not to sure how to check to make sure I'm not making N+1 queries opposed to just 2. Any tips or suggestions to the code are welcome, along with methods to check that eager loading is working correctly.
Use Laravel Debugbar to check queries your Laravel application is creating for each request.
Your Eloquent query should generate just 3 raw SQL queries and you need to make sure this line doesn't generate N additional queries:
$entrants = $challenge->entrants->load('user')->sortByDesc('current_total_amount')->all()
when you do ->with('entrants.user') it loads both the entrants and the user once you get to ->get(). When you do ->load('user') it runs another query to get the user. but you don't need to do this since you already pulled it when you ran ->with('entrants.user').
If you use ->loadMissing('user') instead of ->load('user') it should prevent the redundant call.
But, if you leverage Collection methods you can get away with just running the 1 query at the beginning where you declared $challenges:
foreach ($challenges as $challenge) {
// at this point, $challenge->entrants is a Collection because you already eager-loaded it
$entrants = $challenge->entrants->sortByDesc('current_total_amount');
// etc...
You don't need to use ->load('user') because $challenge->entrants is already populated with entrants and the related users. so you can just leverage the Collection method ->sortByDesc() to sort the list in php.
also, You don't need to run ->all() because that would convert it into an array of models (you can keep it as a collection of models and still foreach it).

Getting value of joined table Yii 2

Hello I am trying to access a value from a joined table schead.section to store in subjectcontainer.section, mainly I am using the scstock data but the section part is located in schead.section so what I did was to join the schead and schstock together so that I can have access to the section column. Here is what I did.
$subject = ActiveCurriculum::find()
->select('scstock.*')
->leftJoin('schead', 'schead.TrNo = scstock.TrNo')
->where([ 'schead.TrNo' => $TrNo])
->one();
$activesubject = new ActiveSubject();
$activesubject->clientid = $clientid;
$activesubject->TrNo = $subject->TrNo;
$activesubject->subjectcode = $subject->subjectcode;
$activesubject->schedday = $subject->schedday;
$activesubject->schedtime = $subject->schedtime;
$activesubject->section = $subject->section;
$activesubject->room = $subject->room;
$activesubject->units = $subject->units;
$activesubject->save();
//reduces the slot of ccsubject by 1
$subject->slots = $subject->slots - 1;
//never forget the saving part
$subject->save();
First $subject will access sctock table to join will schead via TrNo. then $activesubject will access subjectcontainer table to store the values in. Now my problem is I am getting this error.
Can someone help me in trying to solve this?
Make sure to define relation to the Schead object in your model. Example of such relation:
/**
* #property $schead Schead
*/
class YourModel extends \yii\db\ActiveRecord
{
/**
* #return Schead
*/
public function getSchead()
{
return $this->hasOne(Schead::className(), ['field1' => 'field2']);
}
}
Also, $subject->schead.section is a wrong way of accessing related model attributes. Use $subject->schead->section instead. If having schead is optional, don't forget to check existence of related object first, for example:
$subject->schead ? $subject->schead->section : null
Also check code for typos (probably sched / schead?). You can read more about working with relations in official docs.

Eloquent next record

I am trying to get the last 3 blog post from the database into single variables (for templates). I saw a good implementation at some other thred and it works fine for the next record but on the third query returns with NULL. What is your opinion about this problem?
BlogController.php:
public function getIndex($l = 'hu')
{
$post_last = Post::orderBy('created_at', 'desc')->first();
$post_2 = $post_last->next($post_last->created_at);
$post_3 = $post_2->next($post_2->created_at);
var_dump($post_3);
}
Post.php:(Model)
<?php
namespace Civitas;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
/**
* Physical table name
*/
protected $table = 'posts';
/**
* Get next result in record list
*
* #param $created_at
* #return mixed
*/
public function next($c) {
return Post::where('created_at', '<', $c)->get()->first();
}
}
I can't tell why your function doesn't work, but I suggest you try this approach:
$posts = Post::orderBy('created_at', 'desc')->take(3)->get();
$post1 = $posts->shift();
$post2 = $posts->shift();
$post3 = $posts->shift();
This will only run one query instead of three. Calling shift() on the collection will then return the first item and remove it so the second post will be "first" the next time you call it.
In your next function, the result will give the earliest post, not the next one. Therefore, the third call will return null because there is no post after the earliest one. Adding orderBy for created_at field, it will work as expected.
public function next(){
return static::where('created_at', '<' , $this->created_at)
->orderBy('created_at','desc')
->first();
}
Then in your controller, you can call like this:
$post_last = Post::orderBy('created_at', 'desc')->first();
$post2 = $post_last->next();
$post3 = post_last->next()->next();

Using \Zend_Db_Table_Abstract::find($id). MySQL SET field returns string instead of (wanted) int

Basic question How can I fetch the 'type' column as integer value from inside the table mapper?
I have a PHP Zend Framework 1.12 application running a website. Inside MySQL are multiple tables with multiple columns.
Inside two tables I use the SET type. The column is named 'type' and as 'set('LOCAL','EXTERNAL')'. Don't mix up this field type with the ENUM please!
So far no problems, querying the table and fetching the type column as INT or STRING is not a problem:
$Sql = $Db->select()->from('tablename', ['type_as_int' => new \Zend_Db_Expr('type+0')]); //returns INT (if both are selected: 3)
$Sql = $Db->select()->from('tablename', ['type']); //returns STRING (if both are selected: LOCAL,EXTERNAL)
But, in this application also has table mappers that extend Zend_Db_Table_Abstract.
Inside the mapper resides the 'find()' method. Default built in into the abstract to find records by their primary key.
But.. When I use the object to fetch a record , I find the following response inside my populate method:
array([type] => LOCAL,EXTERNAL)
Querying it by hand (and defining the columns myself) would be an options ($this->select()->from...), but isn't there a more elegant way?
(I know that I am using an older version of ZF, but upgrading would cost too much time at this moment.)
After the bounty was started I noticed that there wasn't a really simple anwer, so I began looking deeper into Zend Framework 1.12 and the mapper objects that I use.
I noticed that the 'find()' method just uses the primary key columns to build a query.
So starting with that knowledge I built my own 'find()' method which resides in the 'abstract model mapper' and uses the 'find()' mapper inside the class that extends \Zend_Db_Table_Abstract
/* sample abstract mapper class with find */
abstract class MapperAbstract {
/*
* Zend db table abstract object
* #var \Zend_Db_Table_Abstract
*/
private $DbTable;
public function find($id, $Model) {
$Select = $this->$DbTable->select(\Zend_Db_Table_Abstract::SELECT_WITH_FROM_PART);
//Fetch record and populate model if we got
//a result
$Row = $this->$DbTable->fetchRow($Select);
//do some extra shizzle
if ($Row !== null) {
return $Model->populate((array)$Row);
}
return;
}
}
Now I need to add a method that overrides the default columns.
So I created a method called 'overrideColumns()' that return an array filled with column names that need to be selected or must be overriden.
/**
* Returns array with columns that need to be overridden
* or selected as extra
* #return array
*/
public function overrideColumns() {
return ['type' => new \Zend_Db_Expr('type+0')];
}
And from that point I only needed to adjust the $Select query so it would use the 'overrideColumns()' method.
So the full class becomes something like:
/* sample abstract mapper class with find */
abstract class MapperAbstract {
/*
* Zend db table abstract object
* #var \Zend_Db_Table_Abstract
*/
private $DbTable;
/**
* Returns array with columns that need to be overridden
* or selected as extra
* #return array
*/
private function overrideColumns() {
return ['type' => new \Zend_Db_Expr('type+0')];
}
public function find($id, $Model) {
$Select = $this->DbTable->select(\Zend_Db_Table_Abstract::SELECT_WITH_FROM_PART);
//Check if we need to override columns in the select query
$overrideColumns = $this->getOverrideColumns();
if (!empty($overrideColumns)) {
$Select->columns($overrideColumns); //overrides the columns
}
//Add where clause to the query
//I know there can also be a compound primary key, but
//I'm just ignoring that in this example
$Select->where($this->DbTable->getPrimaryKeyColumn() . ' = ?', $id);
//doing some extra shizzle
//that is not important when I want to explain stuff
//Fetch record and populate model if we got a result
$Row = $this->DbTable->fetchRow($Select);
if ($Row !== null) {
return $Model->populate((array)$Row);
}
return;
}
}
So.. after a while I found the answer I was looking for, without having to declare all columns.

Laravel-eloquent: Call to undefined method Illuminate\Database\Eloquent\Collection::where()

I have two models in many-to-one relationship:
class Meal extends \Eloquent {
/**
* public Integer $id; - primary key
* public String $name;
*/
protected $fillable = array('id','name');
public function mealProperties()
{
return $this->hasMany('MealProperty');
}
}
class MealProperty extends \Eloquent {
/**
* public Integer $id; - primary key
* public Integer $meal_id;
*/
protected $fillable = array('id','meal_id');
public function meal()
{
return $this->belongsTo('Meal', 'meal_id');
}
}
if I ask for first meal first mealProperty everything go fine:
$mealProp = Meal::first()->mealProperties->first();
but if I ask for mealProperty with specific id of first meal this way:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
I get this error:
Call to undefined method Illuminate\Database\Eloquent\Collection::where()
I google what I'm doing wrong two hours, but still nothing.
If I can't use where method, what is possible way to get specific mealProperty?
Thank you for help!
UPDATE for Laravel 5:
Since v5 release there is a method where on the Support\Collection object, so this question/answer becomes irrelevant. The method works exactly like filter, ie. returns filtered collection straight away:
$mealProp = Meal::first()->mealProperties->where('id','=','1'); // filtered collection
// that said, this piece of code is perfectly valid in L5:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
You must distinguish Laravel behaviour:
(dynamic property) Eloquent Collection or Model
$meal->mealProperties
Relation Object
$meal->mealProperties()
Now:
// mealProperties is Eloquent Collection and you call first on the Collection here
// so basically it does not affect db query
$mealProp = Meal::first()->mealProperties->first();
// here you try to add WHERE clause while the db query is already called
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
// So this is what you want to do:
$mealProp = Meal::first()->mealProperties()->where('id','=','1')->first();
You may try this:
$mealProop1 = Meal::first()->mealProperties->find(1); // id = 1
Or something like this:
$mealProops = Meal::first()->mealProperties;
$mealProop5 = $mealProops->find(5); // id = 5
$mealProop7 = $mealProops->find(7); // id = 7
Instead of this:
$mealProp = Meal::first()->mealProperties->where('id','=','1')->first();
Also, following should work:
$mealProp = Meal::first()->mealProperties->first();

Categories