I would like to implement class (multi) table inheritance in Yii but i found it very difficult, so i planned to use the approach of MySQL view.
Here is an example of my tables and classes:
CmsAd is a table that inherits all the fields included in CmsContent table:
CREATE TABLE `CmsAd` (
`Id_CmsAd` varchar(32) NOT NULL,
`Image` varchar(300) DEFAULT NULL,
`Id_PrdClass` varchar(32) DEFAULT NULL,
`Id_PrdCatalog` varchar(32) DEFAULT NULL,
PRIMARY KEY (`Id_CmsAd`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CREATE TABLE `CmsContent` (
`Id_CmsContent` varchar(32) NOT NULL,
`ModifiedBy` varchar(32) DEFAULT NULL,
`ModifiedOn` datetime DEFAULT NULL,
`CreatedBy` varchar(32) DEFAULT NULL,
`CreatedOn` datetime DEFAULT NULL,
`Status` varchar(99) DEFAULT NULL,
`Subject` varchar(350) DEFAULT NULL,
`Text` longtext,
`KeyWord` varchar(300) DEFAULT NULL,
`Code` varchar(340) DEFAULT NULL,
PRIMARY KEY (`Id_CmsContent`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
CmsAd_View is a MySQL view that contains the join of those two tables :
CREATE VIEW `CmsAd_View` AS SELECT `CmsAd`.*, `CmsContent`.* FROM `CmsAd` LEFT JOIN `CmsContent` ON `CmsContent`.`Id_CmsContent` = `CmsAd`.`Id_CmsAd`;
Here are the models of those tables :
class CmsAd extends CActiveRecord {
public function tableName() {
return 'CmsAd_View';
}
}
class CmsContent extends CActiveRecord {
public function tableName() {
return 'CmsContent';
}
}
Notice that the table name of CmsAd is the view CmsAd_View.
Now I would like implement the CRUD the CmsAd. It's ok with find() method because it retrieves from the view CmsAd_View.
My Problem is with insert() and update() methods where we have to insert and update both tables CmsAd and CmsContent.
Is there any one who tried to implement the view approach of table inheritance in Yii ?
Related
I have 3 models: User, Payment and Log. A User has many Payment and both User and Payment have many Log.
User Model
class User
{
public function payments()
{
return $this->hasMany('Payment', 'user_id');
}
public function logs()
{
return $this->morphMany(Log::class, 'loggable');
}
}
users table
CREATE TABLE `users` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`email_verified_at` timestamp NULL DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`remember_token` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `users_email_unique` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Payment Model
class Payment
{
public function user()
{
return $this->belongsTo('User', 'user_id');
}
public function logs()
{
return $this->morphMany(Log::class, 'loggable');
}
}
payments table
CREATE TABLE `payments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`status` varchar(50),
`amount` int(11) NOT NULL,
`collection_date` date NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`user_id` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_payments_user_id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Log Model
class Log
{
public function loggable()
{
return $this->morphTo();
}
}
logs table
CREATE TABLE `logs` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`loggable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`loggable_id` bigint(20) unsigned NOT NULL,
`old_values` text COLLATE utf8mb4_unicode_ci,
`new_values` text COLLATE utf8mb4_unicode_ci,
`user_id` bigint(20) unsigned DEFAULT NULL, /* the user that made the change, if any */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
The Log model stores all changes made to any other model (it's a polymorphic relationship), so if the user changes its name, the Log model will store the older name and the new name. The same applies to Payment: if a payment status changes the Log model will have a new record with the old status and the new status.
I need to show a paginated list of all Log records for a specific User ordered by date. So my code is:
$user = App\User::find($id);
$allLogs = $user->logs();
// Now I need to join (I'm using union) both sets of logs
$allLogs->union($user->payments->logs());
However, since a User can have many Payment, $user->payments returns a Collection, so is no longer a query builder/eloquent object and it fails when I try to call ->logs().
$user->payments()->logs() also doesn't work, because $user->payments() returns a HasMany object and the ->logs() method doesn't exist.
I'm trying to avoid getting each collection of Log separately and then processing them using php (it would be perfect to delegate that task to MySql).
I believe it can be done, because I can write the query on MySql:
select l.*
from payments p
join logs l on p.id = l.loggable_id and l.loggable_type = 'App\\Payments'
where p.user_id = SOMEUSERID
Thanks in advance
Eager load the relations(reduces number of queries)
$user = User::with(['payments.logs', 'logs'])->find($id);
Query using the Log model.
$logs = Log::where([
'loggable_id' => $user->id,
'loggable_type' => 'User',
])
->orWhere(function($query){
$query->whereIn('loggable_id',
$user->payments()->pluck('id'))
->where('loggable_type', 'Payment');
})->get();
OR
Get them individually and then combine them.
$all_logs = collect([]);
$all_logs->push($user->logs);
foreach($user->payments as $p){
$all_logs->push($p->logs);
}
$final_logs = $all_logs->collapse();
OR
Just use the relations, without iterating over the payments. You can combine the results if you want(as shown in the previous approach).
$user_logs = $user->logs;
$payment_logs = $user->payments->pluck('logs')->collapse();
I have three models, SalesReturn, Product and ProductSalesReturn, and their relations are following:
class SalesReturn extends Model
{
public function products()
{
return $this->belongsToMany(Product::class)
->withTimestamps()
->using(ProductSalesReturn::class);
}
}
I use the ProductSalesReturn to represent the intermediate table ( https://laravel.com/docs/6.x/eloquent-relationships#defining-custom-intermediate-table-models ), and ProductSalesReturn has a relation to Unit:
class ProductSalesReturn extends Pivot
{
public function unit()
{
return $this->belongsTo(Unit::class);
}
}
When I eager loading the unit relation like following code:
SalesReturn::with(['products', 'products.unit'])->find($id);
I will get the following error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'units.product_id' in 'where clause' (SQL: select * from `units` where `units`.`product_id` in (1031, 1631, 13391, 14361, 16981, 17441, 41982, 45982, 55741) and `units`.`deleted_at` is null)
The table schemas are following:
CREATE TABLE `sales_returns` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`number` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`user_id` bigint(20) NOT NULL,
`order_id` bigint(20) NOT NULL,
`note` text COLLATE utf8mb4_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `product_sales_return` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`sales_return_id` bigint(20) NOT NULL,
`product_id` bigint(20) NOT NULL,
`unit_id` bigint(20) NOT NULL,
`price` double NOT NULL,
`amount` int(11) NOT NULL,
`gross_profit` double NOT NULL DEFAULT '0',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
How can I eager loading the relation of custom intermediate table in Laravel ?
Thank you.
You could use Laravel Eloquent: Eager Load Pivot Relations
Installation
composer require ajcastro/eager-load-pivot-relations
Configuration
use AjCastro\EagerLoadPivotRelations\EagerLoadPivotTrait;
class Product extends Model
{
// Use the trait here to override eloquent builder.
// It is used in this model because it is the relation model defined in
// SalesReturn::products() relation.
use EagerLoadPivotTrait;
}
Usage
return SalesReturn::with('products.pivot.unit')->get();
Define the table, foreignPivotKey and relatedPivotKey paramethers in your produtcts belongsToMany relationship
I need to use my Website model to get a row from my database within the websites table, however this row is identified through my domains table.
So basically it would be great to do a query on my domains table and match the row, then from that get the website row from the websites table using the website_id column.
But I want to simply pass this data into my controller by just referencing the Model within the method.
class WebsiteController extends Controller {
public function index(Website $website) {
print_r($website);
return view('index');
}
}
My domains table:
CREATE TABLE `domains` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`website_id` INT(11) NOT NULL DEFAULT '0',
`domain` VARCHAR(255) NOT NULL DEFAULT '0',
`active` INT(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
INDEX `website_id` (`website_id`),
CONSTRAINT `website_id` FOREIGN KEY (`website_id`) REFERENCES `websites` (`id`)
)
COMMENT='This table will contain all of the domains registered on MarvWeb, this will link to the website record. '
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=3;
And websites table:
CREATE TABLE `websites` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NULL DEFAULT NULL,
`tagline` VARCHAR(255) NULL DEFAULT NULL,
`description` VARCHAR(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COMMENT='This table will contain all the websites data. '
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=2;
Does this make sense?
Add a website function to your Domain model.
class Domain extends Model{
public function website(){
return $this->hasOne('App\Website');
}
// remainder of model.
}
When you retrieve the Domain query results, the website can be accessed by
print_r($domainRowResult->$website->tagline);
I have a model like this:
<?php
class Post extends Eloquent {
protected $fillable = [];
public function photos()
{
return $this->morphMany('Upload', 'imageable');
}
public function attachments()
{
return $this->morphMany('Upload', 'attachable');
}
}
and my morphMany table's schema is like this:
CREATE TABLE `uploads` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`raw_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`size` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`downloads` int(11) NOT NULL DEFAULT '0',
`imageable_id` int(11) DEFAULT NULL,
`imageable_type` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`attachable_id` int(11) DEFAULT NULL,
`attachable_type` varchar(32) COLLATE utf8_unicode_ci NOT NULL,
`user_id` int(10) unsigned NOT NULL,
`created_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
`updated_at` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
PRIMARY KEY (`id`),
KEY `uploads_user_id_index` (`user_id`),
CONSTRAINT `uploads_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=45 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
now I need to remove one row of this table, I tried $posts->photos()->delete(); but it removed all rows associated to this Post.
Could someone help me?
$posts->photos() is the relationship query to return all of the photos for a post. If you call delete() on that, it will delete all of those records. If you only want to delete a specific record, you need to make sure you only call delete on the one you want to delete. For example:
$posts->photos()->where('id', '=', 1)->delete();
to reomove from pivot table in many to many polymorphic relation just use detach:
$posts->photos($photoModel)->detach();
The relationship isn't even needed for this. Just use the Upload model directly:
Upload::find($id)->delete();
Or even shorter:
Upload::destroy($id);
I am new to this and need a little help. I have a 2 tables...
CREATE TABLE `vehicles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`vehicle_type` varchar(50) NOT NULL,
`vehicle_make` varchar(50) NOT NULL,
`vehicle_model` varchar(50) NOT NULL,
`vehicle_year` varchar(50) NOT NULL,
`vin` varchar(50) NOT NULL,
`registered_state` varchar(10) NOT NULL,
`license_plate` varchar(20) NOT NULL,
`insurrance_policy` varchar(50) NOT NULL,
PRIMARY KEY(`id`)
)
ENGINE=INNODB;
CREATE TABLE `drivers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`first_name` varchar(50) NOT NULL,
`last_name` varchar(50) NOT NULL,
`dob` date NOT NULL,
`ss_no` varchar(50) NOT NULL,
`address` varchar(100) NOT NULL,
`city` varchar(50) NOT NULL,
`state` varchar(10) NOT NULL,
`zip_code` int(5) NOT NULL,
`cell_phone` varchar(50) NOT NULL,
`home_phone` varchar(50),
`dl_no` varchar(50) NOT NULL,
`dl_state` varchar(10) NOT NULL,
`dl_exp` date NOT NULL,
`dl_2_no` varchar(50) NOT NULL,
`dl_2_state` varchar(10) NOT NULL,
`dl_2_exp` date NOT NULL,
`vehicle_id` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY(`id`),
CONSTRAINT `Ref_01` FOREIGN KEY (`vehicle_id`)
REFERENCES `vehicles`(`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
ENGINE=INNODB;
SET FOREIGN_KEY_CHECKS=1;
As you can see every driver has a car associated with him. How can I query every driver and every car that is associated with him in an object.
I can get all the drivers using this.
$d = new Driver();
$data['driver'] = $d->get();
In my model for driver has
var $has_one = array('vehicle');
I want to get all the records in $data['driver']
Here's one simple way, assuming you have one-to-one relationship:
In your Model Driver:
var $has_one = array('vehicle');
In your Model Vehicle:
var $has_one = array('driver');
To get a vehicle and its driver:
$v = new Vehicle();
$v->include_related('driver')->get();
include_related() will only work with $has_one related models.
Driver's properties are now stored in $v with the prefix driver_. To access the vehicle's driver columns:
echo $v->driver_first_name;
echo $v->driver_last_name;
Alternatively, you can auto-load the driver every time you access the vehicle:
// In Vehicle
var $auto_populate_has_one = TRUE;
For has-many relationships, you can do this:
$d = new Driver();
$d->get_by_id($id);// Get a driver by id
foreach ($d->vehicle->get() as $car)
{
// Print all this Driver's vehicles
echo $car->vehicle_model;
}
This is just one way to do it. There are so may ways to access relationships in Datamapper, your best bet is to read the documentation thoroughly, then read it again. Datamapper is a great ORM and you should learn and experiment with all it's features to get the most out of it.
This could be a good opportunity to use a Foreign Key in your Drivers Table. Here is a link to the MySQL documentation on using Foreign Keys in your table: MySQL Foreign Keys
Alternatively if you don't want to deal with the hassles of advanced MySQL calls you could also just process two queries, one to your vehicles table, and one to your drivers table, and then use PHP to iterate (loop) through the results until you can find and match the correct values with each other.
Actually both ways are probably a bit of a hassle to set up, but your foreign key would likely be the easier to maintain.