Before i checked in the afterFind callback if the result of the find is empty.
Since the callback was removed in latest versions, where would be the place to check that from a behavior?
Im not realy sure if that is what i need. My use case is, i want to find out if a query has no result. Then i would create a default entry so it has 1 result.
https://book.cakephp.org/3.0/en/appendices/orm-migration.html#no-afterfind-event-or-virtual-fields
Once defined you can access your new property using $user->full_name. Using the Modifying Results with Map/Reduce features of the ORM allow you to build aggregated data from your results, which is another use case that the afterFind callback was often used for.
Call count() on the ResultSet object
https://api.cakephp.org/3.4/class-Cake.ORM.ResultSet.html#_count
No idea why you're not using that and want to do the count manually, but go on.
Using resultFormatter()
https://book.cakephp.org/3.0/en/orm/query-builder.html#adding-calculated-fields
You behavior will have to add the formatter in your beforeFind() to the query. You can add a custom find that adds it as well. Example code (taken from the docs):
$query->formatResults(function (\Cake\Collection\CollectionInterface $results) {
return $results->map(function ($row) {
$row['age'] = $row['birth_date']->diff(new \DateTime)->y;
return $row;
});
});
Count the results there.
Using map/reduce
https://book.cakephp.org/3.0/en/orm/retrieving-data-and-resultsets.html#map-reduce
More often than not, find operations require post-processing the data that is found in the database. While entities’ getter methods can take care of most of the virtual property generation or special data formatting, sometimes you need to change the data structure in a more fundamental way.
You behavior will have to add the mapper/reducer in your beforeFind() to the query. You can add a custom find that adds it as well. Example code (taken from the docs):
$articlesByStatus = $articles->find()
->where(['author_id' => 1])
->mapReduce($mapper, $reducer);
Check the above link for a detailed explanation including examples of how to create a mapper and reducer.
Related
I'm trying to paginate a union in cakephp 3. I have already found and read How do you modify a UNION query in CakePHP 3?, but am probably too new to cakephp to understand properly what is meant by "use a custom finder". I have read the relevant section in the cakephp book, but still fail to see how this helps to apply the epilog() to the query. Maybe someone could show a complete example?
I have two models Customers and Suppliers. I am trying to builds a search function that returns matching entries from both models, including pagination and sorting.
public function index($q=null) {
$this->loadmodel('Suppliers');
$this->loadmodel('Customers');
$customers=$this->Customers->find('all')->select(['type'=>"'Customers'",'id'=>'id','name'=>'name','phone'=>'phone'])
->where(['name like'=>'%'.$q.'%']);
$suppliers=$this->Suppliers->find('all')->select(['type'=>"'Suppliers'",'id'=>'id','name'=>'name','phone'=>'phone'])
->where(['name like'=>'%'.$q.'%']);
$customers->union($suppliers);
$results=$this->paginate($customers);
$this->set(compact(['q','results']));
}
As detailed in How do you modify a UNION query in CakePHP 3?, this results in the order,limit and offset only being applied to the first select.
Removing the paginate() and applying order,limit and offset manually via epilog() works, but than I'd have to recreate the whole pagination mechanism in my code. While this is my current plan in case I can't find a better solution, I don't believe this would be the right way to do it.
Could someone (#PhantomWatson, #ndm) show an example, how the suggested custom finder can solve this?
Is there any way to access original data in afterSave?
I would like to log the changes on important data.
With $entity->isNew() I could check if it was an insert or an update, but how can I get what changed?
You can access the original values via Entity::getOriginal() or Entity::extractOriginal(). If you want to get all changed fields, combine the latter one with Entity::visibleProperties(), something like:
debug($entity->extractOriginal($entity->visibleProperties()));
This should return the original values of all changed fields.
See also
http://api.cakephp.org/3.0/class-Cake.Datasource.EntityTrait.html#_extractOriginal
http://api.cakephp.org/3.0/class-Cake.Datasource.EntityTrait.html#_getOriginal
http://api.cakephp.org/3.0/class-Cake.Datasource.EntityInterface.html#_visibleProperties
As of CakePHP 3.0.4, you can either use Entity::extractOriginal(), which will return the original value of any field, whether it has changed or not, or use Entity::extractOriginalChanged(), which will only return changed fields.
With this update, to reproduce the behaviour described in the accepted answer, you will thus need something like:
public function afterSave(Event $event, Entity $entity, $options)
{
debug($entity->extractOriginalChanged($entity->visibleProperties()));
}
See CakePHP 3.0.4 Release Notes, stating:
EntityTrait::extractOriginal() now behaves consistently with extract(). Both methods now include all named properties [...] A new method extractOriginalChanged() can be used to extract only the original values of changed attributes.
I am building a custom package provider, and I am not sure what the query method is supposed to return.
Eg of a package provider:
https://github.com/puppetlabs/puppet/blob/master/lib/puppet/provider/package/dpkg.rb
Found example of package provider, but no documentation of what each method is supposed to do.
Thanks!
The query method is called by the Puppet::Provider::Package base type. The properties method populates the property_hash using the query method:
# Look up the current status.
def properties
if #property_hash.empty?
#property_hash = query || {:ensure => :absent}
#property_hash[:ensure] = :absent if #property_hash.empty?
end
#property_hash.dup
end
In the rubydoc of Puppet::Provider it says the following about the property_hash:
Property hash - the important instance variable #property_hash contains all current state values for properties (it is lazily built).
It is important that these values are managed appropriately in the
methods {instances}, {prefetch}, and in methods that alters the
current state (those that change the lifecycle (creates, destroys), or
alters some value reflected backed by a property).
Thus, query should return a hash that reflects the current state of the package expressed in Puppet's package properties.
This is not documented AFAIK. I figured it out by reverse engineering.
Hope this helps. Good luck!
I have attached the Translate behavior to one of my models and I have some shortcomings regarding this:
1) If I don't save data in all fields passed as params when attaching the behavior to the model, $Model::find() method doesn't get the inserted rows.
public $actsAs = array(
'Translate' => array(
'title' => 'title_Translation',
'description' => 'description_Translation',
'description_long' => 'description_long_Translation'
)
);
Ex: if i pass to $Model::save() method only a value for 'title', the data is saved, even in the i18n table, but the $Model::find() doesn't get anything. I must pass data for all the fields.
Can I force it to retrieve those records ?
2) How can I get all the records in the admin side of the application (regardless of the language in which a record is saved) in order to list them so the user can alter it (edit data, save data in multiple languages)? Right now, I can only get the records that correspond to the current language (read from Configure or set explicitly)..
Thank you!
I kind of solved it, I copied the TranslateBehavior to app/Model/Behavior (just to avoid problems on future upgrades and keep the original one just in case) then I changed the _addJoin(...) method of the behavior, just changed the join type from INNER to LEFT on line 255 (I use cake 2.2.3).
Now if a record exist it is always retrieved, even if translated fields are missing.
Don't see any drawbacks besides the need to check if the translation field is empty.
OK, I might be a bit late, but anyway...
1) Cake uses an INNER JOIN when fetching a row and it's associated translations, so basically there's no easy way around this. You have to make sure you save every translatable field, every time - even if you just save it as blank. The only alternative would be to go hacking round the core to make it use a left join rather than an inner join - but don't do that.
2) The cookbook explains how to fetch all records here: http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#retrieve-all-translation-records-for-a-field
Now, probably most of the time you want to get just one translation, so you don't want to modify the definition of your $actsAs['Translate'] array in your model. So what I did, was set up a method in AppModel.php which modifies the $actsAs['Translate'] array on the fly:
/*
* See http://book.cakephp.org/2.0/en/core-libraries/behaviors/translate.html#using-the-bindtranslation-method
* This is for making it so we fetch all translations, as opposed to just that of the current locale.
* Used for eg. editing (multiple translations) via the admin interface.
*/
public function bindAllTranslations(){
$translatableFields = $this->actsAs['Translate'];
$keyValueFields = array();
foreach($translatableFields as $field){
$keyValueFields[$field] = $field.'Translation';
}
$this->bindTranslation($keyValueFields,false); // false means it will be changed for all future DB transactions in this page request - and won't be reset after the next transaction.
}
So, if it's an admin method (or any other situation you want all translations) you call that code before doing a find:
$this->MyModel->bindAllTranslations();
$this->MyModel->find('all');
Hope that helps!
Not exactly sure if it will help in your case, but you can also use
array to set locale before you call find()
$this->YourModel->locale = array("ENG", "GER", "JAP");
This way you will always get all records even if they don't have all possible translations.
Thanks a lot eleonzx, I'm having this problem since a decade, and with your simple answer I can now move forward ! So thanks again.
And maybe this code can help a lot of people :
in my AppController beforeFilter method I call _setLanguage
private function _setLanguage() {
if($this->Session->read('Config.language')){
$locale = $this->Session->read('Config.language');
$this->{$this->modelClass}->setLocale($locale);
}else{
$this->{$this->modelClass}->Behaviors->disable('Translate');
}
}
With the else condition I disable the Translate Behavior on the fly to get the original contents if there is no locale set in the session (I use basic links to switch between languages).
For example I have a wpf window bound to an customer Entity (let suppose it's cus1). Then I load another entity from context :
customer cus2 = context.customers.where(x=>x.id=10).FirstOrDefault();
Now I want cus1 = cus2 ? I can do this way :
cus1.name = cus2.name;
cus1.address = cus2.address;
...
...
This way meets my case (the content of textboxs in the form change immediately into values of cus2) but I wonder if there is anyway to make it shorter since cus1=cus2 doesn't work ?
Thanks
You can use the memberwise Clone method to make a shallowcopy of a business object:
See http://msdn.microsoft.com/de-de/library/system.object.memberwiseclone.aspx
You could also use Serialization or Reflection, to do it on your own. However both oof the methods are slower then writing it directly.
Take a look at this article. Maybe you will find it helpful:
http://www.codeproject.com/KB/dotnet/CloningLINQ2Entities.aspx
Edit:
Btw. Remember, using using MemberwiseClone, in case of ReferenceTypes will effect in copying the references, not objects.
If you want to update the values of a Customer entity in memory with the newest values in the datastore you can use the Refresh method on your ObjectContext.
Here is the documentation.
In your case it would look like:
context.Refresh(RefreshMode.StoreWins, cus1);
If you really want to map two entities you could have a look at AutoMapper. AutoMapper will help you by automatically mapping entities to each other with a default setup that you can tweak to your needs.