Skip to content

Instantly share code, notes, and snippets.

@keulu
Last active April 8, 2022 15:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save keulu/b54791e968231ed438d4cc3dec893aa0 to your computer and use it in GitHub Desktop.
Save keulu/b54791e968231ed438d4cc3dec893aa0 to your computer and use it in GitHub Desktop.
Provide an i18n Model for Codeigniter4
<?php namespace Libraries\Core;
/**
* How to use :
*
* setup your configuration :
*
* in Config/App search for $defaultLocale and configure your $supportedLocales
*
*
* setup your model :
*
* <?php namespace App\Models;
*
* use Libraries\Core\MY_I18n_Model;
*
* class JobModel extends MY_I18n_Model
* {
* protected $table = 'jobs';
* protected $primaryKey = 'id';
*
* protected $i18nTable = 'jobs_i18n';
* // protected $autofillEmptyFields = true;
* protected $i18nKeys = [
* 'title'
* ];
*
* [...]
* }
*
* Setup your database
*
* CREATE TABLE IF NOT EXISTS `jobs` (
* `id` int(11) NOT NULL AUTO_INCREMENT,
* `title` varchar(50) NOT NULL,
* `created_at` datetime NOT NULL,
* `updated_at` datetime DEFAULT NULL,
* `deleted` tinyint(1) NOT NULL DEFAULT '0',
* `deleted_at` datetime DEFAULT NULL,
* PRIMARY KEY (`id`)
* ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*
* CREATE TABLE IF NOT EXISTS `jobs_i18n` (
* `id` int(11) NOT NULL AUTO_INCREMENT,
* `jobs_id` int(11) NOT NULL,
* `locale` varchar(7) NOT NULL,
* `key` varchar(127) NOT NULL,
* `value` varchar(127) NOT NULL,
* PRIMARY KEY (`id`),
* UNIQUE KEY `unique fileds` (`locale`,`key`,`jobs_id`)
* ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
*
*
*
* the idea is that your key title will automaticaly replaced by the good locale.
*
* afterFind : your key title will be replaced by the key title in the _i18n table
* afterUpdate, your key title will be updated and your _i18n reference too
* afterInsert, your key will be created in all $supportedLocales.
* if $autofillEmptyFields set to true,all key will take the new value.
* if set to false, only $currentLocale will be inserted, the other stay empty
* afterDelete. if $useSoftDelete set to true, nothing happens, if set to false, all jobs_id will be removed
*
* You can use multiple keys per jobs_id. title, description, short_description, [...] as many as you want.
*
* thats it !! :)
*/
use CodeIgniter\Database\BaseBuilder;
use Config\App;
use CodeIgniter\Model;
class MY_I18n_Model extends Model
{
/**
* locale detection Accept-Language Header - fallback on \Config\App::$defaultLanguage
*
* @var string
*/
private $currentLocale;
/**
* represent the i18n table_name
*
* @var string
*/
protected $i18nTable;
/**
* new instance of the query builder
*
* @var BaseBuilder
*/
protected $i18nBuilder;
/**
* list of keys to translate
*
* @var array
*/
protected $i18nKeys = [];
/**
* Autofill empty value on insert ?
*
* @var boolean
*/
protected $autofillEmptyFields = false;
public function __construct()
{
parent::__construct();
$this->searchI18nTable();
}
/**
* Looking for an existing table
*/
protected function searchI18nTable()
{
if ($this->db->tableExists($this->i18nTable)) {
$this->currentLocale = service('request')->getLocale();
$this->i18nBuilder = new BaseBuilder($this->i18nTable, $this->db);
$this->afterFind[] = 'i18nAfterFind';
$this->afterInsert[] = 'i18nAfterInsert';
$this->afterUpdate[] = 'i18nAfterUpdate';
$this->afterDelete[] = 'i18nAfterDelete';
}
}
/**
* after Selecting data if single record or multiples.
*
* @param array $vars The resulting row of data
*
* @return array The resulting row of data.
*/
protected function i18nAfterFind($vars)
{
$data_tmp = null;
if (!is_null($vars['data'])) {
if (isset($vars['id'])) {
$data_tmp = $this->joinI18n($vars['data']);
}
else
{
$data_tmp = [];
foreach ($vars['data'] as $data)
{
$data_tmp[] = $this->joinI18n($data);
}
}
}
$vars['data'] = $data_tmp;
return $vars;
}
/**
* after Selecting, data is parsed.
*
* @param array $data The resulting row of data
*
* @return array The resulting row of data.
*/
protected function joinI18n($data)
{
$i18n_query = $this->i18nBuilder
->where('locale', $this->currentLocale)
->where($this->table . '_id', $data['id'])
->get();
foreach ($i18n_query->getResult() as $row)
{
$data[$row->key] = $row->value;
}
return $data;
}
/**
* after Delete
*
* @param array $vars The resulting row of data
*
* @return array The resulting row of data.
*/
protected function i18nAfterDelete($vars)
{
if ($vars['purge'] === true) {
$this->i18nBuilder
->where($this->table . '_id', $vars['id'])
->delete();
}
return $vars;
}
/**
* after Update
*
* @param array $vars The resulting row of data
*
* @return array The resulting row of data.
*/
protected function i18nAfterUpdate($vars)
{
// foreach keys
foreach($vars['data'] as $k => $v)
{
// If my currentKey is in my $i18nKeys
if (in_array($k, $this->i18nKeys)) {
$this->i18nBuilder
->where('locale', $this->currentLocale)
->where($this->table . '_id', $vars['id'])
->where('key', $k)
->update(['value' => $v]);
}
}
return $vars;
}
/**
* after Insert
*
* @param array $vars The resulting row of data
*
* @return array The resulting row of data.
*/
protected function i18nAfterInsert($vars)
{
$app_config = new \Config\App();
$relatedId = $this->db->insertID();
// For all keys
foreach($vars['data'] as $k => $v)
{
// If my currentKey is in my $i18nKeys
if (in_array($k, $this->i18nKeys))
{
// And for each locales
foreach($app_config->supportedLocales as $locale)
{
// if autoFill == true
// Set very i18n keys with the same data
if ($this->autofillEmptyFields)
{
$data = [
$this->table . '_id' => $relatedId,
'locale' => $locale,
'key' => $k,
'value' => $v,
];
}
// if autofill = false
// fill current key and let the others empty
else
{
$data = [
$this->table . '_id' => $relatedId,
'locale' => $locale,
'key' => $k,
];
// select good locale
if ($locale == $this->currentLocale)
{
$data['value'] = $v;
}
// other fields are empty
else
{
$data['value'] = '';
}
}
// insert
$this->i18nBuilder->insert($data);
}
}
}
return $vars;
}
}
@chistel
Copy link

chistel commented Mar 8, 2019

this is nice, but i think it would be better been a trait used in other model class

@keulu
Copy link
Author

keulu commented Mar 13, 2019

this is nice, but i think it would be better been a trait used in other model class

Yes of course a trait is possible, like the responseTrait witch help to handle REST HTTP Response.

Don't know what is better. Why using a Trait is better than extend a class ?

possible to use multiple Trait instead of 1 extend ?

@MGatner
Copy link

MGatner commented May 21, 2019

Yes, multiple traits are fine:
https://www.php.net/manual/en/language.oop5.traits.php#language.oop5.traits.multiple

In general I decide on trait versus extension based on a) whether the functions are likely to be "replaced" and b) whether the developer using the functions will likely be extending other classes and interfaces, which can make mixing confusing or buggy.
From best I can tell in your case I'd agree with @chistel that this seems like a good use of a trait.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment