update a field-collection from a computed field - drupal-7

I'm able to retrieve the fields of the field collection attached to my content type, using codes like :
foreach ($entity->field_collection[LANGUAGE_NONE] as $line) {..}
or from an entity wrapper.
But I'm definitely unable to update the collection fields with values computed in the computed field, like I usually do with other CCK fields, like :
$entity->field_regular[LANGUAGE_NONE][0]['value'] = $value ;
then it is saved normally, as if I edited field_regular 'by hand'.
with collection this won't work (does nothing visible) :
$entity->field_collection[LANGUAGE_NONE][$key]['field_coll_field0'][LANGUAGE_NONE][0]['value'] = $value ;
// entity wrapper way
$coll = entity_load('field_collection_item', array($line['entity']->item_id));
$wcoll = entity_metadata_wrapper('field_collection_item', $coll[$key);
$wcoll->field_coll_field0->set($value) ;
any save() methods gives me a blank page (infinite cgi loop) :
entity_save('field_collection_item',$coll);
wcoll->save();
what should I know to programmatically save collection fields ?
thanks, Jerome

Please check Entity metadata wrappers doc page with simple example how to update a field collection, for example:
<?php
// Populate the fields.
$ewrapper = entity_metadata_wrapper('node', $node);
$ewrapper->field_lead_contact_name->set($contact_name);
$ewrapper->field_lead_contact_phone->set($contact_phone);
$ewrapper->field_lead_contact_email->set($contact_email);
// Create the collection entity and set it's "host".
$collection = entity_create('field_collection_item', array('field_name' => 'field_facilities_requested'));
$collection->setHostEntity('node', $node);
// Now define the collection parameters.
$cwrapper = entity_metadata_wrapper('field_collection_item', $collection);
$cwrapper->field_facility->set(intval($offset));
$cwrapper->save();
// Save.
$ewrapper->save();
?>
So probably you need to do as below:
try {
// Entity wrapper way.
$coll = entity_load('field_collection_item', array($line['entity']->item_id));
$wcoll = entity_metadata_wrapper('field_collection_item', $coll);
$wcoll->field_coll_field0 = $value;
$wcoll->save();
} catch (Exception $e) {
drupal_set_message(t('Error message: #error.',
array('#error' => $e->getMessage())), 'error');
watchdog_exception('MYMODULE', $e);
}
Adding try/catch should prevent you from having a blank page. However if you've still an issue, check watchdog logs or any errors in PHP log file.

Related

CakePHP 3: get a list of all table objects

I'm looking for a way to get a list of all available table objects. These are all the classes that are (by default) located under App/Modal/Table and that are handled by TableRegistry. How to get a list of all those objects?
I know it's possible to fetch all tables of the db:
$tables = ConnectionManager::get('default')->schemaCollection()->listTables();
And then using TableRegistry::get() to get the table object.
But this is not possible for my solution, because there are two cases where this does not work:
custom table names that are different to the table object name
plugin table objects
Any ideas?
Edit: Why? I need all table objects that use a behavior X. In my case a custom SearchableBehavior, which updates a searchindex table on each afterSave event for the saved entity. To update the searchindex for all entities of all tables, I need to know which tables are using the SearchableBehavior and call their update method manually.
$tables = glob(APP."Model".DS."Table".DS."*Table.php");
$tablesNames = [];
foreach ($tables as $name){
$item = explode('Table.php', basename($name));
$tablesNames[] = $item[0];
}
pr(tablesNames);
Write an event listener that listens on Model.initialize and then do a check on the subject, which is the table object if the table has your behavior. Then do something with that list.
If this doesn't work for you - you give zero background info - iterate over the apps Model/Table folder and plugin folder and the vendor folders and search for Model folders and check for *Table.php files. Then try to instantiate the table objects based on the path / namespace and filename and check the models. But this is not very fast, you should cache the resulting list.
I recently had a similar use case, where I needed to access all Table Objects, to initialize the data in the database once, in a console command.
I did it by first building an array of all the paths where the Table Object Classes could reside, then iterating over all files and using the ones ending in "Table.php". Note that this approach might need to be modified slightly depending on your use case.
<?php
use Cake\Core\Plugin;
use Cake\ORM\TableRegistry;
use Cake\Filesystem\Folder;
// Building an array of all possible paths. Firstly the src directory:
$tableClassPaths = [
APP . 'Model' . DS . 'Table' . DS,
];
// Secondly, all loaded plugins:
foreach(Plugin::loaded() as $plugin) {
$dir = Plugin::classPath($plugin) . 'Model' . DS . 'Table' . DS;
if(is_dir($dir)) {
$tableClassPaths[$plugin] = $dir;
}
}
// Iterating over each file in each folder.
$tableObjects = [];
foreach($tableClassPaths as $plugin => $dir) {
foreach(new \FilesystemIterator($dir) as $file) {
// All Files ending in Table.php might be relevant
if($file instanceof \SplFileInfo && $file->isFile()
&& mb_substr($file->getFilename(), -9) === 'Table.php'
) {
$className = mb_substr($file->getFilename(), 0, -9);
if(is_string($plugin)) {
$className = $plugin . '.' . $className;
}
$tableObject = TableRegistry::getTableLocator()->get($className);
// Here you can use / filter the Tables, for example by checking for the presence of a behavior "Indexable":
if($tableObject->hasBehavior('Indexable')) {
$tableObjects[] = $tableObject;
}
}
}
}
?>
Keep in mind, that this is only really suitable for very narrow circumstances, since this completely sidesteps the regular MVC patterns of CakePHP.

Cakephp Update multiple records or create new one if id doesn't exist in database

hello I have an edit form which displays the input fields.I am displaying the data from database in those fields so user can edit them. And also there is a button which ask to add new field. I am able to edit multiple fields but the problem is how can I create a new field in database if there is no existing id of the record.
Note: For the new field I am sending 0 id so that I can check in cakephp that it has a new field.
foreach ($exp as $k => $v) {
$dat[$k]["prp_id"] = $prpid;
$dat[$k]["exp_company"] = $v['company'];
$dat[$k]["position"] = $v['position'];
$dat[$k]["exp_id"] = $v['exp_id'];
}
$this->Experience->saveAll($dat, array('conditions' => array('exp_id' => $v['exp_id'])));
This is updating all the fields. Btw I am also not sure why its working correctly as I am here $v['exp_id'] sending only one value because its out of the loop but it is working perfectly meaning I can be able to multiple fields which I am not sure how. So In the end I have two problems. One is to create a new record if exp_id is 0 and second is why my this code works good for updating records.
Try this:
foreach ($exp as $k => $v) {
$dat[$k]["prp_id"] = $prpid;
$dat[$k]["exp_company"] = $v['company'];
$dat[$k]["position"] = $v['position'];
if ($v['exp_id'] != 0) {
$dat[$k]["exp_id"] = $v['exp_id'];
}
}
$this->Experience->saveAll($dat);

Add a new translatable field to an existing translatable table in CakePHP 2.2

I'm using CakePHP's translatable behavior. I have a few existing fields working fine, but I'm having trouble adding a new translatable field to my model.
CakePHP uses an INNER JOIN to fetch all translatable fields from the database.
Now, if I add an extra translatable field to my model, all the translation records for that field won't exist in the database. And because of the inner join, whenever it tries to fetch ANY existing records from the database, it will return blank - because the INNER JOIN on the new field fails, and so the entire query returns nothing.
Surely people must have come accross this situation before. Is there an easy solution?
One solution would be to edit/override the core and make all the INNER JOIN's into LEFT OUTER JOIN's. Is there anything wrong with that?
Another solution would be to run an update on the translations table to create all the extra records for the new field, every time you add a new translatable field - but I hate that solution.
Is there a better solution? How have others dealt with this problem?
Thanks in advance.
OK, here's a way of making sure the records exist after each time you add a new translatable field. If you've got a better answer, add it, and I'll mark yours as correct.
PS - this is tested for my purposes. I'm using multiple translation tables (http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#multiple-translation-tables). I think it should work for most situations, but if not, it should at least be a good starting point.
In your model (the model that actsAs Translatable), add the following method. What it does is takes an array of locales, and then for every record in the table, and for every translatable field, and for every locale (ie, 3 loops), it checks that a translation record exists. If a translation doesn't exist, it adds a blank one, so at least the INNER JOIN won't fail.
It returns an array of all the records it added, so you can then go through and check them or change their content or whatever.
Here's the model method:
function ensureTranslationIntegrity($localesToCheck){
$allRows = $this->find('all', array('fields' => array('id')));
$fieldsToCheck = array();
$translatableFields = $this->actsAs['Translate'];
foreach($translatableFields as $key => $value){
// actsAs Translatabe can take field names only, or Key => Value pairs - see http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#retrieve-all-translation-records-for-a-field
if(is_numeric($key)){
$field = $value;
} else {
$field = $key;
}
array_push($fieldsToCheck, $field);
}
$translateModel = $this->translateModel();
$addedRows = array(); // This will contain all the rows we have to add
foreach ($allRows as $row){
foreach($fieldsToCheck as $field){
foreach($localesToCheck as $locale){
$conditions = array(
'model' => $this->name,
'foreign_key' => $row[$this->name]['id'],
'field' => $field,
'locale' => $locale
);
$translation = $translateModel->find('first',array('conditions' => $conditions));
if(!$translation){
$data = $conditions; // The data we want to insert will mostly just match the conditions of the failed find
$data['content'] = ''; // add it as empty
$translateModel->create();
$translateModel->save($data);
array_push($addedRows, $data);
}
} // END foreach($localesToCheck as $locale){
} // END foreach($fieldsToCheck as $field){
} // END foreach ($allRows as $row){
return $addedRows;
}
And in your controller, you'd call it something like this:
public function ensure_translation_integrity(){
$locales = array('en_au','en_gb','en_nz','pt_br','xh_za');
$addedRows = $this->YourModel->ensureTranslationIntegrity($locales);
debug($addedRows);
}
Hope that helps someone, but like I said, I'd love to see a better solution if someone has one.

how to display the data returned by get_by_user_id() in DataMapper Code Igniter?

I am new to code igniter data mapper. I have a table called user, and I am trying to retrieve data from the database table and show them to the user.
Here is what I have in the model:
$u=new User();
$results=$u->get_by_user_id($id);
//$results here will be set to huge bunch of none sense data( which also includes the row that I am looking for as well)
if ($u->exists())
{
foreach ($results->all as $row){
$data['user']['first_name']=($row->user_first); //this where I am stuck ..
$data['user']['last_name']=($row->user_last);//this is also where I am stuck..
}
I don't know how to treat results to get a required fields I am looking for and store them in the $data I am passing to the user to view.
Thanks!
When you call get_by_x() on the model, the fields will be populated with data and you can access them like this:
$u = new User();
$u->get_by_user_id($id);
if($u->exists())
{
// you can access the table columns as object fields
$data['user']['first'] = $u->first;
$data['user']['last'] = $u->last;
}
else
{
$data['error'] = 'No such user!';
}
Have a look at the documentation which is really helpful: see Get and Get By.
Also, DataMapper expects all tables to have an id column: see Table Naming Rules. If your column is named id you should then call $u->get_by_id($id) instead of $u->get_by_user_id($id).

Using existing field name as different name

i have existing website.
and i write the new back-end (in cakephp) without changing front-end programm
the discomfort that
db table has field names as
id
news_date
news_title
news_content
is it possiable to do something in cakephp model file (reindentify the field names)
so i can use model in controller as
News.date
News.title
News.content
What you need to do is setup some very basic virtual fields in your news model. Something like this should suit your needs.
public $virtualFields = array(
'title' => 'news_title',
'date' => 'news_date',
'content' => 'news_content'
);
Also do yourself a favour by checking out the other model attributes that could help you out, you'll want to set displayType as new_title I'd imagine.
Is said by Dunhamzz, virtualFields are a good solution until you want to work with these new field-names.
Since I assume your frontend needs to use the old names from the database I would go with the afterFind-callback in your model.
Let's say you've got the model news.php:
# /app/model/news.php
function afterFind($results) {
foreach ($results as $key => $val) {
if (isset($val['News']['title'])) {
$results[$key]['News']['news_title'] = $val['News']['title']);
# unset($results[$key]['News']['title']); //use this if you don't want the "new" fields in your array
}
if (isset($val['News']['date'])) {
$results[$key]['News']['news_date'] = $val['News']['date']);
# unset($results[$key]['News']['date']); //use this if you don't want the "new" fields in your array
}
if (isset($val['News']['content'])) {
$results[$key]['News']['news_content'] = $val['News']['content']);
# unset($results[$key]['News']['content']); //use this if you don't want the "new" fields in your array
}
}
return $results;
}
You need to rename the database-fields to your new wanted value. You then can use these within conditions like every other field.
Only difference is, that you get back an array where all your fields have been renamed to your frontend-fields.
For more information about the available callback-methods have a look here: Callback Methods

Resources