Seems like this should be the easy part, but I can't seem to get this going. My created and updated fields are not being populated. I've tried renaming the fields to something different, but no go. Other than the schema and setting up the class (very basic) as below, am I missing something?
Codeigniter v2.1.2
Datamapper ORM v1.8.2.1
Schema:
CREATE TABLE `users` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1
User Class:
<?php
class User extends DataMapper
{
function User()
{
parent::DataMapper();
}
}
Ok, found the answer and hopefully this will help someone out. I needed to set the following in the application/config/datamapper.php file:
$config['local_time'] = TRUE;
$config['unix_timestamp'] = FALSE;
$config['timestamp_format'] = 'Y-m-d H:i:s';
In your config/datamapper.php file ensure 'created_field' and 'updated_field' are set to match your DB columns. Or you can override them in the model itself. ie.
<?php
class User extends DataMapper
{
var $created_field = 'created';
var $updated_field = 'updated';
function User()
{
parent::DataMapper();
}
}
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();
My table looks like below:
CREATE TABLE IF NOT EXISTS `ProductCategoryImage` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`productCategoryId` INT(11) NOT NULL,
`imageName` VARCHAR(255) NOT NULL,
`thumbnailName` VARCHAR(255) NULL DEFAULT NULL,
`location` TINYINT(2) NOT NULL DEFAULT 1,
`status` TINYINT(2) NOT NULL DEFAULT 1,
`createdAt` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` TIMESTAMP NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
`deletedAt` DATETIME NULL DEFAULT NULL,
`createdById` INT(11) NOT NULL DEFAULT -1,
`updatedById` INT(11) NULL DEFAULT NULL,
`deletedById` INT(11) NULL DEFAULT NULL
);
Inside the ProductCategoryImage model I have written down below two methods:
public function getThumbnailNameAttribute($value)
{
return self::getThumbnailUrl($value);
}
public function setThumbnailNameAttribute($value)
{
$this->attributes['thumbnailName'] = $value;
}
Laravel won't execute above two methods to customize my table field value.
ProductCategoryImage model extends from custom BaseModel and BaseModel extends from Eloquent.
Doesn't Laravel has event handler methods like beforeFind(), afterFind(), beforeSave(), afterSave()?
One of my team member achieved it using the toArray() method:
public function toArray()
{
$toArray = parent::toArray();
$toArray['thumbnailName'] = $this->thumbnailName;
return $toArray;
}
public function getThumbnailNameAttribute($value)
{
return self::getThumbnailUrl($value);
}
Worked like charm.
Accessors/mutators are only called when you access a property on a model. These attributes would be used like this:
$name = $image->thumbnail_name; // getThumbnailNameAttribute()
$image->thumbnail_name = 'foo'; // setThumbnailNameAttribute()
The magic property name will be the snake_case version of your StudlyCase attribute name.
Since your accessor's property name isn't thumbnail_name, Laravel won't find the original value automatically. You can get it from the model's attributes array directly:
public function getThumbnailNameAttribute($value)
{
return self::getThumbnailUrl($this->attributes['thumbnailName']);
}
Note that you still need to call save() for changes made by a mutator to appear in your database.
If you want certain things to happen automatically whenever a model is saving, creating, restored, etc. then you can listen for the appropriate lifecycle event: https://laravel.com/docs/5.7/eloquent#events
Is it possible that i can pass some variable to the model in table name?
I have a module that creates Customers.
Now for each Customers we create a separate table.
The Customers has a login and logout credentials.
Now for whenever a customer is created i can't always create a new model and pass it name there.
So i want that it gets dynamically, I am not sure how i can do it.
For each customer there will be one separate table.
Now i want to create a model for login, but what should i pass in the table name,it should be dynamic right?
What should be the mysql schema to get this done?
This my sample Model code:
class Customer_User_M extends MY_Model
{
protected $_table_name = 'customer1';
protected $_order_by = 'name';
public function __construct()
{
parent::__construct()
}
}
The Customer Table:
CREATE TABLE IF NOT EXISTS `customer1` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`site_key` varchar(50) NOT NULL,
`display_name` varchar(100) NOT NULL,
`ext` varchar(15) NOT NULL,
`auth_user` varchar(100) NOT NULL,
`password` varchar(128) NOT NULL,
`base_ini_id` varchar(50) NOT NULL,
`comments` varchar(200) NOT NULL,
`custom_ini_filename` varchar(50) NOT NULL,
`email` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
You can simply make a function in the model and put the code of create table in the function and pass dynamic table name in the function from controller like below....
Model function code:
function access_customer_table($table_name)
{
//put ur select query here with table name in FROM clause will be $table_name
}
and call this model function in your controller after load the model... and put your table name as argument...
like :
$this->load->model('Customer_User_M');
$this->Customer_User_M->access_customer_table('new_customer_table_name');
Please correct syntax if needed.. let me know if anything wrong in the logic...
I tried to set the table name after loading the model. Just worked for me.
$this->load->model('customer_model','customer');
then,
$customer->table_name = "customer_xxx";
It is better to save common customer details in a customer table and create another tables to store different data of customers with different fields having foreign key customerid.
Here, if you want to pass dynamic tablename to model then you need to predefine all table names as an associative array with key-value pair.Then you can pass desired key-value to model.It can be implemented using a library.So that you can use it all your controllers.
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 ?
When programming OO in PHP i never know exactly how to map a class to simple lists of data. I will try to make a simple example wich i am running into every day:
First the MySQL table creates:
/* create product table */
CREATE TABLE `product` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
`description` text COLLATE utf8_unicode_ci,
`price` decimal(10,0) NOT NULL,
`brand` int(11) NOT NULL,
`deliverytime` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
/* data list for all kind of brands */
CREATE TABLE `brand` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`brand` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
/* brand data*/
insert into `brand`(`id`,`brand`) values (1,'nike'),(2,'adidas'),(3,'diesel'), (4,'dkny'),(5,'lacoste');
/* data list for deliverytime */
CREATE TABLE `deliverytime` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`deliverytime` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
/* deliverytime data */
insert into `deliverytime`(`id`,`deliverytime`) values (1,'1 day'),(2,'2 days'),(3,'3 days'),(4,'4 days'),(5,'5 days'),(6,'6 days'),(7,'1 week'),(8,'1 - 2 weeks'),(9,'2 - 3 weeks'),(10,'4 - 5 weeks');
Then i create the product class.
class Product{
private
$name,
$description,
$price,
$brand
$deliverytime;
public function __construct(){
// etc etc
}
public function save(){
// save the product
}
}
Now the big question(s) are:
How should i handle $brand and $deliverytime in my Product class?
Should i make a Brand and DeliveryTime object (wich in turn are responsible for fetching the right brand and or deliverytime data)?
And what about saving the Product object?
How should i handle the brand and deliverytime data?
What is the beste practice or pattern to handle this kind of situations?
Sorry for this noobish question but i wasnt sure where to look for (tags to search for) :/
EDIT:
Ok lets say i dont want to use somekind of ORM framework (Doctrine, dORM, redbean etc) since it would be a gigantic overkill for my little system + i am realy want to know how to create the mapping myself for learning purposes... any suggestions?
This is a style I like to use
class ProductModel {
public function find($id) {
// fetch product with your database code using parameters to filter
$product = new ProductEntity(
// initialize with non foreighn values (eg. $row->id, $row->name)
new ProductBrandEntity($row->brand_id, $row->brand_name); // available because you joined brand??
new ProductDeliveryTime(/* initialize */)
);
return $product;
}
}
I like to call the object from the database Entities but you can call them whatever you want.. It's basically what you suggested in your question but i prefer to have a Model (from MVC) to initialize the entities and not the entities initializing themselves. You should do some research on ORMs and ActiveRecord because this work has basically already been done for you!