3

The question I originally had has been asked (and answered) several times before1,2,3,4 : how do I call a variable defined in a different function in the same class - but couldn't get the following to work:

class my_model extends CI_Model {

  public $myvar;

  public function test1()
  {
    $this->myvar = "Hello world";
  }

  public function test2()
  {
    return strtoupper($this->myvar);
    //And have tried:
    // return my_model::test1()->$myvar
  }

}

Changing test1 to act as a setter function (and calling that function to access the variables) does work; changing my test1 to:

public test1()
{
  $this->myvar = "Hello world";
  return $this->myvar;
}

and test2 to:

public function test2()
{
  $a = $this->test1();
  return strtoupper($a);
} 

This question has a comment that controller methods in CodeIgniter work slightly differently from regular PHP class methods. Is this also the case for model classes: if so is this the reason I need to call test1 first (and that variables in other methods aren't accessible), or is there something else I've failed to understand?!

Real life example

This is what I'm actually trying to do - please tell me if there's a better way to do it!

I was going to split up the methods which run a select * query and a query which returns a count, and since I'm using CI's QueryBuilder class this seemed the obvious solution:

class my_model extends CI_Model {

  public function __construct()
  {
    $this->load->database();
  }

  public function test1()
  {
    return $this->db->get('Table1');
  }

  public function test2()
  {
    return $this->test1()->result_array();
  }

  public function test3()
  {
    return $this->test1()->num_rows();
  }
}

However, cale b made a comment that this might feel a bit jenky - how else would it be done?

Community
  • 1
  • 1
ChrisW
  • 4,970
  • 7
  • 55
  • 92

3 Answers3

2

cale_b provides you with the correct answer. My intent here is to say the same thing in a different way.

Your Class

class my_model extends CI_Model
{
  public $myvar;

  public function test1()
  {
    $this->myvar = "Hello world";
  }

  public function test2()
  {
    return strtoupper($this->myvar);
  }
}

Here you MUST call test1() before you call test2() because $myvar has not been given a value (set equal to something) until test1() is executed.

There are multiple ways to accomplish the task of giving $myvar a value.

Set a default value when it is declared

class my_model extends CI_Model
{
  public $myvar = 'Hello World';

  public function test1()
  { 
    ...

In the next example $myvar is set in the class constructor which works more or less exactly the same as the above. I also declare 'setter' and 'getter' functions. Notice I made $myvar private for reasons that will be illustrated in the usage example.

class Test_m extends CI_Model
{
  private $myvar;

  public function __construct()
  {
    parent::__construct();
    $this->myvar = "Hello world";
  }

  //a 'setter'
  public function set_myvar($newValue)
  {
    $this->myvar = $newValue;
  }

  //a 'getter'
  public function get_myvar()
  {
    return strtoupper($this->myvar);
  }

}

Now for a usage example. Here's a controller

class Teststuff extends CI_Controller
{

  public function __construct()
  {
    parent::__construct();
    $this->load->model('test_m');
  }

  function index()
  {
    echo $this->test_m->get_myvar()."<br>";  //outputs HELLO WORLD

    //change the value of $myvar
    $this->test_m->set_myvar("Hello Stackoverflow");
    echo $this->test_m->get_myvar()."<br>";  //outputs HELLO STACKOVERFLOW

    //try to access $myvar directly
    echo $this->test_m->myvar;
    //produces a PHP error "Undefined property: Teststuff::$myvar"
  }
}

The error is because $myvar is private. Change the declaration in Test_m to public $myvar; and the output will be 'Hello Stackoverflow'.

Again, this restates what cale_b stated and he deserves the accepted answer.

DFriend
  • 8,869
  • 1
  • 13
  • 26
1

There may be some misunderstanding of terminology.

In your first code example, test1 is a setter. All a setter is supposed to do is set a class variable - potentially accepting the value, if desired. Setters do not necessarily return anything.

Example:

// This is an example of a setter
$this->setMyVar( $var ) {
    $this->myVar = $var;
}

In your second code example, you've actually caused test to be both a setter and a getter - first it sets the value, then returns the value:

// This is an example of a setter AND a getter in one
$this->setMyVar( $var ) {
    $this->myVar = $var;
    return $this->myVar;
}

Now, back to your first example, and your question:
As you stated - the reason it does not work is because the getter method (test2) attempts to get a value from the class variable, but the class variable has not yet been set.

So, the class variable must be set first, somehow, somewhere.

So, to modify your first sample code so that it WOULD work, you would make one of four possible changes:

Option 1:
Declare your variable with the desired value in the top of the class: public $myvar = 'Hello World';

Option 2:
Before calling your getter (elsewhere in your code), call your setter first:

// Where used inside of some other function where you need my_model->myval
my_model->test1();
$value = my_model->test2();

Option 3:
Set the value in a constructor. Adding this code to the top of the class will cause the variable to be immediately populated when the class is constructed:

public function __construct() {
    $this->myvar = 'Hello World';
}

Option 4: Or, call your setter from within your getter. This feels jenky, but may be the right solution in some circumstances:

 public function test2() {  
    // First call test1 to set the variable
    $this->test1();
    // Now $this->myvar has a value, so return it
    return $this->myvar;
 }

As a side-note, as someone who has had to maintain code that was written years ago, I can attest to the value of naming functions in descriptive / meaningful ways. Calling them some variation of set and get, along with a meaningful variable name, helps troubleshooting later.

Made-up example:

class my_model extends CI_Model {

    // Flag for debug.  When true, output debug messages
    public $debug;

    public function setDebug( $debug = TRUE ) {
        $this->debug = $debug;
    }

    public function getDebug() {
        return $this->debug;    
    }

    // ... other class methods here ...
}

EDIT
Based on revisions to the question, I'm offering these (potential) adjustments to the added code in the question:

class my_model extends CI_Model {

  private $results = FALSE;

  public function __construct()
  {
    $this->load->database();
  }

  private function load_results()
  {
    // Could use or remove the logic here, if data remains constant across a single page load keep it, if not, remove it
    // Only hit the database if the $results haven't yet been loaded
    if ( $this->results === FALSE ) {
        $this->results = $this->db->get('Table1');
    }
  }

  public function test2()
  {
    $this->load_results();
    return $this->results->result_array();
  }

  public function test3()
  {
    $this->load_results();
    return $this->results->num_rows();
  }
}
random_user_name
  • 25,694
  • 7
  • 76
  • 115
  • Thanks for the explanations (I'm primarily a python guy - and python doesn't tend to need setters / getters)! I still don't follow why the examples in the links I provided in my question (e.g http://stackoverflow.com/a/2483693/889604) work - this answer `echo`s the variable that is set in another function in the same class without declaring it at the top of the class or without using a getter first – ChrisW Jan 01 '16 at 18:02
  • The key is in the OP's statement at the end of the question you referenced: "in the above example I want to call $var which is a variable **that was defined inside a previous function.** This doesn't work though, so how can I do this?" - the variable had been defined in a previous function. So, presumably in the OP's code, that "setter" function is getting called somewhere else already. – random_user_name Jan 01 '16 at 18:04
  • Eek, sorry - I think I've just realised what's going on in that post - the getters in the first class are being called in the second class *after* they are created (by the `create` class being called). So - I guess I'm right in saying that the only way way to call variables defined in the same class is to use setter/getter methods? I've also updated my question with what I'm really trying to do - is this an acceptable use of your Option 4, or is there a better way? – ChrisW Jan 01 '16 at 18:21
  • 1
    Your revised code is probably fine, I might tweak it a bit, but depends on specifics. For example - is the data expected to change every call? Or is it going to be relatively static? Are your test2 and test3 functions going to get called multiple times in one page load, or just once? (For example, if you are going to call test2 and test3 in the same page load, then there is value in putting the results in the class variable - unless the results is expected to be very large - rather than re-hit the database (I'm unsure if CodeIgniter has db caching or not, which might make it OK) – random_user_name Jan 01 '16 at 18:33
  • Thanks for the addition to the answer! It's really helpful to see better practices than I could ever think of :) – ChrisW Jan 02 '16 at 19:07
0

What you failed to understand is that in the first example you don't run test1() so the myVar property stays empty, as result you get an empty string when test2() is called.

Here's how it should work:

class my_model extends CI_Model {

    public $myVar;

    public function test1()
    {
        $this->myVar = "Hello world";
    }

    public function test2()
    {
        $this->test1();
        return strtoupper($this->myVar);
        //And have tried:
        // return my_model::test1()->$myVar
    }

}

Notie 2 things: I have corrected public $myVar to be correctly initialized and added $this->test1() to call the setting method before returning the set up property.

UPDATE

A better approach to reduce the number of database and method calls would be:

class MY_Model extends CI_Model {

  public function __construct()
  {
    $this->load->database();
  }

  public function test1()
  {
    return $this->db->get('Table1');
  }

  public function test2()
  {
    // Fetch the database and put the result into a test1 object
    $test1 = $this->test1();
    // Get the results array from the test1 object into an array
    $results['array'] = $test1->result_array();
    // Also get the number of rows value from the test1 object
    $results['num'] = $test1->num_rows();
    // Return the results array which contains an 'array' and 'num' indexes
    return $results;
  }
}

In this example, the test1 variables calls $this->test1() method a single time, which returns database results in object form. Then, we simply use the test1 object to get the results in array form and count the rows. Finally, we return the $results array from test2() which contains 'array' and 'num' indexes.

From our controller, we can call our model and access returned values as such:

// Load our modal and connect to database
$this->load->model('my_model', 'model', TRUE);
// Perform user registration with form supplied data
$test2 = $model->model->test2();

print_r($test2['array']);
echo $test2['num'];

In total, we did 1 database call and 2 model method call, instead of 2 database calls and 3 modal method calls. I hope this answers the core goal of your question.

Achraf Almouloudi
  • 756
  • 10
  • 27
  • I'm not sure this actually answers my question - it just repeats the second half of my question that shows that it does work with a `setter` method. It also conflicts with [this answer](http://stackoverflow.com/a/2483693/889604) - so who is (more) correct?! – ChrisW Jan 01 '16 at 14:16
  • I have explained why the first approach you've shown in your question doesn't work, you haven't provided enough details to help you in what you're trying to do. It is very likely you're doing it the wrong way. – Achraf Almouloudi Jan 02 '16 at 14:01
  • 1
    @ErenArdahan We are using PHP here, and he returned using the strtoupper() function, which according to PHP documentation, returns a string, even if the input variable is non-existent, null or none. – Achraf Almouloudi Jan 02 '16 at 18:18
  • Haha, that's fine! I'd really appreciate an upvote to my answer. – Achraf Almouloudi Jan 02 '16 at 18:32
  • @AchrafAlmouloudi I thought my question showed that I'd realised that PHP needed setters/getters (which I find slightly odd, coming from a python background... is this all you were trying to say or have I missed something?), and I did update my question with my specific question. Am I doing it the wrong way? – ChrisW Jan 02 '16 at 19:04
  • Not strictly needed, but in your case the myVar property is set by a "setter" you made, which is the test1() method. You could just set the property inside test2() and it will work, but I followed your example to show the need to call the "setter" method before using that property. Have a look at the __get() and __set() magic methods in PHP which somewhat help automate the setting/getting of new properties. – Achraf Almouloudi Jan 02 '16 at 19:19
  • @ChrisW I have posted an update to my answer explaining a better and less method and database call intensive approach, check it out. – Achraf Almouloudi Jan 05 '16 at 16:58