How does this Singleton Class work?

I've been able to successfully incorporate a Singleton class into my app and am now using that to establish my database connection.

But while it's working, I want to know WHY it works. Below is a breakdown of what I understand combined with some questions. If you could "fill in the gaps," that would be awesome!

First of all, I'm going to use the Scople Resolution Operator to call what I know to be a static method from the "DB" class tht is also protected...

$display_page=DB::query("SELECT * from employers where id=?", [$id]);

Open in new window


Here's the private static function that's beeing called:

private static function query($statement, $bindParams=NULL, $assoc=NULL)

Open in new window


 Because its private, the, __callStatic method is going to be called which looks like this:
 
 
public static function __callStatic($name, $args=NULL){
        if(!self::$conn){
            // connect here

			self::$conn = new PDO("mysql:dbname=adsf; host=adsf", "adsf", 'adsf');
			self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			}
    
        return call_user_func_array('self::'.$name, $args);
    }

Open in new window

     
But here's where I'm getting confused.

If I understand things correctly, "$name, $args=NULL" is my query ("SELECT * from employers were id=?") and my [$id], correct?

...and then when you get to return call_user_func_array('self::'.$name, $args), 'self::'.$name is my $statement and the $args is my [$id'] value.

So how does $conn get included / implemented?

From what I understand, __calStatic is going to be called automatically when "private static function query" is called. I see how the "($name, $args=NULL)" dynamic places those two entities in a spot where they can be acted upon, but how does $conn get established?

Thanks!
brucegustPHP DeveloperAsked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Chris StanyonWebDevCommented:
If you take a look at the __callStatic function, it has this at the start:

if(!self::$conn) {
    self::$conn = new PDO("mysql:dbname=adsf; host=adsf", "adsf", 'adsf');
    self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}

Open in new window

When the function is called, it checks whether $conn is true or false (has been set to a value or hasn't been set to a value). If it hasn't been set, then it sets it by creating a new PDO connection. Now the next time the callStatic is called, it will skip this creation because !self::$conn will return false, having already been set. It then goes on to run the function defined in the $name argument.

With regard to the args, you're slightly off. When you call:

DB::query("SELECT * from employers where id=?", [$id]);

This becomes:

$name = "query";
$args = array(
    "SELECT * from employers where id=?",
    array( $id );
);

So when it then calls call_user_func_array('self::'.$name, $args);

what it's actually doing is calling

self::query($args);

Which in turn is run as

self::query("SELECT * from employers where id=?);", [$id]);

And I'm guessing that the query() function will then use the static self::$conn that was set when the callStatic function was run for the first time
0
brucegustPHP DeveloperAuthor Commented:
Chris!

This is exceptional!

Here's the complete "database.php" file. This is the Singleton Class that's calling my database. Take a look at lines #16 and #13 which will lead to the final question I've got at the bottom of this post:

class DB {
   
   private static $conn; //here's your $conn property

    public static function __callStatic($name, $args=NULL){
        if(!self::$conn){
            // connect here

			self::$conn = new PDO("mysql:dbname=test; host=localhost", "test", 'test);
			self::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
			}
    
        return call_user_func_array('self::'.$name, $args); /here is where you included some inspired commentary, but this is where I have my final question. Please refer to bottom of my post...
    }
	
	private static function query($statement, $bindParams=NULL, $assoc=NULL) //this is what's being initially called by the user. Because it's a private funcdtion, the "__callStatic" is going to be automatically triggered
	{
		if(isset($bindParams))
		{
			$sql=self::$conn->prepare("$statement");
			$sql->execute($bindParams);
		}
		else
		{
			$sql=self::$conn->prepare("$statement");
			$sql->execute();
		}
		
		$count=$sql->rowCount();
		
		if($count>0)
		{
			if($assoc==NULL)
			{
				$result=[];
				while($row=$sql->fetch(PDO::FETCH_ASSOC))
				{
					$result[]=$row;
				}
				return $result;
			}
			else
			{
				$row=$sql->fetch(PDO::FETCH_OBJ);
				return $row->$assoc;
			}
		}
		else
		{
			return 0;
		}	
	}
}

Open in new window


Bottom line: Where is the query actually being executed?

The "__callStatic" is instantiating the database connection. I get that. Thanks to your wonderful commentary, I understand the structure of the "return call_user_func_array('self::'.$name, $args);" dynamic. But what is actually being returned?

The reason I'm hesitant is because if you look at lines #20-#21, you have something that almost seems a little redundant. I know I'm "off," but if the final result of "return call_user_func_array" is..

self::query("SELECT * from employers where id=?);", [$id]);

How does that "fit" with...

$sql=self::$conn->prepare("$statement");
$sql->execute($bindParams);

Thanks!
0
Chris StanyonWebDevCommented:
Right.

In __callStatic, you have this line:

return call_user_func_array(...);

So it will return whatever the function you're calling returns. As we've previously discussed, in your case, it will return whatever query($statement, $args) returns. In simple terms, something like this:

function Static() {
    return Query();
}

private function Query() {
    return "Hello";
}

Open in new window

You can see from that by calling Static, it returns "Hello".

Now if we look at the query() function itself. It takes the $statement, plus a couple of other optional parameters - $bindParams and $assoc. So by using the magic static, the function actually get's called like so:

query( "SELECT * from employers where id=?);", [$id] );

From that we can see that $statment is "SELECT..." and $bindParams is an array with a single value - $id.

These days, we use prepared statements and parameters to run queries because it's safer. The simple overview is that you prepare a query with (optional) placeholders (indicated by question marks), and then we execute that statement, passing in the data for those placeholders, if needed, as an array:

$myDb->prepare("SELECT firstname, lastname FROM people WHERE firstname = ?");
$myDb->execute( array("Chris") );

This is equivalent to running:

"SELECT firstname, lastname FROM people WHERE firstname = 'Chris'

Now if we look at lines 18-27 of your code - it's basically saying - do we need to send data with the query or not? If there's nothing passed into $bindParams, then we just call execute(). If we do have data, we need to call execute($bindParams). 2 quick examples:

$db->prepare("SELECT * from employers");
$db->execute();

$myDb->prepare("SELECT * from employers where id=?");
$myDb->execute( array(123) );

Open in new window

Technically, there is a little redundancy in the code, because you could write it as:

$sql=self::$conn->prepare("$statement");

if(isset($bindParams)) {
    $sql->execute($bindParams);
} else {
    $sql->execute();
}

Open in new window

After your query has executed, it checks to see how many records the resultset holds. If there aren't any (such as a DELETE / INSERT / UPDATE or empty SELECT) then it just returns 0. If there are records, then line 33-46 decides what to return. Line 35-40 loops over the records, adding each one as an assoicated array to an array called $result (personally I'd recommend fetching PDO::FETCH_OBJ instead of PDO::FETCH_ASSOC) . It returns $result, which will then be returned by your static call, and you'll finally get back an array containing all your records.

Line 44-45 have a different effect. If you passed something into the $assoc parameter, then it expects that to be a column name from your SELECT query. It will retrieve a single value from a column that was specified by the $assoc parameter. That part feels a little odd to me and the parameter name doesn't fit with what the code is doing. It should probably be called $columnName or something. It doesn't loop records either, so it will only pull the data from the first record in the query. Basically, you would need to call like this:

$firstname = DB::query("SELECT * from employers where id=?", [$id], 'firstname');

That would return the value from the firstname column of the first record where id matched $id.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
brucegustPHP DeveloperAuthor Commented:
Chris, it's not lost on me that your explanation could've been far less detailed, but you took the time to explain both the "what" and the "why" and that's how I'm wired, friend. Thank you very much!

The $20,000.00 comment that you made (and I'm explaining this back to you) is, "...it will return whatever the function you're calling returns." That was the "thing" that allowed of this to make sense.

In short, the "call_user_fun_array" is not returning a mere database connection. Rather, it is absorbing the entire mechanical workings of the "query" method from lines #16-#51 and publishing the result. The "query" method is DOA because it being private. But the "__callStatic,' being present, is automatically invoked and because it includes the needed database connection, the sql as well as all of the parameters that are being passed into the "__callStatic" method can be processed as a legitimate database interaction and although the user is instantiating the "query" method, the actual data that's being returned is from the "__callStatic" method that's operating behind the curtain.

Correct?

Thanks again!
0
Chris StanyonWebDevCommented:
Hey Bruce,

Yep - that's about the size of it :)

Glad it all made sense - it was quite a long post and a lot to take in !!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.