symfony2 custom entity to array function - arrays

I have to help me convert an entity to array but I have issues resolving associated records, which I need.
However, this gives me an error
The class 'Doctrine\ORM\PersistentCollection' was not found in the
chain configured namespaces ...
The code follows:
public function serialize($entityObject)
{
$data = array();
$className = get_class($entityObject);
$metaData = $this->entityManager->getClassMetadata($className);
foreach ($metaData->fieldMappings as $field => $mapping)
{
$method = "get" . ucfirst($field);
$data[$field] = call_user_func(array($entityObject, $method));
}
foreach ($metaData->associationMappings as $field => $mapping)
{
// Sort of entity object
$object = $metaData->reflFields[$field]->getValue($entityObject);
if ($object instanceof ArrayCollection) {
$object = $object->toArray();
}
else {
$data[$field] = $this->serialize($object);
}
}
return $data;
}
How can I resolve the associated fields into their respective arrays.
I have tried using the built-in, and JMS serialiser, but this gives me issues of nestedness limits, so this is not an option for me.
UPDATE:
I have updated the code to handle instance of ArrayCollection as per #ScayTrase's suggestion. However, the error above is still reported with a one-to-many field map. In debug, the variable $object is of type "Doctrine\ORM\PersistentCollection"

For *toMany association properties implemented with ArrayCollection you should call ArrayCollection::toArray() first. Just check it with instanceof before, like this
if ($object instanceof ArrayCollection) {
$object = $object->toArray();
}

Related

save mathod in database cause Array to string conversion error in laravel

when I execute custom command with
php artisan query:all
every thing is good except error shown in console the error is
Array to string conversion
and the data is stored to database I did not understand the cause of this error and it's hidden when hide save to database method
the code of my service which the problem cause inside it is
<?php
namespace App\Services;
use Carbon\Carbon;
use GuzzleHttp\Client;
use App\Models\weatherStatus;
use Illuminate\Support\Collection;
class ApixuService
{
public function query(string $apiKey, Collection $cities): Collection
{
$result = collect();
$guzzleClient = new Client([ //create quzzle Client
'base_uri' => 'http://api.weatherstack.com'
]);
foreach ($cities as $city) {
$response = $guzzleClient->get('current', [
'query' => [
'access_key' => $apiKey,
'query' => $city->name,
]
]);
$response = json_decode($response->getBody()->getContents(), true); //create json from $response
$status = new weatherStatus(); //create weatherStatus object
//adding prameters
$status->city()->associate($city);
$status->temp_celsius = $response['current']['temperature'];
$status->status = $response['current']['weather_descriptions'];
$status->last_update = Carbon::createFromTimestamp($response['location']['localtime_epoch']);
$status->provider = 'weatherstack.com';
//save prameters
$status->save();
$result->push($status);
}
return $result;
}
}
So you can find some clarity in what you are trying to save, do the following:
$response = json_decode($response->getBody()->getContents(), true);
dd($response);
dd() will dump all the data from the $response and exist the script.
One of the values you are trying to save is an array. The field you are trying to save accepts a string and not array.

CakePHP pass a array to paginate() - new model

Hi I've done a find() and added a new field to some of the results:
$approved = $this->ExpenseClaim->find('all', array('conditions'=> array('ExpenseClaim.claim_status_id' => '3')));
$i = 0;
foreach ($approved as $ap) {
$approved[$i]['ExpenseClaim']['claimTotal'] = $this->ExpenseClaim->expenseClaimTotal($approved[$i]['ExpenseClaim']['id']);
$i++;
}
I now need to pass this to paginate, however I read here that you cannot do this and that I must create another model to use the afterFind() method only on this one particular find.
So I've created the new Model called ExpenseClaimTotal and set the UseTable to
public $useTable = 'expense_claims';
Then in the new models afterFind() method I did a simple debug:
public function afterFind($results, $primary = false) {
debug($results);
//return $results;
}
But when I now try and do a find against this new model in pagesController it fails:
$this->loadModel('ExpenseClaimTotal');
$approved = $this->ExpenseClaimTotal->find('all', array('conditions'=> array('ExpenseClaim.claim_status_id' => '3')));
This is the error I get:
Database Error
Error: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'ExpenseClaim.claim_status_id' in 'where clause'
SQL Query: SELECT `ExpenseClaimTotal`.`id`, `ExpenseClaimTotal`.`user_id`, `ExpenseClaimTotal`.`claim_status_id`, `ExpenseClaimTotal`.`created`, `ExpenseClaimTotal`.`modified`, `ExpenseClaimTotal`.`approved`, `ExpenseClaimTotal`.`approved_by`, `ExpenseClaimTotal`.`declined_by`, `ExpenseClaimTotal`.`date_submitted` FROM `expenses`.`expense_claims` AS `ExpenseClaimTotal` WHERE `ExpenseClaim`.`claim_status_id` = 3
There doesnt seem to be much in the docs about using 2 models for one table
You don't want to paginate an array
You're already performing a find, it's not sensible to perform a find and then paginate the resultant array.
Simply paginate your model data directly and inject your total values in the process. As such - if you put your original "added a new field to some of the results" logic in the model:
class ExpenseClaim extends AppModel {
public function afterFind($results, $primary = false) {
foreach ($results as &$ap) {
if (isset($ap['ExpenseClaim']['id'])) {
$ap['ExpenseClaim']['claimTotal'] = $this->expenseClaimTotal($ap['ExpenseClaim']['id']);
}
}
return $results;
}
}
Your controller code becomes simply:
public function index() {
$conditions = array('ExpenseClaim.claim_status_id' => '3');
$data = $this->paginate($conditions);
$this->set('data', $data);
}
And the code is simple and "just works".
Enhancements
The above is the simplest way to achieve the desired results, but has some disadvantages - namely it will call the total method on pretty much all finds.
Depending on exactly what you're doing you may wish to for example:
Cache your totals
If appropriate, you can remove problems by simply adding the field "claim_total" to the database, and recalculate whenever it changes. That would mean there is absolutely no extra logic when reading from the expense claim model.
Use a custom find type
If you don't want to recaculate the total on all finds - you can create a custom find type
class ExpenseClaim extends AppModel {
public $findMethods = array('allWithTotals' => true);
protected function _findAllWithTotals($state, $query, $results = array()) {
if ($state === 'before') {
return $query;
}
foreach ($results as &$ap) {
$ap['ExpenseClaim']['claimTotal'] = $this->expenseClaimTotal($ap['ExpenseClaim']['id']);
}
return $results;
}
And then use it in your paginate call:
public function index() {
$this->paginate['findType'] = 'allWithTotals'; # <-
$conditions = array('ExpenseClaim.claim_status_id' => '3');
$data = $this->paginate($conditions);
$this->set('data', $data);
}
In this way, only the index method will trigger the call to add the totals.

How can I convert validation error field names to input names in CakePHP?

I have a CakePHP (latest version) web app with forms and validation all working properly using traditional postback, but now I'm switching some of the forms to submit via ajax. When there are validation errors, I would like to get them back on the client as JSON formatted like so:
{
"success":false,
"errors":{
"data[User][username]":["This is not a valid e-mail address"],
"data[User][password]":["You must choose a password"]
}}
The keys for the errors array need to correspond to the name attribute on the form fields. We have some prebuilt client script that is expecting JSON formatted in this way. The good news is that this is very close to what the validationErrors object looks like in CakePHP. So I'm currently doing this in my controller:
if ($this->User->save($this->request->data)) {
} else {
if ($this->request->is('ajax')) {
$this->autoRender = $this->layout = false;
$response['success'] = false;
$response['errors'] = $this->User->validationErrors;
echo json_encode($response);
exit(0);
}
}
However, this is what the JSON response looks like:
{
"success":false,
"errors":{
"username":["This is not a valid e-mail address"],
"password":["You must choose a password"]
}}
Note that the errors keys have just the basic database table field names in them. They are not converted into data[User][username] format, which the FormHelper usually takes care of.
Is there some easy way to adjust the array before I return it? I don't want to simply loop through and prepend "data[User]" because that is not robust enough. I'd like some code I can put in one place and call from various controllers for various models. What does FormHelper use to come up with the input name attributes? Can I tap into that? Should I somehow use a JSON view?
That's because that's the way the $validationErrors array is formatted. To obtain the output you want you will have to loop through, there's no way around it.
foreach ($this->User->validationErrors as $field => $error) {
$this->User->validationErrors["data[User][$field]"] = $error;
unset($this->User->validationErrors[$field]);
}
I would suggest instead passing all errors to json_encode(). $this->validationErrors is a combined list of all model validation errors for that request available on the view (compiled after render). You should move your display logic (echoing json) into your view, and loop through it there.
in the view
$errors = array();
foreach ($this->validationErrors as $model => $modelErrors) {
foreach ($modelErrors as $field => $error) {
$errors["data[$model][$field]"] = $error;
}
}
$response['errors'] = $errors;
echo json_encode($response);
This would output something like this:
{
"success":false,
"errors": [
"data[User][username]": "This is not a valid e-mail address",
"data[User][password]": "This is not a valid password",
"data[Profile][name]": "Please fill in the field"
]
}
I have created a small recursive function to create validation error as a string with column name so that can be passed as json object.
/**
* prepare erorr message to be displayed from js
*
* #param array $errors validation error array
* #param stringn $message refernce variable
*
* #return void
*/
public function parseValidationErrors($errors, &$message)
{
foreach ($errors as $columnName => $error) {
$message .= "<strong>$columnName:</strong> ";
foreach ($error as $i => $msg) {
if (is_array($msg)) {
$this->_parseValidationErrors($msg, $message);
} else {
$message .= str_replace("This field", "", $msg . " ");
isset($error[$i + 1]) ? $message .= " and " : $message;
}
}
}
}
and controller code goes like this.
if (!$this->YourModel->saveAll($modelObject)) {
$errors = $this->YourModel->validationErrors;
$message = '';
$this->parseValidationErrors($errors, $message);
$response = array('status' => 'error', 'message' => $message);
}
$response['errors']['User'] = $this->User->validationErrors;

codeigniter multiple table joins

function in codeigniter model is not working. I'm getting http error 500 because of below code in codeigniter model function. if I remove this code.. code is working fine.
function get_ad_by_user($a,$b)
{
$this->db->select('title,ci_categories.category,ci_subcategories.subcategory,state,city,locality,ad_website,description,phone_number,address,image_token1,image_token2');
$this->db->from('ci_pre_classifieds');
$this->db->join('ci_categories', 'ci_categories.category_id = ci_pre_classifieds.category');
$this->db->join('ci_subcategories', 'ci_subcategories.subcategory_id = ci_pre_classifieds.sub_category');
$this->db->where('ci_pre_classifieds.id',$a);
$this->db->where('ci_pre_classifieds.ad_string_token',$b);
$q=$this->db->get();
$this->db->last_query();
log_message('debug','******query'.$this->db->last_query().'********');
if($q->num_rows()==1)
{
foreach($q->result_array() as $row)
{
$data['title]=$row['title'];
$data['category']=$row['ci_categories.category'];
$data['sub_category']=$row['ci_subcategories.subcategory'];
$data['state']=$row['state'];
$data['locality']=$row['locality'];
$data['ad_website']=$row['ad_website'];
$data['description']=$row['description'];
$data['phone_number']=$row['phone_number'];
$data['address']=$row['address'];
}
}
else
{
$data['message']="sorry either classified expired or deleted";
}
return $data;
}
I'm getting error in for each block..if I remove everything working...How to copy $row array in $data array
$data['title]=$row['title']; has a ' missing.
This could be the reason but without that error message, it's hard to tell.
Also, you may want to reduce the function to the following:
function get_ad_by_user($a,$b)
{
$data = array();
$this->db->select('title,ci_categories.category,ci_subcategories.subcategory,state,city,locality,ad_website,description,phone_number,address,image_token1,image_token2');
$this->db->from('ci_pre_classifieds');
$this->db->join('ci_categories', 'ci_categories.category_id = ci_pre_classifieds.category');
$this->db->join('ci_subcategories', 'ci_subcategories.subcategory_id = ci_pre_classifieds.sub_category');
$this->db->where('ci_pre_classifieds.id',$a);
$this->db->where('ci_pre_classifieds.ad_string_token',$b);
$q=$this->db->get();
log_message('debug','******query'.$this->db->last_query().'********');
if($q->num_rows()==1)
{
$data[] = $q->row_array();
}
else
{
$data['message']="sorry either classified expired or deleted";
}
return $data;
}
If you are only returning one row in your query, you can simply use $q->row_array() or $q->row() to return the single row.
Just in case, I would also declare $data = array() at the start of your get_ad_by_user function, otherwise it "could" complain that $data does not exist.

Cakephp, i18n, Retrieve translation records for associated models

Quoting from the cakephp Book (ver 1.3):
Note that only fields of the model you are directly doing find on will be translated. Models attached via associations won't be translated because triggering callbacks on associated models is currently not supported.
Has anyone come up with a solution for this?
If not could you give me some pointers concerning the following simple scenario.
I have 2 models:
Project, Category.
Project HABTM Category
I have properly set up i18n table and I have a few entries in the db, all translated. When I retrieve a project it does retrieve the translation but not the translated category because as it says in the cakephp book models attached via associations won't be translated.
I have another workaround; I don't know if it is any better or worse performance- or style-wise, only that it suits the "fat models, skinny controllers" goal:
AppModel.php
public function getTranslatedModelField($id = 0, $field) {
$res = false;
$db = $this->getDataSource();
$tmp = $db->fetchAll('SELECT content from s2h_i18n WHERE model = ? AND locale = ? AND foreign_key = ? AND field = ? LIMIT 1',
array($this->alias, Configure::read('Config.language'), $id, $field)
);
if (!empty($tmp)) {
$res = $tmp[0]['s2h_i18n']['content'];
}
return $res;
}
SomeModel.php
public function afterFind($results, $primary = false) {
foreach ($results as $key => $val) {
if (isset($val['SomeOtherModel']) && isset($val['SomeOtherModel']['id'])) {
$results[$key]['SomeOtherModel']['name'] =
$this->SomeOtherModel->getTranslatedModelField($val['SomeOtherModel']['id'], 'name');
}
// other possible queries for other models and/or fields
}
return $results;
}
OK I found a solution. Which is mostly a workaround. I should have thought of that earlier.
What I'm doing is the following. I'm finding all projects and recursively all categories associated with projects. Now since cakephp does not translate categories I am using the results from the initial query and I am performing a second one only for categories but using the category id values that I found on the first query. Now cakephp translates categories since I'm only searching for them and I can have their data translated.
At the moment I'm OK with this solution but it would be nice if first cakephp makes the translate behavior out of the box ready or secondly if someone had a behavior that could support retrieval of translation on associated models.
I generalized the afterFind part a bit, so that it automatically grabs the fields to translate from the associated models' actsAs["Translate"] array, and uses an array of associated models to (potentially) translate:
public function afterFind($results, $primary = false){
$modelsToTranslate = array("SomeModel", "AnotherModel");
foreach ($results as $key => $val){
foreach($modelsToTranslate as $mtt){
if (isset($val[$mtt])){
foreach($val[$mtt] as $fieldname => $fieldval){
foreach ($this->$mtt->actsAs["Translate"] as $fieldToTranslate){
$results[$key][$mtt][$fieldname][$fieldToTranslate] = $this->$mtt->getTranslatedModelField($val[$mtt][$fieldname]['id'], $fieldToTranslate);
}
}
}
}
}
return $results;
}
I took above solution and generalized both functions a bit, now it needs to be used together with the translate behaviour and both functions need to go into the model.php - everything else should work by itself:
public function getTranslatedModelField($id = 0, $field) {
$res = false;
$translateTable = (isset($this->translateTable))?$this->translateTable:"i18n";
$db = $this->getDataSource();
$tmp = $db->fetchAll(
"SELECT content from {$translateTable} WHERE model = ? AND locale = ? AND foreign_key = ? AND field = ? LIMIT 1",
array($this->alias, Configure::read('Config.language'), $id, $field)
);
if (!empty($tmp)) {
$res = $tmp[0][$translateTable]['content'];
}
return $res;
}
public function afterFind($results, $primary = false) {
if($primary == false && array_key_exists('Translate', $this->actsAs)) {
foreach ($results as $key => $val) {
if (isset($val[$this->name]) && isset($val[$this->name]['id'])) {
foreach($this->actsAs['Translate'] as $translationfield) {
$results[$key][$this->name][$translationfield] =
$this->getTranslatedModelField($val[$this->name]['id'], $translationfield);
}
} else if($key == 'id' && is_numeric($val)) {
foreach($this->actsAs['Translate'] as $translationfield) {
$results[$translationfield] =
$this->getTranslatedModelField($val, $translationfield);
}
}
}
}
return $results;
}

Resources