📜 ⬆️ ⬇️

The idea of ​​Doctrine I18n in Magento

It all started with the fact that I took on the revision of another freelancing project. The task was to complete the implementation of the multilingual Gallery module. The problem arose, as always, suddenly ...

Progress engine


The gallery consists of albums and items. Both the album and the item have the following main fields:

It turns out that the title and description should depend on StoreView, that is, store_id in the database. At first there was an option to add just another store_id field, so I did it at the beginning, but then I saw that this decision was just ridiculous! Let me explain why:

It seems and not bad, but not at all focused on the end user.

Freak idea


Since I often use Symfony1.4 + Doctrine1.2 lately, I didn’t have to look for a solution for a long time. I decided to implement a Magento functionality similar to the Doctrine I18n behavior .

What for?


And because it is convenient and easy! Plus, I could not find the standard functionality that would implement it. Although there was an idea to do everything through EAV (to create entity_type, attributes, and all that is needed there), as for me, it is difficult and confusing.
The beauty of this decision is that the collection and the model, as they were and remained on the external api, but now you do not need to think about saving and sharing data for several StoreView.
')

Cheap and angry


The implementation itself consists in creating another layer of abstraction for the Source models (the collection and the model itself). And then we simply inherit from them, if we need to implement data storage and work with several StoreViews.

Here are the classes themselves:
- app / code / local / Sj / Gallery / Model / Mysql4 / Translation.php
abstract class Sj_Gallery_Model_Mysql4_Translation extends Mage_Core_Model_Mysql4_Abstract
{
const TABLE_SUFIX = '_translation' ;
protected
$_translatableFields = array();

/**
* Standard resource model initialization
*
* @param string $mainTable
* @param string $idFieldName
* @return Mage_Core_Model_Mysql4_Abstract
*/
protected function _init($mainTable, $idFieldName)
{
if (empty($ this ->_translatableFields)) {
throw new Exception( 'You must specify translatable fields' );
}
$ this ->_setMainTable($mainTable, $idFieldName);
}

/**
* Retrieve select object for load object data
*
* @param string $field
* @param mixed $value
* @return Zend_Db_Select
*/
protected function _getLoadSelect($field, $value, $ object )
{
$tableName = $ this ->getMainTable();
$select = parent::_getLoadSelect($field, $value, $ object );

$select->joinLeft(
array( 'trnslt' => $ this ->getTranslationTableName()),
'trnslt.id = ' . $tableName . '.' . $field . '
AND trnslt.store_id = '
. ( int )$ object ->getStoreId(),
$ this ->getTranslatableColumns()
);

return $select;
}

/**
* Set multilang field names
*
* @param array $fields
* @return Sj_Gallery_Model_Mysql4_Translation
*/
public function setTranslatableFields($fields)
{
if (!is_array($fields)) {
return false ;
}

$ this ->_translatableFields = $fields;
return $ this ;
}

/**
* Get multilang field names
*
* @return array
*/
public function getTranslatableFields()
{
return $ this ->_translatableFields;
}

/**
* Get multilang columns
*
* @return array
*/
public function getTranslatableColumns()
{
$columns = $ this ->getTranslatableFields();
$columns[ 'translation_id' ] = 'trnslt.id' ;
$columns[ 'store_id' ] = 'trnslt.store_id' ;
return $columns;
}

/**
* Get translation table name
*
* @return string
*/
public function getTranslationTableName()
{
return $ this ->getMainTable() . self::TABLE_SUFIX;
}

/**
* Save object object data
*
* @param Mage_Core_Model_Abstract $object
* @return Mage_Core_Model_Mysql4_Abstract
*/
public function save(Mage_Core_Model_Abstract $ object )
{
$adapter = $ this ->_getWriteAdapter();
$adapter->beginTransaction();
try {
$data = $ object ->getData();
$translations = array();
foreach ($ this ->_translatableFields as $field) {
if (isset($data[$field])) {
$translations[$field] = $data[$field];
unset($data[$field]);
}
}
$onDuplicate = array_keys($translations);
$translations[ 'id' ] = $ object ->getId();
$translations[ 'store_id' ] = $ object ->getStoreId();

$adapter->insertOnDuplicate(
$ this ->getTranslationTableName(),
$translations,
array_combine($onDuplicate, $onDuplicate)
);
parent::save($ object );

$adapter->commit();
return $ this ;
} catch (Exception $e) {
$adapter->rollBack();
throw $e;
}
}
}


* This source code was highlighted with Source Code Highlighter .


- app / code / local / Sj / Gallery / Model / Mysql4 / Translation / Collection.php
abstract class Sj_Gallery_Model_Mysql4_Translation_Collection extends Mage_Core_Model_Mysql4_Collection_Abstract
{
protected function _initSelect()
{
$tableName = $ this ->getResource()->getMainTable();

$ this ->getSelect()
->from(array( 'main_table' => $tableName))
->joinLeft(array( 'trnslt' => $ this ->getResource()->getTranslationTableName()),
'trnslt.id = main_table.' . $ this ->getResource()->getIdFieldName(),
$ this ->getResource()->getTranslatableColumns()
);
return $ this ;
}

public function addStoreToFilter(Mage_Core_Model_Store $store)
{
$ this ->addFieldToFilter( 'trnslt.store_id' , $store->getId());
return $ this ;
}
}

* This source code was highlighted with Source Code Highlighter .


Practice


Everything is crazy. Previously, when creating the Source model, it was necessary to inherit from Mage_Core_Model_Mysql4_Abstract, now it is from Sj_Gallery_Model_Mysql4_Translation.
And you need to create a table for the translation itself in the install file of your module. One more table with another suffix "_translation" (this value is a class constant and can be changed).
The only and very important point is that you always need to set store_id in the model before calling the load method!

Example of using the collection:
$collection = Mage::getModel( 'gallery/group' )->getCollection()
->addStoreToFilter(Mage::app()->getStore())
->addFieldToFilter( 'status' , 1)
->getItems();


* This source code was highlighted with Source Code Highlighter .


Example of using the model:
$store = Mage::app()->getStore($request->getParam( 'store' ));
$group = Mage::getModel( 'gallery/group' )
->setStoreId($store->getId())
->load($id);


* This source code was highlighted with Source Code Highlighter .


The source code for the install of the Gallery module file:
$installer = $ this ;
$installer->startSetup();

$installer->run( "
CREATE TABLE {$this->getTable('gallery/gallery')} (
`gallery_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`filename` varchar(255) NOT NULL DEFAULT '',
`status` smallint(6) NOT NULL DEFAULT '0',
`created_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`gallery_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
"
);

$installer->run( "
CREATE TABLE IF NOT EXISTS `{$this->getTable('gallery/group')}` (
`collection_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`file` varchar(255) NOT NULL DEFAULT '',
`status` tinyint(4) NOT NULL,
`created_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`collection_id`),
KEY `gallery_group_idx` ( `collection_id` )
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
"
);

$installer->run( "
CREATE TABLE `{$this->getTable('gallery/items_translation')}` (
`id` int(10) unsigned NOT NULL,
`title` varchar(255) NOT NULL DEFAULT '',
`description` varchar(20000) NOT NULL DEFAULT '',
`store_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`, `store_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
ALTER TABLE `{$this->getTable('gallery/items_translation')}`
ADD FOREIGN KEY (`id`) REFERENCES `{$this->getTable('gallery/gallery')}` (`gallery_id`)
ON DELETE CASCADE;

CREATE TABLE `{$this->getTable('gallery/group_translation')}` (
`id` int(10) unsigned NOT NULL,
`title` varchar(255) NOT NULL DEFAULT '',
`description` varchar(20000) NOT NULL DEFAULT '',
`store_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`, `store_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;
ALTER TABLE `{$this->getTable('gallery/group_translation')}`
ADD FOREIGN KEY (`id`) REFERENCES `{$this->getTable('gallery/group')}` (`collection_id`)
ON DELETE CASCADE;
"
);

$installer->endSetup();


* This source code was highlighted with Source Code Highlighter .


Sources can be downloaded here .

PS: I did not set myself the goal to implement full-fledged i18n functionality. I just solved the problem and I liked the solution, because it is portable and easy to understand. This is enough to work transparently with the multi store view, but you can do even better. For example, to organize everything in the form of a module , create a setup model, which itself will create additional tables, put the names of fields that depend on the store into the configuration.

In this article, the code was highlighted using Source Code Highlighter .

Source: https://habr.com/ru/post/115428/


All Articles