CakePHP 3 Entity Confusion - cakephp

Here is the issue I am facing all the time since I started to learn CakePHP 3
What is this concept of entity a real world example would help alot.
public function add()
{
// why do we have to create new entity / what is the role of entity here.
$comment = $this->Comments->newEntity();
if ($this->request->is('post','put')) {
// why do we have to use this line after posting / what is the role of this line.
$comment = $this->Comments->patchEntity($comment,$this->request->data);
if ($this->Comments->save($comment)) {
$this->Flash->success('comment submitted successfully.');
} else {
$this->Flash->error('Sorry, comment could not be updated.');
}
}
return $this->redirect($this->referer());
}

Let me open the book for you:
While Table Objects represent and provide access to a collection of
objects, entities represent individual rows or domain objects in your
application. Entities contain persistent properties and methods to
manipulate and access the data they contain.
-
why do we have to create new entity / what is the role of entity here.
Almost everything, if not all, in Cake3 works with entities, what an entity is is explained above. You need to create a new entity so that the FormHelper can work with it, AFAIR it can still work with an array if configured to do so as well but the entity should be used.
The reason entities exist is to abstract the data. Some people think entities are the representation of a DB row - that's wrong. As the book says, they can be a row but don't have to represent a row because the 3.0 ORM can work with other resources as well. In theory you can have a CSV data source that returns an entity per line.
I suggest you to read the entity code in the CakePHP core to get a deeper understanding of what else entities provide, just saying they're "just" a set of properties is to short thought.
why do we have to use this line after posting / what is the role of this line.
The post data is merged into the previously created entity, that's it. Use the API if you have basic questions like that. See the API entry for patchEntity().

In simple word, Entity is a set of one record of table and their relational table, on that you can perform operation without touch of database and encapsulate property of entity (fields of table) as you want.
Advantages of Entity.
Modifying result sets outside of the database (for formatting or otherwise)
Needing to represent both the table and row in the same class.
Data validation was a fucking nightmare.
Inconsistent API in terms of both how we handled things internally as well as what (and how) we returned stuff.
Other random stuff as you want.
You can do run-time modification of result sets. Just add a method to your entity to return results in the way you want. This also means you can use composition for managing entities (yaya traits)
Validation is beautiful. We can validate data before it gets into an object and then validate the object state in a separate step.
It is easier for developers to understand what they are dealing with. You either have an object or an array of objects. An object can be linked to data which can also include other objects, but you no longer have to guess at what the array key will be, nor whether its nested funkily.
We can iterate on the interface for tables and entities separately. We couldn't easily change internals for the old Model class because of the implications on both, whereas now we can (in theory) change one without mucking about in the other.
It looks prettier simple.

Try this:
if ($this->request->is('post','put')) {
$data = $this->request->getData();
$comment = $this->Comments->newEntity();
$comment = $this->Comments->patchEntity($comment, $data);
$status = $this->Comments->save($comment);
if ($status) {
$this->Flash->success('comment submitted successfully.');
} else {
$this->Flash->error('Sorry, comment could not be updated.');
}
}
return $this->redirect($this->referer());
}
My advice is never use Post and Put in the same function. Just for good pratice. Put works fine when you make a update using id like a parameter.

Related

Cakephp 3 - Building an entity over several forms/actions

This is a design question.
I'm trying to build a booking system in cakephp3.
I've never done something like this with cake before.
I thought the best way might be to -- as the post title suggests -- build up an entity over several forms/actions.
Something like choose location -> enter customer details -> enter special requirements -> review full details and pay
So each of those stages becomes an action within my booking controller. The view for each action submits its content to the next action in the chain, and i use patch entity with the request data, and send the result to the new action's view.
I've started to wonder if this is a good way to do it. One significant problem is that the data from each of the previous actions has to be stored in hidden fields so that it can be resubmitted with the new data from the current action.
I want the data from previous actions to be visible in a read only fashion so I've used the entity that i pass to the view to fill an HTML table. That's nice and it works fine but having to also store that same data in hidden fields is not a very nice way to do it.
I hope this is making sense!
Anyway, I thought I'd post on here for some design guidance as i feel like there is probably a better way to do this. I have considered creating temporary records in the database and just passing the id but i was hoping I wouldn't have to.
Any advice here would be very much appreciated.
Cheers.
I would just store the entity in the DB and then proceed with your other views, getting data from the DB. Pseudo:
public function chooseLocation() {
$ent = new Entitiy();
patchEntity($ent,$this->request->data);
if save entity {
redirect to enterCustomerDetails($ent[id]);
}
}
public function enterCustomerDetails($id) {
$ent = $this->Modelname->get($id);
// patch, save, redirect again ...
}

CakePHP 3.0: How Can I Organize These Model Functions

I am working on a CakePHP3 application that will be used to display information about which products our suppliers are currently offering.
** Different Vendors provide their product lists in different ways, CSV, JSON, or by way of a web scrape **
I have 2 models that I have created:
Vendors - This references a specific Vendor that we use.
VendorProducts - This references all the products that all of our vendors offer.
I would like to be able to call something like:
$vendor->getAvailableProducts()
and have it either get the CSV and parse it, grab the JSON, or scrape the suppliers website and use this to populate the VendorProducts table in the database with products from this supplier.
I understand the idea behind Fat Models and Skinny Controllers, however I'm having a bit of difficulty implementing this feature.
I would like to provide the following functionality.
The Vendor's getAvailableProducts() function can be called via the web interface AND/OR a cakephp shell script that could be run in a cron job.
As some functionality (like scraping the website) takes a considerable amount of time, I would like to be able to see the progress of this function in the view,
eg: X/Y Products Updated from {Supplier}.
This can be broken down into the following questions:
1. Which file should my "getAvailableProducts()" function go in?
2. As each $vendor has a unique updateProducts() function, how would the correct function be called from $vendor->getAvailableProducts()
// something like this?
public function getAvailableProducts() {
if($vendor->name == "SupplierA") {
getProductsFromSupplierA();
}
if($vendor->name == "SupplierB") {
getProductsFromSupplierB();
}
..., etc.
}
3. How can the progress of this function be returned to a View?
Don't use table classes for that create a new namespace within the model layer or in the app itself:
src/Vendor
src/Model/Vendor
Have a factory that constructs and returns you the Vendor classes:
$vendorA = VendorFactory::get('SupplierA');
$vendorB = VendorFactory::get('SupplierB');
Each vendor class must implement a method fetchProducts(), use an interface or an abstract base class for that.
The method should return a normalized array that can be used to turn the products in entities:
$this->newEntities(VendorFactory::get('SupplierA')->fetchProducts());
You'll have a hard time determining the progress if there is no way to know the total amount of records. Which is likely when you scrape the website. Same issue applies when the API doesn't tell you the total amount of records per JSON data set. If you're able to get that total count somehow you can do this:
$this->newEntities(VendorFactory::get('SupplierA')->fetchProducts([
'limit' => 50,
'offset' => 0
]);
And implement pagination for the vendor which you can then use to run over all the records in a while() loop in chunks of X records. If you trigger that via shell you can create a "job" for that and update the progress after each chunk. There are multiple existing solutions for this kind of task already out there. Finally use Comet or Websockets to get the status updated on your website. Or the old way: Trigger an AJAX request every X seconds to check the status.
There is a lot more that could be said, but this is actually already a very broad question, there is very likely not enough detail to cover all cases. Also it might be possible (I'm pretty sure) there are different ways to solve this.

Write to multiple tables in joomla component?

I'm trying to create a component (front end) that uses multiple tables. I found 1 or 2 post that partially answer to the question but none really does. The point seems always simple and evident for the one who knows how to do it but it is never really explained (or I missed the right post).
In my component, the user enters data in one view that need to be stored in two tables:
the standard Joomla User table i.e. # __users
an additional table to store data that are not included in Joomla i.e. # __users_complements
I'm a beginner, so maybe I'm wrong, but I understood that the standard functions of joomla can only save results of a form in one table .
In my case, I guess that I have to override the standard functions in my model: com_component / model / my_model.php.
1) I'm confused because I do not really understand which function must be overrided: save ()? store ()? other?
2) Let's say I override the save() function, should I rewrite all the code to save data (explode the data array and create all the update queries) or should I create 2 standard table objects.
In this case, (2 objects) it seems weird to send each time the whole data array to the parent function as I know that a part is for table 1 and the other part for the table 2. I should be able to split before don't I?
3) Should I create 2 models and manage those models from my controller when I get back data from the form and call the save function of the model?
Could you help me to clarify how to do this saving in multiple tables?
An example with code will be very much appreciated.
Thank you
I finally made it. As I spent many hours on this and found that a lot of people where looking for an answer, here is how I did.
I suppose you know how to create a component, using the standard MVC structure:
Component entry point
Component controller
Eventually component router
Component view
Component model
Component controller
In model components\my_component\models\my_model.php create your own save function
public function save($data)
{
// Initialise variables.
$userId = (!empty($data['id'])) ? $data['id'] : (int)$this->getState('user.id');
$user = JFactory::getUser();
$table_one = $this->getTable('TableOne', 'MyComponentTable', array());
$table_two = $this->getTable('TableTwo', 'MyComponentTable', array());
// Bind the data.
if (!$table_one->bind($data))
{
$this->setError(JText::sprintf('USERS PROFILE BIND FAILED', $user->getError()));
return false;
}
if (!$table_two->bind($data))
{
$this->setError(JText::sprintf('USERS PROFILE BIND FAILED', $user->getError()));
return false;
}
// Store the data.
if (!$table_one->save($data))
{
$this->setError($user->getError());
return false;
}
if (!$table_two->save($data))
{
$this->setError($user->getError());
return false;
}
return $user->id;
}
Of course, you need the getTable function called in the save function
public function getTable($type = 'TableOne', $prefix = 'MyComponentTable', $config = array())
{
// call the administrator\components\com_mycomponent\tables\__tablename__.php
$this->addTablePath(JPATH_COMPONENT_ADMINISTRATOR . '/tables');
return JTable::getInstance($type, $prefix, $config);
}
And it works! So simple!
Of course, as I said in my question the whole $data is sent to the parent save() function to with data that are not necessary for table_one or table_two. It works this way with the standard joomla structure (no hack or direct query in the code).
Hope it helps.
There may be those out there who disagree with the way that the following method disrupts the MVC structure just a bit, but I've found it to be the simplest for me.
Typically, you have a model that fits one of the tables. In your example with pushing data to the users table as well as one in your component, I would add the following to the model for the table in your component:
public function save($data) {
if (!parent::save($data)) {
return false;
}
// add necessary code to save to the users table, since there isn't a standard way to do this that I'm aware of
// sometimes I will grab another model even
require_once(JPATH_BASE . '/administrator/components/com_users/models/user.php');
$other_model = $this->getInstance('user', 'UsersModel');
$other_model->save($data);
return true;
}
The first part of this function should save the data to the components table just like normal. But you can tack what you need on to the rest of the component to make whatever you like happen.
I would almost guarantee that there is a better way to chain models (and I've seen some of the changes happening in the Joomla Platform core that will lead to better ways in the future), but this should get you going for now.
In addition, for prompt 3, I would handle in the controller if you need to sometimes save just one table and sometimes save both. I've found that the save functions are pretty safe to run even when parts aren't loaded, so I usually just let it run.

Can I deal with Records in an easier way in CakePHP?

I started to tackle CakePHP and read through the documentation, but two things still seem a bit clumsy to me.
I know other Frameworks where I have a certain record which I'd like to store, but CakePHP suggests me to do it anonymously:
$this->Foo->create(array(...));
$this->Foo->save();
Why can't I tell CakePHP which Record to save, just like in every other framework:
$foo = $this->Foo->create(array(...));
$foo->save();
I would like to iterate through a whole RecordSet inside of a Controller. Why do I need to iterate using
$foos = $this->Foos->find('all');
foreach($foos as $foo){
$foo['Foo'] // ... here we have $foo.
I don't understand why find() returns a 2-dimensional array and there are only records in the inner array. Why isn't this directly an array of records?
$this->Foo is an instance of your Foo model. When you call methods on it, you are calling methods on the active record (if there is one) of that instance of the Foo model. So in terms of telling Cake which record to save, you don't need to - Cake knows to save the current active record.
Here's the code you pasted with comments, which might help.
// Prepare this instance of the Foo model to save a new record
$this->Foo->create(array(...));
// Save the new record that we have just prepared
$this->Foo->save();
And the other way...
// Call the create method on this instance of the Foo model, and return what?
// Return another instance of the Foo model?
// Why not just continue using the instance we already have, ie, $this->Foo
$foo = $this->Foo->create(array(...));
// Call the save method on the duplicate instance of the Foo model that was
// returned from the create method?
$foo->save();
// Why did 'create' need to return a duplicate instance of the model to do a save???
// Why not call the save on the same instance of the Foo model that we used to call the create?
Point 2. This is basically for consistency. Often, you'll be returning data from multiple tables, linked to one another. Lets say tables Foo and Bar have a 1 to 1 relationship, and you're getting Foo records, along with their associated Bar records.The array returned will need Foo and Bar keys, eg: inside of your foreach loop, $foo might contain:
$foo['Foo']['column1'], $foo['Foo']['column2'], $foo['Bar']['column1'], $foo['Bar']['column2']
To be consistent, when you only fetch from one table, it still returns in the form $foo['Foo']['column1'], just like it would if you fetched joined data from multiple tables.
EDIT: In response to your comment, say you have the code:
$foos = $this->Foos->find('all');
Say you wanted to call some model method on each row of your returned array, there are a few ways you could do it. One way is something like:
// This is code for the controller
$this->Car->find('all');
foreach($cars as $car){
$this->Car->driveTwoMiles($car); // the driveTwoMiles would be in your model class
}
So in your model, you'd have a method:
// This would be a method in your model class
function driveTwoMiles($car){
$this->id = $car['Car']['id']; // set the active record
// we are now inside the model, so $this->id is the same as calling $this->Car->id from the controller
// Do whatever you want here. You have an active record, and your $car variable, holding data
$this->Post->saveField('distance_driven', $car['Car']['distance_driven']+2);
}
Also, for cases where you just want to update one record, not many, you can just do a "read" rather than a "find('all')" - more info in the links below.
I would highly recommend reading all the way through these pages in the cake cook book:
http://book.cakephp.org/2.0/en/models/retrieving-your-data.html - Retrieving Data
http://book.cakephp.org/2.0/en/models/saving-your-data.html - Saving data
http://book.cakephp.org/2.0/en/models/deleting-data.html - Deleting data
All contain really important foundational info on how to work with Cake Models. Spend the time to really understand it now, and you'll save yourself countless headaches and code re-factors in future!

How to use Yii with a multilingual database model?

I’m having a problem getting the data from my database which I created to be completely multilingual, and I hope someone here can help me.
I’ve split up all my tables in 2 parts; the “universal” table (does not contain any text that needs to be translated) and the table which contains all the fields that need to be translated with their translations.
Example tables:
base_material
id
picture
base_material_i18n
base_material_id
localization_id
name
description
review_status
review_notes
localization
id
language_name
Query to get the translations (using English (en) as a fall-back language if there is no translation available):
SELECT o.id
, o.type
, o.code
, o.position
, ifnull(t.name,d.name) name
, ifnull(t.description,d.description) description
FROM base_material o
INNER JOIN base_material_i18n d
ON ( o.id=d.base_material_id)
LEFT OUTER JOIN base_material_i18n t
ON ( d.base_material_id=t.base_material_id AND t.localization_id='nl' )
WHERE d.localization_id='en'
My question is how I can automatically get those translations (with the fall-back language as in this query) attached to my model in Yii when I’m searching for the base_material objects? (This is only 1 example table, but almost all my tables (20+) are built in this way, so if possible I would be needing something flexible)
An example of an existing system using what I would need is Propel: http://propel.posterous.com/propel-gets-i18n-behavior-and-why-it-matters
Any ideas how to go about doing that? I’ve checked the existing Yii extensions regarding multilingual sites (like Multilingual Active Record), but they all use a different database design (general information+fall-back language in the main table, translations in the i18n table), and I’m not sure how to change those extensions to use my kind of DB model.
If someone knows of a way to change that existing extension so it can use my kind of DB scheme, then that would be absolutely brilliant and probably the best way to do this.
Edit: I've added a bounty because I still can't find anything on how to let Propel work with Yii (there does exist an extension for Doctrine, but Doctrine doesn't support this kind of DB model with translations either), nor any more information as to how to deal with this using an existing Yii extension or with scopes.
Edit: 98 times viewed but only 3 upvotes and 1 comment. I can't help feeling like I'm doing something wrong here, be it in my question or application/database design; either that or my problem is just very unique (which would surprise me, as I don't think my multilingual database design is that absurd ;-). So, if anyone knows of a better all-round solution for multilingual sites with Yii and/or Propel (apart from the current extensions which I really don't like due to the duplication of text fields) or something similar, please let me know as well.
Thanks in advance!
Have you tried http://www.yiiframework.com/extension/i18n-columns/ (based on http://www.yiiframework.com/extension/stranslateablebehavior/)?
It is an alternate, simpler, approach by adding new table fields in the style of {field}_{language code}, and then setting the translated field in the original model to the current language's translation on afterFind.
In essence, it will get you up and running with translatable fields with the translated content being fetched "automatically", for good and bad :). Adding and removing languages (=columns) is done using migrations.
I am also looking for a generic solution to implement i18n into Yii models.
Recently I chose a very similar database schema for a project like you.
The only difference is, that I am not using a separate language table, I store the language information in the i18n table.
The following solution is without a custom SQL statement, but I think this could be implemented with relation params, anyway, if you're working with foreign key in your database (eg. MySQL InnoDB) gii will create relations between your base_material and base_material_i18n table, like
class BaseMaterial extends CActiveRecord
public function relations()
{
return array(
'baseMaterialI18ns' => array(self::HAS_MANY, 'base_material_i18n', 'id'),
);
}
class BaseMaterialI18n extends CActiveRecord
public function relations()
{
return array(
'baseMaterial' => array(self::BELONGS_TO, 'base_material', 'id'),
);
}
Now you'd be able to access your translations by using the object notation for relations.
$model = BaseMaterial::model()->with('baseMaterialI18ns')->findByPk(1);
foreach($model->baseMaterialI18ns AS $translation) {
if ($translation->language != "the language I need") continue:
// do something with translation data ...
}
I thought about creating a behavior or base class for those models which would act a as helper for managing the translations - pseudo code:
I18nActiveRecord extends CActiveRecord
protected $_attributesI18n;
// populate _attributesI18n on query ...
public function __get($name) {
if(isset($this->_attributesI18n['language_I_need'][$name]))
return $this->_attributesI18n[$name];
else if(isset($this->_attributesI18n['fallback_language'][$name]))
return $this->_attributesI18n[$name];
else
parent::__get();
}
CActiveRecord __get() source
There is more work to be done to find the needed i18n record, also you could further limit the with() option to improve performance and reduce parsing on the PHP side.
But there may be different use cases how to determine the value, e.g. all translations, translation or fallback, no fallback (empty value).
Scenarios could be helpful here.
PS: I would be up for a github project!
You can try to use a simple multilingual CRUD extension.
it is very simple to use and modificate. you just need to add language field to your table.
just watch description here: http://all-of.me/yii-multilingual-crud/
it is in alpha state, but tried on a few projects. you can easily modificate it or contact author to fix or add features

Resources