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
Best Solution
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 wasn't 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 wasn't 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:
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.:
You might just as well have extended MyInvoker to be a subclass of Foo, e.g.
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:
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
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:
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:
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 usesprintf()
orstr_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.