tags:

views:

252

answers:

9

PHP mysql database I have created a follow on question to this one here that is specifically about pagination

I need to call a method from one class in another, and be able to change the method that is called. Like so

class db{

    function a(){ echo 'I run a query';}
    function b(){ echo 'im the other query';}

}


class YourClass {

    var $fcn;
   $db = new db()

    function invoke(){
         call_user_func($this->fcn);
    }

} 



$instance = new YourClass;
$instance->fcn = 'db->a';
$instance->invoke();

I want to use a method 'a' from the db class in the 'yourClass' method 'invoke' Thanks

Ok this is what i have put together from the answers provided and it works.

    class A {

    function a(){ 
     $x = 'Method a is used';
     return $x;
     } 
    function b(){ 
     $x = 'Method b is used';
     return $x;
     } 
}

class B {

    function invoke($obj, $method){

     echo call_user_func( array( $obj, $method) );

    }

} 

$instance = new B();
$instance->invoke(new A(),"a");

Which writes, 'Method a is used' to the screen

But i really want to be able to pass arguments to method "a" so i tried the code below.

class A {

    function a($var1,$var2,$var3){ 
     $x = 'the three passed values are  ' . $var1 . ' and ' . $var2 . ' and ' . $var3;
     return $x;
     } 
    function b(){ 
     $x = 'im method b';
     return $x;
     } 
}

class B {

    function invoke($obj,$arguments){

     echo call_user_func_array($obj,$arguments);

    }

} 

$arguments = array('apple','banana','pineapple');
$use_function = array(new A(),"a");

$instance = new B();
$instance->invoke($use_function,$arguments);

It almost works but i get these errors above the correct answer

Missing argument 1 for A::a(),.....for argument 2 and 3 as well but then the answer prints to the screen "the three passed values are apple and banana and pineapple"

I'm probably making a rookie mistake I've been coding all day. If someone could fix the script above and submit the working code, I would be eternally grateful. I have to put this issue to bed so i can go to bed. Thanks

A: 

Are you asking if PHP has functional references? It doesn't. But it does let you call functions by putting their name in a string, or an array of a class name and method name. See call_user_func() for a description, and variable functions.

dlamblin
+2  A: 

Here are two ways of doing it:

class YourClass {
    var $fcn;
    function invoke($arguments){
       //one way:
       $this->{$this->fcn}($arguments);
       //another way:
       call_user_func_array(array($this, $this->fcn), $arguments);
    }
    function a(){ 
       echo 'I am a()';
    }
} 

$instance = new YourClass;
$instance->fcn = 'a';
$instance->invoke();

This'll print out "I am a()" from inside the class.

brianreavis
That almost what i need. If i had a class called class db{ function a(){ echo 'I am a()';}could i do this$db = new db;$instance = new YourClass$instance-fcn = '$db->a';$instance->invoke();?Thanks for the help
andrew
Ahha, I see... fixed :)
brianreavis
yes, but the method 'a' needs to be in an entirely different class
andrew
A: 

I am guessing that you are using php here. Php supports variable functions which might solve you problem but as far as I am aware does not support delegates/function pointers.

What database are you using? I would be against putting queries within the code and using stored procedures as an alternative, if this is supported in the database you are using. This may solve the underlying problem you have.

bobwah
Hi, i am using php and mysql
andrew
MySql supports stored procedure from 5.0 onwards:http://dev.mysql.com/tech-resources/articles/mysql-storedprocedures.htmlHere is an example calling a stored procedure from php which needs you to build php with mysqli:http://www.java2s.com/Tutorial/MySQL/0201__Procedure-Function/CallingastoredprocedureinPHP.htm
bobwah
+7  A: 

As of PHP5.3 you could use closures or functors to pass methods around. Prior to that, you could write an anonymous function with create_function(), but that is rather awkward.

Basically, what you are trying to do could be done with the Strategy Pattern.

removed example code, as it wasnt helpful anymore after the OP changed the question (see wiki)

Apart from that, you might want to look into Fowlers's Data Source Architectural Patterns. The Zend Framework (and pretty much all other PHP frameworks) offers database access classes you could use for these patterns and there is also a paginator class, so why not check them out to learn how they did it.

removed EDIT 1 as it wasnt helpful anymore after the OP changed the question (see wiki)

EDIT 2 Ok, let's take a step by step approach to this (not using a Strategy Pattern though)

What you are asking for in the question can easily be solved with this code:

class Foo
{
    public function bar()
    {
        echo 'bar method in Foo';
    }
}

class MyInvoker
{
    protected $myObject;

    public function __construct()
    {
        $this->myObject = new Foo();
    }

    public function __call($method, $args)
    {
        $invocation = array($this->myObject, $method);
        return call_user_func_array($invocation, $args);
    }
}

With this code you'd just call the appropriate methods. No setting of methods names. No clumsy extra invoke method. No reinventing of how methods are called. You dont need it, because PHP has the __call function that you just taught to send all methods not existing in MyInvoker to $myObject, e.g. Foo.:

$invoker = new MyInvoker;
$invoker->bar(); // outputs 'bar method in Foo called'

You might just as well have extended MyInvoker to be a subclass of Foo, e.g.

class MyInvoker extends Foo {}

and then you could do the same. This not what you need though and it illustrates how pointless it is, to do such a thing. MyInvoker now does nothing by itself. It is an empty class and effectively the same as Foo. Even with the previous approach using the __call method it is not doing anything. This is why I have asked you to be more specific about the desired outcome, which is a Paginator.

First try:

class Paginator()
{
    // A class holding all possible queries of our application
    protected $queries;

    // A class providing access to the database, like PDO_MySql
    protected $dbConn;

    public function __construct()
    {
        $this->db      = new MyPdo();
        $this->queries = new DbQueries();
    }

    public function __call($method, $args)
    {
        $invocation = array($this->queries, $method);
        $query      = call_user_func_array($invocation, $args);
        return $this->dbConn->query($query);
    }
}

With that code, our Paginator creates everything it needs inside itself, tightly coupling the db connection class and all queries and it allows you to call upon these, like so

$paginator = new Paginator;
// assuming we have something like getImageCount() in DbQueries
echo $paginator->getImageCount();

What is happening then is, Paginator will recognize it doesnt know getImageCount() and will invoke the __call method. The __call method will try to invoke the getImageCount() method on the DbQueries. Since it exists, it will return the query, which in turn is passed to the db connection to execute it. Great you'd say, but it's not. In fact, this is horrible. Your paginator's responsibility is to count items in a table and fetch items from this table in a certain range and amount. But right now, it is not doing anything like this. It is completely oblivious to whats going on, so lets try a new class:

class Paginator
{
    protected $dbConn;
    protected $itemCount;

    public function __construct($dbConn)
    {            
        $this->dbConn = $dbConn;
    }

    public function countItems($query)
    {
        $this->itemCount = $this->dbConn->query('select count(*) from (?)', $query);
        return $this->itemCount;
    }

    public function fetchItems($query, $offset = 0, $limit = 20)
    {
          $sql = sprintf('select * from (?) LIMIT %d, %d', $offset, $limit);
          return $this->dbConn->query($sql, $query);
    }
}

Much better. Now our Paginator is an aggregate instead of a composite, meaning it does not instantiate objects inside itself, but requires them to be passed to it in the constructor. This is called dependency injection (and also provides a loose coupling, when dbConn uses an interface) which will make your app much more maintainable, as it is easy to exchange components now. This will also come in handy when Unit Testing your code.

In addition, your Paginator now concentrates on what it is supposed to do: counting and fetching items of an arbitrary query. No need to pass methods around. No need for obscure method invocation. You'd use it like this:

$paginator = new Paginator($dbConn);
$query     = $dbQueries->findImagesUploadedLastWeek(); // returns SQL query string
$images    = $paginator->countItems($query);
if($images > 0) {
    $images = $paginator->fetchItems($query);
}

And that's it. Well, almost. You'd have to render the pagination of course. But this should be rather trivial, if you extend what you already have above. The $imageCount property is a hint at where to go next.

Anyway, hope that I could shed some light.

P.S. The $this->dbConn->query($sql, $query) calls are of course dummy code. Dont expect to be able to copy and paste it and get it working. In addition, you should make sure the queries added to the Paginator SQL is safe to use. You wouldnt want someone to insert a query that deletes all your db rows. Never trust user input.

P.P.S. $query should be an SQL query string. Check the PHP manual for PDO::prepare. In general, it yields better performance and security to prepare a statement before executing it. The page in the manual will give you the clues about the ? in the query calls. If you dont want to use PDO, just use sprintf() or str_replace() to replace ? with $query, e.g. $this->dbConn->query(sprintf('SELECT count(*) from (%s)', $query) but keep in mind that this has none of the benefits of a prepared statement and potentially opens the door for SQL Injection vulnerabilities.

P.P.P.S Yes, Dependency Injection is generally a preferred strategy. This is an advanved topic though and might be too much to fully grasp right now, but it's well worth looking into it. For now, it should be enough if you try to favor favor aggregation over composition. Your classes should only do what they are responsible for and get any dependencies through the constructor.

Gordon
sorry about that but i wasn't getting the answers i wanted.
andrew
Well, and now you didnt get the answers you needed ;) Really, it is pointless to do `$instance->invoke('method')` when you just `call_user_func` the method to an object inside $instance. You could just as well use `__call()` then and put the `invoke()` code in there. Then you could do $instance->a(). This is typical for decorators and adapters. The big question is, what is the purpose of YourClass.
Gordon
Check Edit2 for some more code.
Gordon
Thanks that looks like what i need, now i just have to try and implement it.
andrew
Im learning a lot here. Is dependency injection in general a preferred strategy. For example, I have an Authentication class which requires a database connection. Is it better to instatiate my database class $db = new db(localhost,...) and then pass the authentication class my database object $auth = new Authentication($db) instead of creating an instance of $db inside the authentication class?What if i created a db class in auth but only made a connection if one had not already been made?Thanks
andrew
I don't understand what you are doing by setting $query = = $dbQueries->findImagesUploadedLastWeek(); and then passing it to the method count items. Is it somehow related to the (?) that is in the query string. And its used again for fetch items. What sort of info would findImagesUploadedLastWeek() return?
andrew
I need a bit more explanation of the part where you pass %query when you run the query. I think this is related to the question mark but i can't quite figure it out. Would $query be equal to something like 'table_name'? Is this somehow substituted into the postion of the question mark
andrew
Check PPS and PPPS for additional explanation. I have also added some more links and comments throughout the post. If any of those things is too hard to grasp right now, leave it for later. Just adapt what is already there to get your paginator done.
Gordon
A: 
class A {
   function a(){ echo 'I run a query';}
   function b(){ echo 'im the other query';}
}

class B {

    function Test() {
        invoke(new $A(), "a");
    }

    function invoke($obj, $method){
        //  Non static call
        call_user_func( array( $obj, $method ) );

        //  Static call
        //call_user_func( array( 'ClassName', 'method' ) );
    }
}

I hope this helps. Remember to check this as the right answer.

Colour Blend
could you make this a complete example this is nearly the best example, sorry I'm just learning php i need is spelled out for me
andrew
I made another post. Let me know if that's what you want.
Colour Blend
+1  A: 

you are almost there

 class db {
  function a(){ echo 'I run a query';}
  function b(){ echo 'im the other query';}
 }


 class YourClass {
  var $fcn;
  function __construct() {
    $this->db = new db();
  }
  function invoke() {
    call_user_func(array(
    $this->{$this->fcn[0]}, 
    $this->fcn[1]
   ));
  }

 }

 $instance = new YourClass;

 $instance->fcn = array('db', 'a');
 $instance->invoke();
 $instance->fcn = array('db', 'b');
 $instance->invoke();

the syntax is quite fancy, but it works

// edit: from your comment it looks like the simplest option is to pass method name as string, like this

 class Paginator {
      function __consturct($db, $counter_func) ...

      function get_count() {
            $args = func_get_args();
            return call_user_func_array(
                  array($this->db, $this->counter_func),
                  $args);
      }
 }

 new Paginator($db, 'get_num_products');
stereofrog
Is there a cleaner way to do it? Colour Blend's answer is fairly simple.
andrew
i'm not quite sure what are you trying to do here. A real-life example would be helpful. Most probably, there is a simpler way.
stereofrog
I have a database class (db) which contains functions such as get_num_images(). I want to use functions from my db class in my paginator class AND i want to be able to change the function that the paginator class uses. So in some situations i will ask paginator class to use db->get_num_images() and other times tell it to use db->get_num_products.
andrew
Have a look at the original question, i have updated it with more detail
andrew
A: 
class DB {
   function a(){ echo 'I run a query';}
   function b(){ echo 'im the other query';}
}

class B {

    protected $db;
    private $method;

    function __constructor($db) { $this->db; }

    function invoke($m){
        $this->method = $m;
        //  Non static call
        call_user_func( array( $this->db, $this->method ) );
    }
}

$db = new DB();

$b = new B($db);
$b->invoke('a');

I have made little modifications to my initial answer. You could also check out this post, it may help:

http://stackoverflow.com/questions/1716652/database-and-oop-practices-in-php/1716908#1716908

Colour Blend
A: 

You need to make this change:

$arguments = array('apple','banana','pineapple');
$a = new A();
$use_function = array(&$a,"a"); // Make this changes to your code

$instance = new B();
$instance->invoke($use_function,$arguments);

Please remember to tick this as the right answer if it helps.

Colour Blend
A: 

The Observer Design Pattern may be useful for this sort of thing, or it might be a misuse of the pattern; I don't know yet. Anyway, for your consideration:

class DbObserver implements SplObserver
{
   public function update(SplSubject $subject)  // Required
   {
       $method = $subject->getObserverMethod();
       $args   = $subject->getObserverArgs();
       $this->$method($args);
   }

   private function a($args) 
   {
       echo 'I run query ' . $args[0] . '<br />';
   }

   private function b($args)
   {
       echo 'I run query ' . $args[0] . ' because ' . $args[1] . '<br />';
   }

   private function c() 
   {
       echo 'I have no argument' . '<br />';
   }
}

class ObserverObserver implements SplObserver
{
    public function update(SplSubject $subject)  // Required
    {
        if (count($subject->getAttached()) > 1) {
            echo 'I saw that<br />';
        } else {
            echo 'Nothing happened<br />';
        }
    }
}

class DbSubject implements SplSubject
{
    private $observerMethod;
    private $observerArgs = array();
    private $attached     = array();

    public function notify()  // Required
    {
        foreach ($this->attached as $each) {
            $each->update($this);
        }
    }

    public function attach(SplObserver $observer)  // Required 
    {
        $this->attached[] = $observer;
    }

    public function detach(SplObserver $observer)  // Required
    {
        $key = array_keys($this->attached, $observer);
        unset($this->attached[$key[0]]);
    }

    public function setObserverMethod($method, $args = array())
    {
        $this->observerMethod = $method;
        $this->observerArgs = $args;
        return $this;
    }

    public function getObserverMethod()
    {
        return $this->observerMethod;
    }

    public function getObserverArgs()
    {
        return $this->observerArgs;
    }

    public function getAttached()
    {
        return $this->attached;
    }
}

$db_subj = new DbSubject;
$db_obs  = new DbObserver;
$db_subj->attach($db_obs);
$args = array('A');
$db_subj->setObserverMethod('a', $args)->notify();
$args = array('B', 'I can');
$db_subj->setObserverMethod('b', $args)->notify();
$obsvr = new ObserverObserver;
$db_subj->attach($obsvr);
$db_subj->setObserverMethod('c')->notify();
$db_subj->detach($db_obs);
$db_subj->notify();

/**
I run query A
I run query B because I can
I have no argument
I saw that
Nothing happened
**/
GZipp