Saving Array to Database in CakePHP - arrays

I'm out of ideas here is my controller:
class GoogleNewsController extends AppController {
var $name = 'GoogleNews';
var $uses = array('GoogleNews', 'SavedNews');
var $helpers = array('Html','Form');
function index() {
$saved = $this->set('news',$this->GoogleNews->find('all'));
Im reading data from 'GoogleNews' and they are in my array. Array looks like this:
array(10) {
[0]=>
array(1) {
["GoogleNews"]=>
array(12) {
["title"]=>
string(32) "FIFA 11 für 25,49€ aus Jersey"
["link"]=>
string(54) "http://feedproxy.google.com/~r/myDealZ/~3/HuNxRhQJraQ/"
["pubDate"]=>
string(31) "Mon, 06 Dec 2010 10:53:22 +0000"
["creator"]=>
string(5) "admin"
["guid"]=>
array(2) {
["value"]=>
string(30) "http://www.mydealz.de/?p=15137"
["isPermaLink"]=>
string(5) "false"
}
["description"]=>
string(355) "
And I want to save elements to my database 'SavedNews'
I need to save description and title.
Can anybody tell me how should I write it?
$this->SavedNews->set(array('description' =>$this->GoogleNews->find('description')));
Is this a solution?
Its only way that it works, but it puts null values to my columns.

If I'm understanding your requirements correctly, the following should work.
In your controller:
class NewsController extends AppController
{
function import_from_google()
{
// Load the GoogleNews model and retrieve a set of its records
$this->loadModel('GoogleNews');
$newsFromGoogle = $this->GoogleNews->find('all');
$this->loadModel('SavedNews');
foreach ($newsFromGoogle as $_one) {
// Reset the SavedNews model in preparation for an iterated save
$this->SavedNews->create();
// Assemble an array of input data appropriate for Model::save()
// from the current GoogleNews row
$saveable = array(
'SavedNews' => array(
'title' => $_one['GoogleNews']['title'],
'description' => $_one['GoogleNews']['description']
)
);
// send the array off to the model to be saved
$this->SavedNews->save($saveable);
}
$this->autoRender = false; // No need to render a view
}
}
Refine as desired/required. For example, the iterated save operations should happen in the SavedNews model, rather than in the controller. The above code also has no fault-tolerance.
HTH.

Related

Adding addCondition() in a Form with fields from other tables

I don't know the syntax to access "CurrentTable.ForeignKey" nor "OtherTable.PrimaryKey" in a $model->addCondition() statement.
This is a fragment of my code which works:
$mm = new SYSPC_MODEL($this->app->db,['title_field'=>'MODEL_NAME']);
$mm->addCondition('MODEL_NAME', 'LIKE', 'DESK%');
In place of simply searching for MODEL_NAME like 'DESK%', I would like to display only the FK_MODEL_id values which exist in the SYSPC_MODEL table for the same FK_OS_ID than the current record FK_OS_ID value. So in SQL, we should have something like:
SELECT SYSPC_MODEL.MODEL_NAME WHERE ( DHCP_PC.FK_OS_ID = SYSPC_MODEL.id )
To understand easier the context, I reduced my code as much as possible:
<?php
include_once ('../include/config.php');
require '../vendor/autoload.php';
class SYSPC_OS extends \atk4\data\Model {
public $table = 'SYSPC_OS';
function init() {
parent::init();
$this->addFields([ ['OS_NAME', 'required'=>true, 'caption'=>'Identifiant d\'OS'],
['OS_DESCRIPTION', 'required'=>true, 'caption'=>'Description d\'OS']
]);
}
} // End of class SYSPC_OS
class SYSPC_MODEL extends \atk4\data\Model {
public $table = 'SYSPC_MODEL';
function init() {
parent::init();
$this->addFields([ ['MODEL_NAME', 'caption'=>'Nom du modele'],
['MODEL_BASE_RPM', 'caption'=>'Rpm de base']
]);
$this->hasOne('FK_OS_id',[new SYSPC_OS(),'ui'=>['visible'=>false]])->addField('OS_NAME','OS_NAME');
}
} // End of class SYSPC_MODEL
class DHCP_PC extends \atk4\data\Model {
public $table = 'DHCP_PC';
function init() {
parent::init();
$this->addFields([ ['PCNAME', 'required'=>true, 'caption'=>'Nom du pc']
]);
$this->hasOne('FK_OS_ID',['required'=>true,new SYSPC_OS(),'ui'=>['visible'=>false]])->addField('OS_NAME','OS_NAME');
$this->setOrder('PCNAME','asc');
$this->hasOne('FK_MODEL_id',['required'=>true,new SYSPC_MODEL(),'ui'=>['visible'=>false]])->addField('MODEL_NAME','MODEL_NAME');
}
} // End of class DHCP_PC
class PcForm extends \atk4\ui\Form {
function setModel($m, $fields = null) {
$PcWidth = 'three';
parent::setModel($m, false);
$gr = $this->addGroup('PC name');
$gr->addField('PCNAME',['required'=>true,'caption'=>'Nom du pc']);
$gr = $this->addGroup('OS');
$mm2 = new SYSPC_OS($this->app->db,['title_field'=>'OS_NAME']);
$gr->addField('FK_OS_ID',['width'=>$PcWidth],['DropDown'])->setModel($mm2);
$gr = $this->addGroup('Modèle');
$mm = new SYSPC_MODEL($this->app->db,['title_field'=>'MODEL_NAME']);
$mm->addCondition('MODEL_NAME', 'LIKE', 'DESK%'); // Works fine but I would like to display only the FK_MODEL_id values
// which exist in the SYSPC_MODEL table for the same FK_OS_ID
// than the current record FK_OS_ID value :
// SELECT SYSPC_MODEL.MODEL_NAME WHERE ( DHCP_PC.FK_OS_ID = SYSPC_MODEL.id )
$gr->addField('FK_MODEL_id', ['width'=>$PcWidth], ['DropDown'])->setModel($mm);
return $this->model;
}
} // End of class PcForm
$app = new \atk4\ui\App();
$app->title = 'Gestion des PC';
$app->initLayout($app->stickyGET('layout') ?: 'Admin');
$app->db = new \atk4\data\Persistence_SQL(
"pgsql:host=".$GLOBALS['dbhost'].";dbname=".$GLOBALS['dbname'],
$GLOBALS['dbuser'],
$GLOBALS['dbpass']
);
$g = $app->add(['CRUD', 'formDefault'=>new PcForm()]);
$g->setIpp([10, 25, 50, 100]);
$g->setModel(new DHCP_PC($app->db),['PCNAME', 'OS_NAME', 'MODEL_NAME']);
?>
Please look at https://github.com/atk4/ui/pull/551 - it might be what you're looking for.
Example here: https://ui.agiletoolkit.org/demos/autocomplete.php
Docs: https://agile-ui.readthedocs.io/en/latest/autocomplete.html?highlight=lookup#lookup-field
$form = $app->add(new \atk4\ui\Form(['segment']));
$form->add(['Label', 'Add city', 'top attached'], 'AboveFields');
$l = $form->addField('city',['Lookup']);
// will restraint possible city value in droddown base on country and/or language.
$l->addFilter('country', 'Country');
$l->addFilter('language', 'Lang');
//make sure country and language belong to your model.
$l->setModel(new City($db));
Alternatively you can use something other than drop-down, here is UI example:
https://ui.agiletoolkit.org/demos/multitable.php
Selecting value in the first column narrows down options in the next. You can have a hidden field inside your form where you can put the final value.
Thanks for your support but I still have some questions.
Question 1: I found "addRelatedEntity" and "relEntity" but I didn't found a description of those commands. Does it exist ? Is this a possible solution for my issue ?
Question 2: Is it possible to 'Lookup' in another table and if yes, how ?
Question 3: If 'Lookup' is not the solution, how to make a join (with filtering in the where clause) inside a model ?
Question 4: If the join is not the solution, is it possible to use DSQL inside a model ?
Question 5: Or do you have a DSQL example (with a self made join between several tables) associated with a CRUD ?

Whats's the proper way to loop through an array for an api in laravel 5.1

I am trying to loop through an array. I have experimented a bit but it's not working properly. it outputs.but it doesn't loop through everything I have in my database. this is my code for my ApiController
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Requests;
class ApiController extends Controller
{
public function index()
{
foreach (Post::all() as $post )
return array(
'results' => [
array('id' =>$post->title,
'marketname' => $post->subtitle),
]
);
}
}
also the curly bracket just before last is highlighted in my editor. When I hover over it, it says it's expecting a return. But if I move the return to where it says it should be, I get no output. To be clear I want to display something like this
"results": [
{
"id": "1002336",
"marketname": "0.2 Harvest Home Echo Park Market"
},
{
"id": "1006207",
"marketname": "0.3 Echo Park Market"
},
{
"id": "1003343",
"marketname": "0.4 La Familia Verde Farmers Market"
},
any help would be greatly appreciated
You need to collect the results first, and then return them.
public function index()
{
$results = array();
foreach (Post::all() as $post)
{
$results[] = array('id' => $post->title, 'marketname' => $post->subtitle);
}
return array('results' => $results);
}
The reason you're not getting the expected output is because you're returning prematurely after the first iteration of your loop.
I found a clean, concise answer. Here's the code
public function index() {
return Post::get(['title as id', 'subtitle as marketname']);
}

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.

Cakephp and Ajax-Post

I've set up cake php for ajax and json-requests using mostly this turtorial:
http://book.cakephp.org/2.0/en/views/json-and-xml-views.html
Everything is working fine, but if I make a post request (in this case for using cakephp with json-rpc), Cake enters an infite Loop saveing hundreds of empty entries in my database until it finally runs out of memory even if my controller and is completly empty, as long there is some json-output (even an 505 error message works). Is this due to automagic?
I am able to save data as intentend, when I pass data which is properly set up to fit the model. But if send anything else (just params or empty data for example) I enter this infite loop.
Even if my posts contain errors, I think this should not happen.
Here is the error message:
<b>Fatal error</b>: Allowed memory size of 33554432 bytes exhausted (tried to allocate 71 bytes) in <b>/var/www/****/cake/lib/Cake/Controller/Controller.php</b> on line <b>333</b><br />
Here is my call with jquery:
$.ajax({
url: '/dezem/cake/users.json', //even if I send the data to user without the json extension, the same happens...
type: 'POST',
dataType: 'json',
data: '{"method": "echo", "params": ["Hello JSON-RPC"], "id": 1}',
success: function(){alert("YEEHHEHAAW")},
error: function(){alert("Nööööööööööööööö")}
})
As requested here the Code inside my controller:
class UsersController extends AppController {
public $helpers = array('Js', 'Html');
public $components = array('RequestHandler');
....
public function index() {
$this->User->recursive = -1;
$users = $this->User->find('all');
if($this->RequestHandler->isAjax()){
$this->autoLayout = $this->autoRender = false;
if ($this->request->is('post')){
$this->set(array(
'data' => $users,
'_serialize' => array('data')
));
$this->render('/layouts/json');
}
else if ($this->request->is('get')) {
$this->set(array(
'data' => $users,
'_serialize' => array('data')
));
$this->render('/layouts/json');
}
}
}
}
As I said, an empty controller leads to the same result:
public function index() {
}

CakePHP label convention

When using $this->Form->input('fieldname'), CakePHP creates a label element that, by convention, uses the fieldname. I know I can specify another string to use for the label as an argument to the input method, but is there any way I can specify the label name in the model, so I don't have to repeat the label name in multiple ctp files (e.g. so I only have to change it in one place)?
So I created this and made a quick test which worked, however, can't guarantee it's bug free:
<?php
App::uses('FormHelper', 'View/Helper');
class MyFormHelper extends FormHelper {
public function label($fieldName = null, $text = null, $options = array()) {
if ($text === null) {
$entity = $this->entity();
$field = array_pop($entity);
$model = $this->model();
$object = $this->_models[$model];
if (isset($object->labels[$field])) {
$text = $object->labels[$field];
}
}
return parent::label($fieldName, $text, $options);
}
}
Drop that in app/View/Helper/MyFormHelper.php
Add it to the helper array and alias it if you want to.
Add a public $labels array to the model with field => label-text structure.
Hope it works.

Resources