laravel / framework

The Laravel Framework.
https://laravel.com
MIT License
32.5k stars 11.01k forks source link

Eloquent model is not loading correct data for "one-to-many" relationship when foreign and local keys are string #24803

Closed mvladimir7 closed 6 years ago

mvladimir7 commented 6 years ago

Description:

I have 2 tables:

countries

TABLE `countries` (
  `iso` varchar(2) COLLATE utf8mb4_unicode_ci NOT NULL,
  `default_timezone` varchar(191) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'UTC',
  `lat` decimal(11,8) DEFAULT NULL,
  `lon` decimal(11,8) DEFAULT NULL,
  `disabled` tinyint(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`iso`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

country_currencies

CREATE TABLE `country_currencies` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `country_iso` varchar(2) COLLATE utf8mb4_unicode_ci NOT NULL,
  `currency_code` varchar(3) COLLATE utf8mb4_unicode_ci NOT NULL,
  `default` tinyint(1) NOT NULL DEFAULT '0',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `country_currencies_country_iso_currency_code_unique` (`country_iso`,`currency_code`),
  KEY `country_currencies_currency_code_foreign` (`currency_code`),
  CONSTRAINT `country_currencies_country_iso_foreign` FOREIGN KEY (`country_iso`) REFERENCES `countries` (`iso`) ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `country_currencies_currency_code_foreign` FOREIGN KEY (`currency_code`) REFERENCES `currencies` (`code`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=265 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Models are mapping to these tables:

Country

class Country extends Model
{
    protected $table = 'countries';

    protected $primaryKey = 'iso';

    protected $fillable = ['iso', 'default_timezone', 'lat', 'lon', 'disabled'];

    public function currencies()
    {
        return $this->hasMany(CountryCurrency::class, 'country_iso', 'iso');
    }
}

CountryCurrency

class CountryCurrency extends Model
{
    protected $table = 'country_currencies';

    protected $fillable = ['country_iso', 'currency_code', 'default'];

    public function country()
    {
        return $this->belongsTo(Country::class, 'country_iso', 'iso');
    }
}

My foreign keys and local keys are string.

Steps To Reproduce:

Load data from related country_currencies table for specific country:

Country::find('US')->currencies;

it generates such sql query:

select * from `country_currencies` where `country_currencies`.`country_iso` = 0 and `country_currencies`.`country_iso` is not null

in query above it generates country_currencies.country_iso = 0 but has to generates this country_currencies.country_iso = 'US'

the correct SQL query has to be this one:

select * from `country_currencies` where `country_currencies`.`country_iso` = 'US' and `country_currencies`.`country_iso` is not null

I didn't dive deep into the laravel code but it seems there is some bug related to relationship.

mvladimir7 commented 6 years ago

Solution

Add cast attribute to model, if there is a need to use string as primary key:

protected $casts = [
        'iso' => 'string',
    ];