How to implement a two-level relationship in Laravel 4? - php

I try to get the city name from an entity. I get my entity like this :
$entity = Entity::find(1);
And I would like to get the city name like this :
$entity->addresses->cities->name
But it doesn't work ? I tried multiple ways but impossible to get the city corresponding to the entity.
Here is my model :
Entity :
class Entity extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'entities';
public function addresses ()
{
return $this->hasMany('Address');
}
Address :
class Address extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'addresses';
public $timestamps = false;
public function cities ()
{
return $this->belongsTo('City');
}
public function entities ()
{
return $this->belongsTo('Entity');
}
City :
class City extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'cities';
public function addresses ()
{
return $this->hasMany('City');
}
Do I have to do an intermediate manipulation ? Or do I have to add something in my model ?
I have this error :
Undefined property: Illuminate\Database\Eloquent\Collection::$cities (View: /Applications/MAMP/htdocs/project/app/views/administrator/general.blade.php)
Thank you for your help

In your City model, I believe the addresses() function should return $this->hasMany('Address');
Also, there is some errors in how you are traversing the results of your find query.
Remember one Entity has many addresses, so if you are trying to do $entity->address->city, it has no idea which address you are talking about. You will have to loop through them.
Another thing I like to do is not pluralize all your functions. Just make the ones that will return many things plural. For example, one address will only ever have one city (how can one address belong to multiple cities?) It would be most helpful to change the function name to city() then. One city can have many addresses, so you'd want to have an addresses() function in your City model.
With that in mind, the code that should work for you is...
foreach($entity->addresses as $address) {
echo $address->city->name;
}

Since every entity can have multiple addresses and $entity->addresses() returns a Collection-object, you cant directly get all cities. You have to loop through all addresses and get the corresponding cities.
$cities = array();
foreach( $entity->addresses() as $address ) {
$cities[] = $address->city;
}

Related

How to copy datas from one table into another one with all columns but not id in Laravel?

I use Laravel 6.x and I need to copy datas from one table to another. As usual both of tables has many columns and I'm looking for a solution where I don't depend on column names. In the future maybe columns will be change and I don't want to touch this part of software on every changes.
I want to do something like this:
INSERT INTO product_copys (SELECT * from products);
I want to copy all columns without the id from the products table.
I use Product and ProductCopy models to handle these datas.
Is there any handy solution for this in Laravel, Eloquent?
You may use the following
In your App\Product Model
class Product extends Model
{
protected $hidden = ['id'];
//...
}
Then in your Controller
$copy = Product::all()->toArray();
ProductCopy::insert($copy);
If you need to process a lot (thousands) of Eloquent records, using
the chunk command will allow you to do without eating all of your RAM:
Product::chunk(200, function($products)
{
ProductCopy::insert($products->toArray());
});
https://laravel.com/docs/7.x/eloquent#chunking-results
I found a solution to get columns without id column (thanks to Laravel Tricks). Here is code for the Product model:
/**
* Get all columns of model
*
* #return Array
*/
public static function getTableColumns() {
$model = new Product();
return $model->getConnection()
->getSchemaBuilder()
->getColumnListing($model->getTable());
}
/**
* Return model's columns without given columns
*
* #param Array $without_columns
* #return Array
*/
public static function withoutColumn(Array $without_columns) {
return array_diff(self::getTableColumns(), $without_columns);
}
And here you can use it:
$columns = Product::withoutColumn(['id']);
Now need only run a raw SQL query in the controller:
public function backup() {
$columns = implode(',', Product::withoutColumn(['id']));
DB::statement('INSERT INTO product_copys ('.$columns.', date_of_backup) (SELECT ' .
$columns .', NOW() AS date_of_backup FROM products)');
return Response::HTTP_OK;
}

Populating parameters with default value using relationships in Laravel 5.2

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.

Laravel Removing Pivot data in many to many relationship

Not sure if I set this up correctly. In Laravel I'm creating two models with a many-to-may relationship
The models are Item and Tags. Each one contains a belongsTo to the other.
When I run a query like so:
Item::with('tags')->get();
It returns the collection of items, with each item containing a tags collection. However the each tag in the collection also contains pivot data which I don't need. Here it is in json format:
[{
"id":"49",
"slug":"test",
"order":"0","tags":[
{"id":"3","name":"Blah","pivot":{"item_id":"49","tag_id":"3"}},
{"id":"13","name":"Moo","pivot":{"item_id":"49","tag_id":"13"}}
]
}]
Is there anyway to prevent this data from getting at
you can just add the name of the field in the hidden part in your model like this:
protected $hidden = ['pivot'];
that's it , it works fine with me.
You have asked and you shall receive your answer. But first a few words to sum up the comment section. I personally don't know why you would want / need to do this. I understand if you want to hide it from the output but not selecting it from the DB really has no real benefit. Sure, less data will be transferred and the DB server has a tiny tiny bit less work to do, but you won't notice that in any way.
However it is possible. It's not very pretty though, since you have to override the belongsToMany class.
First, the new relation class:
class BelongsToManyPivotless extends BelongsToMany {
/**
* Hydrate the pivot table relationship on the models.
*
* #param array $models
* #return void
*/
protected function hydratePivotRelation(array $models)
{
// do nothing
}
/**
* Get the pivot columns for the relation.
*
* #return array
*/
protected function getAliasedPivotColumns()
{
return array();
}
}
As you can see this class is overriding two methods. hydratePivotRelation would normally create the pivot model and fill it with data. getAliasedPivotColumns would return an array of all columns to select from the pivot table.
Now we need to get this integrated into our model. I suggest you use a BaseModel class for this but it also works in the model directly.
class BaseModel extends Eloquent {
public function belongsToManyPivotless($related, $table = null, $foreignKey = null, $otherKey = null, $relation = null){
if (is_null($relation))
{
$relation = $this->getBelongsToManyCaller();
}
$foreignKey = $foreignKey ?: $this->getForeignKey();
$instance = new $related;
$otherKey = $otherKey ?: $instance->getForeignKey();
if (is_null($table))
{
$table = $this->joiningTable($related);
}
$query = $instance->newQuery();
return new BelongsToManyPivotless($query, $this, $table, $foreignKey, $otherKey, $relation);
}
}
I edited the comments out for brevity but otherwise the method is just like belongsToMany from Illuminate\Database\Eloquent\Model. Of course except the relation class that gets created. Here we use our own BelongsToManyPivotless.
And finally, this is how you use it:
class Item extends BaseModel {
public function tags(){
return $this->belongsToManyPivotless('Tag');
}
}
If you want to remove pivot data then you can use as protected $hidden = ['pivot']; #Amine_Dev suggested, so i have used it but it was not working for me,
but the problem really was that i was using it in wrong model so i want to give more detail in it that where to use it, so you guys don't struggle with the problem which i have struggled.
So if you are fetching the data as :
Item::with('tags')->get();
then you have to assign pivot to hidden array like below
But keep in mind that you have to define it in Tag model not in Item model
class Tag extends Model {
protected $hidden = ['pivot'];
}
Two possible ways to do this
1. using makeHidden method on resulting model
$items = Item::with('tags')->get();
return $items->makeHidden(['pivot_col1', 'pivot_col2']...)
2. using array_column function of PHP
$items = Item::with('tags')->get()->toArray();
return array_column($items, 'tags');

Doctrine Compund Keys

I have these 3 table
"Business" with these fields: Id, Name, etc..
"City" with these fields: Id, Name, etc..
And then I have a table called BusinessCity (given that a bussines can be related to many cities). This table has the fields "BusinessId" and "CityId".
Im trying to relate the CityId to the City entity, and BusinessId to the business entity, on the class BusinessCity. I've been googling this for the past 3 days and couldnt find an answer, if this has been asked before im sorry i didnt see it. Could anyone help me or give me some pointers on how to get this done. Thanks in advance
What you are trying to achieve is a bi-directional many-to-many relation with a joinTable.
Many businesses can reside in multiple cities and in one city there can be multiple businesses.
In a many-to-many relationship either side can be the owning side. JoinTable definition can be left out and has sensible defaults but if you want to specify it concretely i included it in the example.
Business (in this example: owning side = inversedBy = JoinTable definition)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/City", inversedBy="businesses",cascade="{persist,merge}" fetch="EAGER")
* #ORM\JoinTable(name="BusinessCity",
* joinColumns={#JoinColumn(name="business_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="city_id", referencedColumnName="id")}
* )
*/
protected $cities;
public function __construct()
{
$this->cities = new ArrayCollection();
}
public function getCities()
{
return $this->cities;
}
public function setCities(Collection $cities)
{
// using map with closure to have dublicate/type-checking provided by addCity
$this->cities->map(function($city) {
$this->addCity($city);
});
return $this;
}
public function addCity(CityInterface $city)
{
// ... you don't want dublicates in your collection
if (!$this->cities->contains($city)) {
$this->cities->add($city);
}
return $this;
}
public function removeCity(CityInterface $city)
{
$this->cities->removeElement($city);
return $this;
}
// other properties and methods ..
City (inverse side = mappedBy)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/Business", mappedBy="cities")
*/
protected $businesses;
// getters & setters ...
// other properties and methods ...
This is actually pretty simple, all you have to do is define your JoinTable. It's not easy to find in the docs, but there is an example in the section Composite Primary Keys.
In short, all you have to do is use oneToMany/manyToOne-associations with the class representing your JoinTable instead of directly associating both Business and City with ManyToMany-associations.

Using AR findAll function giving only one object back when using the with statement

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

Categories