2

I don't truly understand how chaining functions work on the values that are returned.

Let's say I have a function that returns a string or array

public static $query; 

public static function getArray($arr) {
     Database::$query = $arr;
     return Database::$query;
}

public function single() {
    return Database::$query[0];
}

Why, when I call it can I then not chain a function onto this to affect the string (In this example I was to append ' test' and how would I go about doing this?

Why can I simply not call

Database::getArray(array("test","test2"))->single();

Without getting a Call to a member function single() on array error. But instead, make it return only the first value of the array.

How would I go append doing what I'm trying to achieve here? Why is my logic wrong?

tereško
  • 58,060
  • 25
  • 98
  • 150
  • what is `$query`? may need to see your `Database` class – treyBake Jul 10 '18 at 10:46
  • Just a `public static $query;` I'll add it! –  Jul 10 '18 at 10:46
  • Possible duplicate of [Chaining Static Methods in PHP?](https://stackoverflow.com/questions/125268/chaining-static-methods-in-php) – billyonecan Jul 10 '18 at 11:02
  • FYI, static variables are basically namespaced globals and static methods - namespaced global functions. This has nothing to do with OOP. Also, making another "database wrapper" is a really bad idea. – tereško Jul 10 '18 at 11:02
  • @tereško What makes having another 'database wrapper' a really bad idea? –  Jul 10 '18 at 11:14
  • @ConorReid first of all, you end up obscuring and limiting the underlying abstraction: mysql or pdo. You end up loosing the functionality. At the same time, the way you approach it, you are creating a global state, which makes anything it touches untestable by design. You instead should use dependency injection to pass around to instance of pdo or mysqli and isolate the SQL code inside custom-written [data mappers](https://martinfowler.com/eaaCatalog/dataMapper.html). – tereško Jul 10 '18 at 11:19
  • @tereško Meh, I say let people walk before they try to run. A simple database wrapper can be a good way to play with structuring code. Not every project needs a beautiful framework. – IMSoP Jul 10 '18 at 11:26
  • 1
    @IMSoP I have seen too many "senior" developer who still write like that even after a decade of programming. Not pointing out to a newbie, that what he is doing is not actually OOP and that there is a better way, would be a disservice. – tereško Jul 10 '18 at 11:29
  • @tereško I agree regarding static vs OO, but I think introducing Depency Injection, data mappers, and testability, to someone still learning basic language features, is just going to cause confusion. – IMSoP Jul 10 '18 at 11:31
  • He has been lurking around for more than half a year. It's about the time for training wheels to come off. I understand your point, but what I fear is that without additional push he would end up stagnating. Besides, DI is a basic OOP concept and data mappers are one of the easiest-to-understand architectural patterns. – tereško Jul 10 '18 at 11:33
  • @tereško Thank you for mentioning it. You make a valid point and I think it's more important to learn the right way than it is to implement the wrong solution. I'll be taking a look into everything you've mentioned. –  Jul 10 '18 at 12:10

2 Answers2

5

When you call a method, the return value is whatever that method decides to return; the return value doesn't have any automatic relationship with the object you called the method on. For instance:

class A {
    public function foo() {
        return 'Hello, World!';
    }
}
$a = new A;
echo $a->foo();

The value returned is just an ordinary string, just as if foo was a global function not attached to any object.

In PHP, strings (and other "basic types" like arrays) are not objects, so you can't call any methods on them. Even if you could, those methods would be built into the language, and you couldn't just decide that ->single() could be called on any array.

What may be confusing is that some people write methods with the convention that they return an object, known as a "fluent interface", or more generally "chained methods". This is not a feature of the language, just a natural consequence of returning an object from a method:

class A {
    public function foo() {
        return new B;
    }
}
class B {
    public function bar() {
         return 'Hello, World!';
    }
}
$a = new A;
$b = $a->foo(); // $b is a B object
echo $b->bar();
// We can combine this into one line:
echo (new A)->foo()->bar();

There is nothing special about this chaining; it's just that wherever you have an object, you can call appropriate methods on it, just as wherever you have a number, you can do maths with it. Compare with a simple addition:

function foo() {
    return 1;
}
$a = foo();
$a = $a + 2;
echo $a;
// We can combine this into one line:
echo foo() + 2;
// Or keep the assignment:
$a = foo() + 2;
echo $a;

The object doesn't know it's being chained - in fact, it shouldn't need to know anything about the code around it, and that's an important part of structured programming.

A common pattern is then to have modifying methods which return the object they just modified, so you can make a series of modifications in one go:

class A {
     private $words = [];
     public function addWord($word) {
         $this->words[] = $word;
         // $this is the current object, which is an instance of class A
         return $this;
     }
     public function getString() {
         return implode(' ', $this->words);
     }
 }
 $a = new A;
 // Calling $a->addWord(...) gives us back the same object
 $a = $a->addWord('Hello');
 $a = $a->addWord('World');
 // Calling $a->getString() gives us back a string
 echo $a->getString();

 // We can combine this into one line:
 echo (new A)->addWord('Hello')->addWord('World')->getString();

Note that you can only refer to $this if you have created an instance of the object (with the new keyword), not in a method declared as static. A static method can still have this kind of pattern, but it will need to return some other object, like new self (a new instance of the current class) or self::$foo (an object created earlier).

IMSoP
  • 89,526
  • 13
  • 117
  • 169
  • Ok, I understand and have got an example working by returning `new self` Do you know if there's any way to check if the function is being chained? I would like to return a different value if the function isn't being chained. –  Jul 10 '18 at 11:06
  • @ConorReid There is not. As I say, "chaining" is not a feature of the language. Writing `$a = foo(); $a = $a->something();` is exactly the same as writing `$a = foo()->something();` in the same way as writing `$a = 1; $a = $a + 2;` is the same as writing `$a = 1 + 2;`. – IMSoP Jul 10 '18 at 11:24
  • @ConorReid I've added an extra example half-way down my answer to make this clearer. – IMSoP Jul 10 '18 at 11:29
0

it's called fluent interface, if you want to chain methods from same class you have to return this from each of them which you want to call fluently, so your code should look like:

public static $query; 

public function getArray($arr) {
     Database::$query = $arr;
     return $this;
}

public function single() {
    return Database::$query[0];
}

after applying changes, the construct Database::getArray(array("test","test2"))->single(); will work, however you may consider renaming method getArray, because as its name suggests, it shouldn't be returning $this, but array

@EDIT

you should change the type of function getArray from public static function to public function to make it work, also your final statement will change to something like:

(new Database())->getArray(array("test","test2"))->single();

however, in this case, I would consider redesigning your class and creating some kind of singleton so that you instantiate Database class only once and store the object somewhere

bestestefan
  • 852
  • 6
  • 20
  • 2
    how can you return `this` if there's no instance to return? – billyonecan Jul 10 '18 at 10:57
  • @billyonecan yeah, I missed the point it's static function in class, updated answer – bestestefan Jul 10 '18 at 11:00
  • If it wasn't static would I need to create a new Database object in every function I use it so it's in scope? Seems quite messy and unrequired. –  Jul 10 '18 at 11:08
  • @ConorReid that's the whole point of singleton design - create one instance and store it somewhere globally and then access the one and the only instance of the class, that's the only way to go if you want to have fluent interface in your class – bestestefan Jul 10 '18 at 11:09