I have a model called Game, which has a hasMany relationship with a Planet model (which in turn has a belongsTo defined back to the Game model).
Upon creating my first Game record, I use the value of its starting_num_planets field to try to create that many associated Planet records. The GamesController does this by calling a function in the PlanetsController which, each time called, returns an array that matches the Planet model, and should be saveable, which is the next thing the GamesController tries to do.
The Game record is created just fine, but I am somehow getting an invalid SQL syntax error apparently at the point at which I'm trying to access the second controller's function. Which is weird, because I'm not trying to save until a couple of lines later.
Here's the code from the GamesController:
public function newgame() {
if ($this->request->is('post')) {
$this->Game->create();
if ($this->Game->save($this->request->data)) {
// Add the 'id' element to the Game array and put the whole game record on the session
$this->request->data['Game']['id'] = $this->Game->getLastInsertID();
$this->Session->write('Game', $this->request->data);
// Create the number of planet records indicated by start_num_planets
$count = 0;
$planets = array();
while ($count < $this->request->data['Game']['starting_num_planets']) {
$planetData = $this->Game->Planet->generate_planet_data($this->request->data['Game']['id']);
$planets[$count] = $planetData;
$this->Game->Planet->create();
$this->Game->Planet->save($planetData);
$count++;
}
$this->redirect(array('action' => 'main'));
} else {
$this->Session->setFlash('There was a problem creating the game.');
}
}
}
At the line number for this line:
$planetData = $this->Game->Planet->generate_planet_data($this->request->data['Game']['id']);
I am getting:
Error: SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'generate_planet_data' at line 1
SQL Query: generate_planet_data
And I have no idea why, I'm afraid.
Here's the function being called in the PlanetsController:
public function generate_planet_data($gameId) {
$planetNames = array(1 => 'name1', 2 => 'name2', 3 => 'name3', 4 => 'name4');
$planetImages = array(1 => '01', 2 => '02', 3 => '03', 4 => '04');
$planetData = array();
// game_id is the foreign key, do I have to explicitly state that in the model?
$planetData['Planet']['game_id'] = $gameId;
$planetData['Planet']['planet_name'] = $planetNames[rand(1,10)];
$planetData['Planet']['planet_image_file'] = $planetImages[rand(1,4)];
return $planetData;
}
The model association in Game.php is simply:
var $hasMany = array(
'Planet' => array(
'className' => 'Planet'),
'Player' => array(
'className' => 'Player'
)
);
Can anyone tell what's going wrong? This is my first attempt at using associated models, and it's gone a bit awry! :)
The problem is that you're calling a missing function here:
$planetData = $this->Game->Planet->generate_planet_data($this->request->data['Game']['id']);
As you said generate_planet_data is on the controller. So you should call it thusly:
$planetData = $this->generate_planet_data($this->request->data['Game']['id']);
As stated in the comments, this logic probably belongs in the Planet model. If you moved it there you would then be able to access it via your original way.
Related
I'm making a project where a user can publish/post their own stories and read others' stories. Very simple.
This is my controller method named publish:
public function published()
{
$story = array('author' => $this->session->userdata('username'),
'title' => $this->input->post('title'),
'synopsis' => $this->input->post('synopsis'));
$new_storyid = $this->story_model->new_story($story);
if($new_storyid != NULL)
{
$genre = $this->input->post('genre');
for($temp=0;$temp<count($genre);$temp++)
{
$genres[$temp] = array('story_id' => $new_storyid,
'story_genre_name' => $genre[$temp]);
}
$insert_genre = $this->story_model->new_story_genre($genres);
$tag = $this->input->post('tags');
for($temp=0;$temp<count($tag);$temp++)
{
$tags[$temp] = array('story_id' => $new_storyid,
'story_tag_name' => $tag[$temp]);
}
$content_warning = $this->input->post('content_warning');
for($temp=0;$temp<count($content_warning);$temp++)
{
$content_warnings[$temp] = array('story_id' => $new_storyid,
'story_content_warning_name' => $content_warning[$temp]);
}
//$chapter = array('story_id' => $new_storyid,
//'chapter_number' => 1, 'chapter_title' => $this->input->post('chapter_title'),
//'chapter_content' => $this->input->post('chapter_content'),
//'chapter_number' => 1, 'date_added' => mdate('%Y-%m-%d %h-%i-%s',time()));
//$result = $this->story_model->add_chapter($chapter);
//if($result){
//redirect('account/userprofile_published_stories');}
}
}
This is my model methods for the above controller method:
public function new_story($story)
{
$this->db->select('user_id');
$query = $this->db->get_where('users',array('username' => $story['author']))->result();
foreach($query as $row)
{ $userid = $row->user_id; }
$publish = array('user_id' => $userid,
'story_title' => $story['title'],
'synopsis' => $story['synopsis']);
$this->db->insert('story',$publish);
return $this->db->insert_id();
}
public function new_story_genre($genre)
{
foreach($genre as $row)
{
$this->db->insert('story_genre', $row);}
}
public function add_chapter($chapter){
$this->db->where('story_id', $chapter['story_id']);
return $this->db->insert('chapters', $chapter);
}
I haven't added the other 2 functions for my tags and content warning inserts because i am confused right now. It all works fine, my genre is inserted.
My tables looks like this:
Story tables
In inserting a story in my above method, the first thing i do is insert a new story row in my story table and returns the new_storyid variable.
after that with the new storyid i add the genre,tags,content warning then the chapters.
My question is, what should i return in my methods for inserting the genre,tags,contentwarning?
I forgot this part because every model method ive written so far always returns a variable i needed in my controller. My first thought was to return a TRUE/FALSE variable if insert is successful/fail but barring special circumstances since ive already processed the data its 100% sure to insert successfully. Should i be returning TRUE/FALSE and adding an if statement like:
if($insert_genre){
//insert tags here
if($insert_tags){
//insert content warning here
if($insert_content_warning){
//insert chapters here
//redirect to view here
}
}
}
Or can i just not return anything? and if so, is this a proper/right way?
EDIT: I forgot to mention i haven't yet added form_validation rules before all the inserts. So my function will be nested in multiple if statements.
I just edited my model method:
public function new_story_genre($genre){
$inserted = 0;
foreach($genre as $row){
$this->db->insert('story_genre', $row);
$inserted += $this->db->affected_rows();}
if($inserted == count($genre)){
return TRUE;}else{ return FALSE; }
}
Above compares the number of inserted rows with the number of rows passed into the method. Everytime a row is inserted it adds 1 to the inserted variable. So if my controller passes 3 rows into the method, the inserted variable should also be 3 for a successful insert.
I think you are correct in always returning something. Errors can and do happen for whatever reason, and its a good idea to account for them even if you already validated your data (you never know). Coding practices suggest that more than a couple of nested ifs is bad practice. A personal preference of mine is to check for failure rather than success all the way down the chain until the last lines of the function (if it got that far than everything is good to go).
A scheme like this I usually use:
public function something() {
if (!$insert_genre) {
// add flash error message
// redirect to controller
}
if (!$insert_tags) {
// add flash error message
// redirect to controller
}
if (!$insert_content_warning) {
// add flash error message
// redirect to controller
}
// yay, something went right!
}
In this kindof circumstance it is very procedural. The most important conditions should be first, and if C depends on A, then A should be the first condition.
Unrelated:
It is hard to follow some of your text here, but it also seems like you should look into how you are doing the genres. If the entered genre already exists in the database do you really need to add it? Shouldn't you just use a relationship there storing the id in the main table and joining when displaying?
Im trying use "$this->input->post();" of Codeigniter to do not need specify each field in my form. But im getting troubles when try insert into Database.
Look my controller:
public function cadastrar(){
$var = $this->input->post(null, TRUE);
$this->load->model('m_clientes');
$this->m_clientes->inserir($var);
}
My controller is simplified here because i know how to handle database in codeigniter.
The result of this post was:
Array ( [nome] => Raphael [sobrenome] => Schubert [cpf] => 893.528.432-89 [rg] => 4529875231908472 [telefone] => (53) 2980-5792 [celular] => (53) 9 2180-7529 [rua] => Israel de Almeida [numero] => 859 [cep] => 88.312-000 [bairro] => São Vicente [cidade] => ITAJAÍ [estado] => Santa Catarina [email] => rfswdp#gmail.com [tipo] => pf [cnpj] => 34.827.481/2834-78 [inscricaoestadual] => 34120489032814930128 [razaosocial] => Teste [nomefantasia] => Disney [dataaberturaempresa] => 10/21/15 [proprietario] => Marcos Aurelio )
I normaly use this way to insert:
$data = array(
'user_name' => $this->input->post('user_name',TRUE);
'user_phone' => $this->input->post('user_phone',TRUE);
'user_role' => $this->input->post('user_role',TRUE);
);
$this->name_of_model->inserir($data);
And works...
But i was trying to use just $this->input->post(); to get all fields from form. Because my actualy application will have hundreds of fields and i was trying to do not write each line.
So my model actually was:
public function inserir($var){
if($var!=NULL):
print_r($var);
$this->db->insert('tb_usuarios',$var);
endif;
}
But i`m getting and error saying:
Message: Undefined property: Clientes::$db
and
Message: Call to a member function insert() on null
My table name is: "tb_usuarios"
I changed all fields in database to accept NULL to see if i`m get some field name wrong... but not work...
Any tips??
There is no need to catch the POST var inside $var. You can see POST variable inside the model very well. So all you need to do in the controller is:
public function cadastrar(){
$this->load->model('m_clientes');
$this->m_clientes->inserir();
}
,and inside your model:
public function inserir(){
if( count($this->input->post()) > 0):
$this->db->insert('tb_usuarios',$this->input->post());
endif;
}
The fields names in your form must correspond to the column names inside your table.
The message Message: Call to a member function insert() on null means you forgot to load the database library, just like remiheens said.
But my advice is, to use form validation for your fields, so you may be sure all necessary fields are completed using the required data format for each one of them. Although this may require allot of coding, there is no other safe way from errors on database operations. You cannot trust the user to insert the data correctly, that's why you need form validation.
In here $var = $this->input->post(null, TRUE); you use null. null is not valid input name. name = ''
and this will works $this->input->post('user_name',TRUE);
cz of it has input tag name (name = 'user_name').
We use ,TRUE) next to input post field to allows XSS Protection
$var = $this->input->post(null, TRUE); Is Wrong
while you trying this, it shows
Message: Undefined property: Clientes::$db and Message: Call to a member function insert() on null.
will not work
public function cadastrar(){
$var = $this->input->post(null, TRUE);//will never give valid response
$this->load->model('m_clientes');
$this->m_clientes->inserir($var);
}
Works well
public function cadastrar(){
$this->load->model('m_clientes');
$data = array(
'user_name' => $this->input->post('user_name',TRUE);
'user_phone' => $this->input->post('user_phone',TRUE);
'user_role' => $this->input->post('user_role',TRUE);
);
$this->name_of_model->inserir($data);
$this->load->model('m_clientes');
}
This is because your database isn't loaded into codeigniter instance. ($this->db)
Just try to autoload "database" library (config/autoload.php) or load/connect your database in your model with :
$this->load->database();
Don't forget to edit your config/database.php ;)
I handle hundreds of fields everytime but I also validate basically each one. I always do this:
$customer['name'] = $this->input->post('customer_name');
$customer['age'] = $this->input->post('customer_age');
$customer['country'] = $this->input->post('customer_country');
$customer['city'] = $this->input->post('customer_city');
// validations
if(isAgeValid($customer['age']) == FALSE)
{
echo 'Hold on, your age...hmm check it out!';
return;
}
$this->customers_model->add($customer);
The function that handles the insertion only has this:
public function add($data)
{
$data = $this->security->xss_clean($data);
$this->db->insert('customers', $data);
return $this->db->insert_id();
}
Pretty clean and simple. Now, if you don't want to validate the fields or just want to validate some of them and want to insert the others without validation this is what I purpose based on the previous code:
// validations..
if(isAgeValid());
$customer = array();
foreach($_POST as $key => $value)
$customer[$key] = $value;
$this->customers_model->add($customer);
$data = array(
'user_name' => $this->input->post('user_name');
'user_phone' => $this->input->post('user_phone');
'user_role' => $this->input->post('user_role');
);
$this->load->model('name_of_model');
$this->name_of_model->inserir($data);
Model:
public function inserir($data)
{
$this->db->insert('***', $data);
if ($this->db->affected_rows() > 0) {
return true;
}
}
I have a stream/table named profiles. All of its column are stream-fields. I am trying to restrict the result returned by the the function, get_entries() depending on some criteria. Below is my code:
$data = [
'stream' => 'profiles',
'namespace' => 'users',
'where' => 'user_id = 3' // lets say, this is my criteria
];
$row = $this->streams->entries->get_entries($data); // returns empty
The varaible, $row resulted in empty array. Although there is one row in table, profiles where user_id is 3. I have read the documentation of pyrocms and it pretty much says the exact way to use the where clause (just like above).
NOTE: I have also tried writing like
'where' => 'profiles.user_id = 3'`
joy !to avoid table conflict. Still no
But when I write the code like this:
$row = $this->streams->entries->get_entries($query);
$query = [
'stream' => 'profiles',
'namespace' => 'users'
];
// No where clause this time
$row = $this->streams->entries->get_entries($query);
This time $row returns all rows including the row with user id 3.
I am unable to use the where clause in get_entries in a right way. I might have done some mistake. Help me out guyz
NOTE: I am using community edition.
I think this might be due to a bug (well, not a bug, but a feature that doesn't work as intended).
If I'm intentionally issue a wrong query, the sql query output is
SELECT [ ... ] LEFT JOIN `default_profiles` as `profiles` ON `profiles`.`user_id`=`default_profiles`.`created_by` WHERE (user_id` = 1) ORDER BY `default_profiles`.`created` DESC
Here you see that PyroCMS tries to lookup the data for the "created_by" field. And that doesn't work in this case.
If you disable the 'created_by' field, you should get the correct row:
$this->streams->entries->get_entries(
array(
'stream' => 'profiles',
'namespace' => 'users',
'where' => 'user_id = 3',
'disable' => 'created_by'
)
);
It would be great if you could file an issue on the pyrocms github page. If you won't I'll do it in the next few days.
Model
public function get_entries($table, $where) {
$this->db->select('*');
$this->db->from($table);
foreach ($where as $key => $value) {
$this->db->where($key, $value);
}
$this->query = $this->db->get();
foreach ($this->query->result_array() as $row) {
$array1[] = $row;
}
if ($this->query->num_rows() == 0)
return false;
else
return $array1;
}
call this model function as
$row = $this->streams->entries->get_entries('profiles',array('user_id '=>3));
I'm Trying to solve this error i'm having with PHP, i'm not completely familiar with the Language, so it would be nice if you would help me out, I can't figure out this error.
I have this Code Here:
public function index() {
$counterino = ClientsJobs::all()->count();
$MasterArray = array();
/* Go Through All of the Records in the Client-Jobs Table and Resolve their columns to Desired Names */
for ($i = 1; $i <= $counterino; $i++ ) {
//Temporary Array for one Name-Resolved-Row of the Table.
$tempArray = array(
'id' => ClientsJobs::find( $i )->id, // id
'client_name' => ClientsJobs::find( $i )->clients->fname , // get the first name ( based on fk )
'job_name' => ClientsJobs::find( $i )->jobs->name, // get the name of the job ( based on fk )
'wage' => ClientsJobs::find( $i )->wage, // wage for the job
'productivity'=> ClientsJobs::find( $i )->producivity // productivity level for the job
);
$MasterArray[] = $tempArray; //add the row
}
return $MasterArray;
}
This code changes the names of the of the Columns in the ClientsJobs Junction Table.
public function up()
{
Schema::create('clients-jobs', function(Blueprint $table)
{
$table->increments('id')->unsigned();
$table->integer('client_id')->unsigned();
$table->foreign('client_id')->references('id')->on('clients');
$table->integer('job_id')->unsigned();
$table->foreign('job_id')->references('id')->on('jobs');
$table->decimal('wage', 4, 2);
$table->decimal('productivity', 5, 2); // 0.00 - 100.00 (PERCENT)
$table->timestamps();
});
}
The Jobs and Clients Table are very simple.
I am having the Error in the index() function I posted above, it says
'Trying to get property of non-object'
Starting on the Line
'client_name' => ClientsJobs::find( $i )->clients->fname,
It's also mad at me for the other parts of setting the array.
I have tested the individual functions I am using to set the array and they all work, fname should also return a string, I used dd() to get the value.
I have tried:
-Using FindorFail
-Setting the Array without the for loop and setting each element manually
-Dumping out multiple parts of the function to make sure it works( counterino, all of the functions for the array, .. )
My guess is that it has to do with the type-deduction of PHP, I actually only need a string array, but would still like to use the name mappings because I am going to be passing this a View I am using for some of my other stuff. The Code was actually working earlier, but I broke it somehow (adding a new record or running a composer update?) anyway, there's some serious voodoo going on.
Thanks in Advance for the help, I am working on this project for a Non-Profit Organization for free.
P.S. I am using Laravel 4.2, and Platform 2.0
First off, this is a horrible practice:
$tempArray = array(
'id' => ClientsJobs::find( $i )->id, // id
'client_name' => ClientsJobs::find( $i )->clients->fname , // get the first name ( based on fk )
'job_name' => ClientsJobs::find( $i )->jobs->name, // get the name of the job ( based on fk )
'wage' => ClientsJobs::find( $i )->wage, // wage for the job
'productivity'=> ClientsJobs::find( $i )->producivity // productivity level for the job
);
By calling ClientJobs::find($i) multiple times, you are doing multiple times the same lookup - either to your DB, or to your cache layer if you have one configured.
Secondly, the answer to your question depends on your ClientJobs model. For your example to work, it needs:
A valid clients relations, defined as follows:
public function clients()
{
return $this->hasOne(...);
}
clients also needs to be a valid 1:1 always existing relation. i.e. there must always be one client. If there isn't, you are susceptible to the error you just got (as the `clients̀ magic would end up being null)
The same applies to jobs.
In every case, it is better to make sure everything is set first. Check using the following:
$clientJob = ClientJobs::find($i);
if (!$clientJob->clients || $clientJob->jobs) throw new \RangeException("No client or job defined for ClientJob $i");
And then catch the exception at whichever level you prefer.
Best approach
public function index() {
$masterArray = array();
ClientsJobs::with('clients', 'jobs')->chunk(200, function($records) use (&$masterArray) {
foreach ($records as $record) {
$masterArray[] = array(
'id' => $record->id, // id
'client_name' => !empty($record->clients) ? $record->clients->fname : null,
'job_name' => !empty($record->jobs) ? $record->jobs->name : null,
'wage' => $record->wage,
'productivity'=> $record->productivity,
);
}
});
return $MasterArray;
}
Your Approach is very wrong
If you want to return an array you can do like this
$counterino = ClientsJobs::all()->toArray();
This will fetch all rows from the table and the toArray will convert the object into an array
I'm using CakePHP 1.3.8, and I've installed the CakeDC Search plugin. I have a Tutorial model, which is in a HABTM relationship with a LearningGoal model.
I have a search action & view in the Tutorials controller with which I can successfully search fields in the Tutorial model. I'd also like to filter my tutorial search results using LearningGoal checkboxes on the same form. I've tried adding various parameters to Tutorial's $filterArgs and TutorialsController's $presetVars. I've also tried moving the relevant $filterArgs to the LearningGoal model. I have not yet been able to successfully trigger the entry for learning goals in $filterArgs.
I think I must be missing something obvious. Or maybe the Search plugin doesn't support what I'm trying to do. Does anyone know how to use this plugin to search on associated models?
So here's what I've figured out. You can combine what's below with the Search plugin directions to search on related models.
The $filterArgs piece in the Tutorial model must look like this:
var $filterArgs = array(
array('name' => 'LearningGoal', 'type' => 'subquery', 'method' => 'findByLearningGoals', 'field' => 'Tutorial.id'),
);
Here's the supporting function in the Tutorial model:
function findByLearningGoals($data = array()) {
$ids = explode('|', $data['LearningGoal']);
$ids = join(',', $ids);
$this->LearningGoalsTutorial->Behaviors->attach('Containable', array('autoFields' => false));
$this->LearningGoalsTutorial->Behaviors->attach('Search.Searchable');
$query = $this->LearningGoalsTutorial->getQuery('all',
array(
'conditions' => array('LearningGoalsTutorial.learning_goal_id IN (' . $ids . ')'),
'fields' => array('tutorial_id'),
)
);
return $query;
}
In TutorialsController, $presetVars should look like this:
public $presetVars = array(
array('field' => 'LearningGoal', 'type' => 'checkbox', 'model' => 'Tutorial'),
);
And in my search action in TutorialsController, I did this:
$this->LearningGoal = $this->Tutorial->LearningGoal;
The Prg component seems to need that.
I am using CakePHP version 2.X
Every time I come to do this in a project I always spend hours figuring out how to do it using CakeDC search behavior so I wrote this to try and remind myself with simple language what I need to do. I've also noticed that although Michael is generally correct there is no explanation which makes it more difficult to modify it to one's own project.
When you have a "has and belongs to many" relationship and you are wanting to search the joining table i.e. the table that has the two fields in it that joins the tables on either side of it together in a many-to-many relationship you want to create a subquery with a list of IDs from one of the tables in the relationship. The IDs from the table on the other side of the relationship are going to be checked to see if they are in that record and if they are then the record in the main table is going to be selected.
In this following example
SELECT Handover.id, Handover.title, Handover.description
FROM handovers AS Handover
WHERE Handover.id in
(SELECT ArosHandover.handover_id
FROM aros_handovers AS ArosHandover
WHERE ArosHandover.aro_id IN (3) AND ArosHandover.deleted != '1')
LIMIT 20
all the records from ArosHandover will be selected if they have an aro_id of 3 then the Handover.id is used to decide which Handover records to select.
On to how to do this with the CakeDC search behaviour.
Firstly, place the field into the search form:
echo $this->Form->create('Handover', array('class' => 'form-horizontal'));?>
echo $this->Form->input('aro_id', array('options' => $roles, 'multiple' => true, 'label' => __('For', true), 'div' => false, true));
etc...
notice that I have not placed the form element in the ArosHandover data space; another way of saying this is that when the form request is sent the field aro_id will be placed under the array called Handover.
In the model under the variable $filterArgs:
'aro_id' => array('name' => 'aro_id', 'type' => 'subquery', 'method' => 'findByAros', 'field' => 'Handover.id')
notice that the type is 'subquery' as I mentioned above you need to create a subquery in order to be able to find the appropriate records and by setting the type to subquery you are telling CakeDC to create a subquery snippet of SQL. The method is the function name that are going to write the code under. The field element is the name of the field which is going to appear in this part of the example query above
WHERE Handover.id in
Then you write the function that will return the subquery:
function findByAros($data = array())
{
$ids = ''; //you need to make a comma separated list of the aro_ids that are going to be checked
foreach($data['aro_id'] as $k => $v)
{
$ids .= $v . ', ';
}
if($ids != '')
{
$ids = rtrim($ids, ', ');
}
//you only need to have these two lines in if you have not already attached the behaviours in the ArosHandover model file
$this->ArosHandover->Behaviors->attach('Containable', array('autoFields' => false));
$this->ArosHandover->Behaviors->attach('Search.Searchable');
$query = $this->ArosHandover->getQuery('all',
array(
'conditions' => array('ArosHandover.aro_id IN (' . $ids . ')'),
'fields' => array('handover_id'), //the other field that you need to check against, it's the other side of the many-to-many relationship
'contain' => false //place this in if you just want to have the ArosHandover table data included
)
);
return $query;
}
In the Handovers controller:
public $components = array('Search.Prg', 'Paginator'); //you can also place this into AppController
public $presetVars = true; //using $filterArgs in the model configuration
public $paginate = array(); //declare this so that you can change it
// this is the snippet of the search form processing
public function admin_find()
{
$this->set('title_for_layout','Find handovers');
$this->Prg->commonProcess();
if(isset($this->passedArgs) && !empty($this->passedArgs))
{//the following line passes the conditions into the Paginator component
$this->Paginator->settings = array('conditions' => $this->Handover->parseCriteria($this->passedArgs));
$handovers = $this->Paginator->paginate(); // this gets the data
$this->set('handovers', $handovers); // this passes it to the template
If you want any further explanation as to why I have done something, ask and if I get an email to tell me that you have asked I will give an answer if I am able to.