I have 3 tables
products
pages
gallery - two foreign keys page_id, product_id
(are the foreign keys supposed to be mapped together UNIQUE, why?)
<?php
class Gallery extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'gallery';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public static $rules = array(
// 'email'=>'required|email|unique:users',
// 'password'=>'required|alpha_num|between:6,12|confirmed',
// 'password_confirmation'=>'required|alpha_num|between:6,12'
);
public function pages() {
return $this->belongsTo('Pages', 'page_id');
}
public function products() {
return $this->belongsTo('Products', 'products_id');
}
}
Pages Model
<?php
class Pages extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'pages';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public static $rules = array(
'title'=>'required',
'body'=>'required'
// 'email'=>'required|email|unique:users',
// 'password'=>'required|alpha_num|between:6,12|confirmed',
// 'password_confirmation'=>'required|alpha_num|between:6,12'
);
public function gallery() {
return $this->hasOne('Gallery');
}
}
Products Model
<?php
class Products extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'products';
// public $timestamps = false;
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
public static $rules = array(
'title'=>'required',
'body'=>'required'
// 'email'=>'required|email|unique:users',
// 'password'=>'required|alpha_num|between:6,12|confirmed',
// 'password_confirmation'=>'required|alpha_num|between:6,12'
);
public function gallery()
{
return $this->hasOne('Gallery');
}
}
I'm doing this in PagesController:
$page = Pages::find($page_id);
$page->gallery throws this error Call to undefined method Gallery::newQuery();
I created the Gallery table and foreign keys using migrations. Why do i have to create relationships between the models, these relationships are defined in the database? (le noob question)
2.Read a lot about what could be the cause of this and it's not namespacing.
Been stuck on this for 3 days now, any help is greatly appreciated.
I think is has something to do with my model relationships.
=============================== UPDATE =========================
Okay, so i did $gallery = new Gallery; in my PageController, and this seems to worky
But now i get this funky error:
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (spaggo.gallery, CONSTRAINT gallery_product_id_foreign FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE ON UPDATE CASCADE) (SQL: insert into gallery (page_id, updated_at, created_at) values (6, 2015-06-21 15:24:51, 2015-06-21 15:24:51))
First, What's your Laravel version? It actually might be due to namespacing in spite of your assumption.
Secondly, as a convention I recommend you name your models singular such as Product and Page.
To answer your other question: Yes, your database may contain constrains for these relations, but unfortunately Eloquent ORM can't make use of them. You're still required to define these relations at the model level.
Shouldn't it be extends Model for all Laravel models (unless of course it's some old syntax) and that Model must be declared via use statement: use Illuminate\Database\Eloquent\Model;.
I know first hand that if I forget to add Model parent class to my models then Call to undefined method App\SomeClass::newQuery error follows.
Related
I'm working on a Laravel 8 project, it's being used as an API for a frontend. My API contains Brands and Forms, a brand is created by a user and a brand can have a form.
My brand schema contains a slug column, it's this column that's present in my front-end URLs, the slug column is unique, e.g:
/account/brands/my-brand/forms/
/account/brands/my-brand/forms/create/
A form has a brand_id column, this is later used as part of Laralve's hasOne and hasMany relationship for automatic joining since it's easier this way and means I don't have to have an ugly URL.
The trouble I'm having is when I want to show the user a list of their forms for the brand they're on I don't have access to the brand_id in the request, only the slug as this is part of the URL, whereas to retrieve my forms I need the brand_id.
How could I easily (without having another function) obtain this whilst still ensuring my relationships in my model work correctly?
Retrieving forms for a brand that belong to a user
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index($brand)
{
// $brand -> this is the slug, unsure how to utilise this on the joined brand
$forms = Form::where('user_id', Auth::id())->with('brand')->get();
return response()->json([
'forms' => $forms
], 200);
}
My Brand model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Brand extends Model
{
use HasFactory, SoftDeletes;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'brands';
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'uuid',
'brand',
'url',
'telephone',
'link_terms',
'link_privacy',
'seo_description',
'base64_logo',
'base64_favicon',
'text_marketing',
'text_promos',
'text_broker',
'text_footer_1',
'text_footer_2',
'text_credit_disclaimer',
'analytics_internal_affiliate'
];
/**
* The relationships that should always be loaded.
*
* #var array
*/
protected $with = [
'form'
];
/**
* Get the form associated with the user.
*/
public function form()
{
return $this->hasOne(Form::class);
}
/**
* Get the brand that owns the comment.
*/
public function brand()
{
return $this->belongsTo(User::class);
}
}
My Form model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Form extends Model
{
use HasFactory, SoftDeletes;
/**
* The table associated with the model.
*
* #var string
*/
protected $table = 'forms';
/**
* The attributes that are mass assignable.
*
* #var string[]
*/
protected $fillable = [
'name',
'loan_amount',
'loan_min_amount',
'loan_max_amount',
'loan_term',
'loan_min_term',
'loan_max_term'
];
/**
* Get the brand that owns the comment.
*/
public function brand()
{
return $this->belongsTo(Brand::class);
}
}
The path of least resistance would be to harness the whereRelationship() method on your query.
I believe that would look like this:
$forms = Form::where('user_id', Auth::id())
->whereRelationship('brand', '=', $brand)
->with('brand')
->get();
https://laravel.com/docs/master/eloquent-relationships
An alternative which would be more work to set up but would likely make your life easier long term would be to use route model binding. https://laravel.com/docs/master/routing#route-model-binding
You can ask Laravel's IoC container to resolve the full record for you by typehinting it and naming the variable correctly in the method signature.
Since you are using a column other than id to identify it, you need to inform Laravel of this. There are a couple of different ways to do this, discussed at https://laravel.com/docs/master/routing#customizing-the-default-key-name
Then, you can ask Laravel's IoC container to build an instance of the model by searching in the database for you right in the method signature. You simply need to typehint the parameter and ensure it's name matches that in the route definition.
Okay, so I have a question. I'm programming a really complex report and the interface uses Laravel 5.2. Now the thing is that, depending on certain conditions, the user does not always need all parameters to be filled. However, for simplicity purposes, I made it so that the report always receives the complete set of parameters no matter what. So I have three tables:
tblReportParam
ID
ParamName
DefaultValue
tblReportParamValue
ParamID
ReportID
Value
tblReport
ID
UserName
Now, I have a solution that works, but for some reason, it just feels like I should be able to make better use of models and relationships. I basically have just my models and controllers and solved the whole thing using SQL.
It feels somewhat close to this but not quite. So basically, you need to always load/save all parameters. If parameter x is actually defined by the user then you use his definition otherwise you go with the default defined in tblReportParam. Anyone has any idea how to do this?
EDIT:
Okay, so I checked Eddy's answer and tried to work it in our system, but another colleague of mine started implementing a many-to-many relationship between the tblReport and the tblReportParam table with the tblReportParamValue acting as the pivot so I'm having some difficulty adapting this solution for our system. Here's the two models:
class ReportParam extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblReportParam';
protected $primaryKey = 'ID';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['ID', 'NomParam', 'DefaultValue'];
public function renourapports()
{
return $this->belongsToMany('App\Report');
}
}
class Report extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'tblReport';
protected $primaryKey = 'ID';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['ID', 'NoEmploye', 'NoClient', 'NoPolice', 'DateCreation', 'DateModification', 'runable', 'DernierEditeur'];
public $timestamps = false;
public function params()
{
return $this->belongsToMany('App\ReportParam ', 'tblReportParamValue', 'ReportID', 'ParamID')->withPivot('Valeur');
}
}
Now this actually is a pretty neat solution, but it only works if the parameter is actually in the pivot table (i.e. the relationship actually exists). What we want is that for the parameters that aren't in the pivot table, we simply want their default value. Can Eddy's solution work in this case?
Using Eloquent models
class ReportParam extends Model
{
public function paramValue() {
return $this->hasOne('App\ReportParamValue', 'ParamID');
}
public function getDefaultValueAttribute($value) {
if ( $this->paramValue ) return $this->paramValue->Value; //relationship exists
return $this->DefaultValue;
}
}
$reportParam->value; // return the relationship value or the default value;
UPDATE
Now that tblReportParamValue is a pivot table you should redefine your relationships. In ReportParam model add
public function reports() {
return $this->belongsToMany('App\Report', 'tblReportParamValue', 'ParamID', 'ReportID')->withPivot('Value');
}
And in Report model, defined the opposite
public function params() {
return $this->belongsToMany('App\ReportParam', 'tblReportParamValue', 'ReportID', 'ParamID')->withPivot('Value');
}
Now getting the default value from ReportParam becomes too complicated because it will one ReportParam has Many Reports. So doing $reportParam->reports() will bring back every single report that uses that paramID in the pivot table. Therefore looking for a value would mean going through all the reports. We could avoid that by changind the function definition.
public function getDefaultValue($reportID) {
$reportValue = $this->reports()->wherePivot('ReportID', $reportID)->first();
return $reportValue ? $this->reportValue->Value : $this->DefaultValue;
}
//In Controller
$report = Report::find(1);
$reportParam = ReportParam::find(1);
$reportParam->getDefaultValue($report->ID);
Ok I think this might work. If it doesnt, I am really sorry, I don't know any better.
When I retrieve the count for my results via laravel's paginator ($var->total() function) OR adding on ->count() to my DB query, it returns the incorrect count. Even when there is ZERO items on the page after a filter is applied, it still shows the incorrect number. This number stays the same, it doesn't change - it's the total # of rows in my parent model. However, again - it shouldn't show the total # of rows in my parent model - it should show the rows that are available after a filter is applied.
Filtered Query in Question:
$orders = list_formstack::with(['order' => function($query) use ($checktv, $dstart, $dend, $thestatus, $pharmacy, $searchid, $searchsubid, $searchrefill, $searchgrams, $filterrep) {
$query->whereNotNull('subid');
if($checktv == "No") {
$query->whereNull('intake');
}
elseif($checktv == "Yes") {
$query->whereNotNull('intake');
}
if($dstart && $dend)
$query->whereBetween('orders.created_at', [$dstart, $dend]);
if($thestatus)
$query->where('orders.status', $thestatus);
if($pharmacy)
$query->where('pharmacy', $pharmacy);
if($searchid)
$query->where('orders.id', $searchid);
if($searchsubid)
$query->where('orders.subid', $searchsubid);
if($searchrefill)
$query->where('refill', $searchrefill);
if($searchgrams)
$query->where('grams', $searchgrams);
if($filterrep)
$query->where('salesrep', $filterrep);
}])->where(function($query) use ($prodgroup, $state, $search, $columns, $searchpatient, $searchcarrier) {
if($state)
$query->where('state', $state);
if($searchpatient)
$query->where('first', 'like', $searchpatient)->orWhere('last', $searchpatient)->orWhere('phone', $searchpatient);
if($searchcarrier)
$query->where('insurancecarrier', 'like', $searchcarrier);
})->orderBy('created_at', 'desc')->paginate(50);
Parent Model:
class list_formstack extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'formstack';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['parentid','first','last','state','insurancecarrier'];
/**
* Get the form for an order
*/
public function order()
{
return $this->hasMany('App\list_orders', 'formid', 'id')->groupBy('id');
}
}
Child / Relationship Model:
class list_orders extends Model
{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'orders';
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = ['id','status','subid','formid','salesrep','billedamt','copayamt','completed','refillcount','gramsleft','billedamt','copayamt','salesrep','intake','created_at'];
public function formstack()
{
return $this->belongsTo('App\list_formstack', 'orderid');
}
}
So as you can see in my Eloquent query, I filter out results based on whether a user chooses that filter or not. So if I filter out "thestatus" to one specific status, it shows the correct results for only that status HOWEVER the count DOES NOT change.
I even tried to throw the results into a collection:
$count = collect($orders);
$count->count();
No luck - same incorrect number.
Any insight on this? What am I doing wrong? Thanks in advance!
EDIT: I should note that using Laravel's Query Builder (NO Eloquent ORM Models) results in the pagination count working correctly.
Parent Model DB Table
Parent Model Table Structure
Child Model DB Table
Child Model Table Structure
So I have a pension type:
class Type extends Base
{
use RecordStatusActiveTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'pn_pension_type';
/**
* The actual primary key for the model.
*
* #var string
*/
protected $primaryKey = 'pension_type_id';
// Belongs To -----
public function pensionClass() {
return $this->belongsTo('App\Models\Control\PensionClass', 'pension_class_id');
}
// Belongs To Many -----
public function qualifiers()
{
return $this->belongsToMany('App\Models\Pension\Qualifier', 'pn_pension_type_qualifier', 'type_id', 'qualifier_id');
}
}
And a pension Qualifier:
class Qualifier extends Base
{
use RecordStatusActiveTrait;
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'pn_pension_qualifier';
/**
* The attributes that should be casted to native types.
*
* #var array
*/
protected $casts = [
'rules' => 'array',
];
// Belongs To Many -----
public function types()
{
return $this->belongsToMany('App\Models\Pension\Type', 'pn_pension_type_qualifier', 'qualifier_id', 'type_id');
}
}
They have a many to many relationship with a table in between as normal. I would like to grab a set of pension types and find the unique Qualifiers between all of them. I COULD just foreach through each of their qualifiers and end up with a list of unique qualifiers but I was wondering if their was an "Eloquent" way.
Using whereHas will allow you to restrict the results based on the relation. In regards to what you want, you'd want to query from your Qualifiers like so.
Qualifiers::whereHas('pensions', function($query) use ($pensionIds) {
$query->whereIn('id', $pensionIds);
})->distinct()->get();
Adding on ->distinct() will then get you the unique entries.
I am having an issue with my Yii install where I am trying to get a fairly basic query back but I am not getting the results back that the online tutorials are saying that I should get. I have 2 models that look roughly like this:
Pricing:
class Pricing extends CActiveRecord
{
/**
* Returns the static model of the specified AR class.
* #param string $className active record class name.
* #return Pricing the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'pricing';
}
/**
* #return string the primary key
*/
public function primaryKey(){
return 'ID';
}
...
/**
* #return array relational rules.
*/
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(
'xpricing_routes' => array(self::HAS_MANY, 'PricingRoutes', 'ID_pricing'),
);
}
and PricingRoutes:
class PricingRoutes extends CActiveRecord
{
/**
* Returns the static model of the specified AR class.
* #param string $className active record class name.
* #return PricingRoutes the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* #return string the associated database table name
*/
public function tableName()
{
return 'pricing_routes';
}
/**
* #return string the primary key
*/
public function primaryKey(){
return 'ID';
}
...
/**
* #return array relational rules.
*/
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(
'xpricing' => array(self::BELONGS_TO, 'Pricing', 'ID_pricing'),
);
}
Then in the controller we have:
$criteria = new CDbCriteria;
$criteria->with = array('xpricing_routes');
$criteria->together=true;
$pricing_records = Pricing::model()->findAll($criteria);
$pricing_records_arr = CHtml::listData($pricing_records, 'id', 'name');
echo '<pre>';
print_r($pricing_records);
print_r($pricing_record_arr);
echo '</pre>';
As you probably already know, we have 2 tables called pricing and pricing_routes. The pricing routes table has a foreign key called ID_pricing that goes to the ID field in the pricing table. The pricing table has one entry and the pricing_routes table has 4 entries that all have the primary key for the one item in the pricing table in the ID_pricing field. So we should be getting 4 results to the query that we are running and when I run the query that Yii is generating with AR, that is what I get.
The problem that we are having is that the $pricing_records variable is an array that only has one Pricing object. That object contains the data we need but not really in a usable fashion. The $pricing_records_arr variable is just an empty array. The documentation that I have found seems to suggest that we should be getting an array of pricing objects each containing the information from the pricing_routes table as well. We are aware that using AR may not be the best way to get this data but we have our reasons for getting this to work so any ideas on how to do that would be much appreciated.
EDIT:
It turns out that this ended up being a misunderstanding of what I was getting back. The comments on this question gave me the information that I needed.
If you call
$pricing_records = Pricing::model()->findAll($criteria);
you'll get only one active record with properties filled with the values from your table "pricing".
If you want to get all the records from "pricing_routes" belonging to this specific "pricing" you have to call
$pricing_records->xpricing_routes
where "xpricing_routes" is the name of the relationship you have correctly defined in your model. It returns array of PricingRoutes or null if there is no matching record.
if you use findall it will return an array even there is one record
$pricing_records = Pricing::model()->findAll($criteria);
foreach($pricing_records as $var){
$var->xpricing_routes->ID_pricing;//any variable
}
or
if there only one field you can use
$pricing_records = Pricing::model()->find($criteria);
$pricing_records->xpricing_routes->ID_pricing;//any variable