I have the following classes in my application:
<?php
class Connection extends Mysqli{
public function __construct($mysqli_host,$mysqli_user,$mysqli_pass, $mysqli_db) {
parent::__construct($mysqli_host,$mysqli_user,$mysqli_pass,$mysqli_db);
$this->throwConnectionExceptionOnConnectionError();
}
private function throwConnectionExceptionOnConnectionError(){
if(!$this->connect_error){
echo "Database connection established<br/>";
}else{
//$message = sprintf('(%s) %s', $this->connect_errno, $this->connect_error);
echo "Error connecting to the database.";
throw new DatabaseException($message);
}
}
}
class DatabaseException extends Exception
{
}
class Page extends Mysqli{
private $con;
public function __construct(Connection $con) {
$this->con = $con;
if(isset($_GET['id'])){
$id = $_GET['id'];
}else{
$id = 1;
}
$this->get_headers($id);
$this->get_content($id);
$this->get_footer($id);
}
private function get_headers($pageId){
$retrieveHead = $this->con->prepare("SELECT headers FROM pages WHERE page_id=?");
$retrieveHead->bind_param('i',$pageId);
$retrieveHead->execute();
$retrieveHead->bind_result($header);
$retrieveHead->fetch();
$retrieveHead->close();
echo $header;
}
private function get_footer($pageId){
$retrieveFooter = $this->con->prepare("SELECT footer FROM pages WHERE page_id=?");
$retrieveFooter->bind_param('i',$pageId);
$retrieveFooter->execute();
$retrieveFooter->bind_result($footer);
$retrieveFooter->fetch();
$retrieveFooter->close();
echo $footer;
}
private function get_content($pageId){
$retreiveContent = $this->con->prepare("SELECT template_id, section_title, i1, i2 FROM content WHERE page_id=? ORDER BY sequence DESC");
$retreiveContent->bind_param('i',$pageId);
$retreiveContent->execute();
$retreiveContent->bind_result($template_id, $section_title, $i1, $i2);
while ($retreiveContent->fetch()) {
//Variables will be populated for this row.
//Update the tags in the template.
$template = $this->get_template($template_id);
$template = str_replace('[i1]',$i1,$template);
$template = str_replace('[i2]',$i2,$template);
//$theTemplate is populated with content. Probably want to echo here
echo $template;
}
}
private function get_template($template_id){
$retreiveTemplate = $this->con->prepare("SELECT code FROM templates WHERE template_id=?");
$retreiveTemplate->bind_param('i',$template_id);
$retreiveTemplate->execute();
$retreiveTemplate->bind_result($template);
$retreiveTemplate->fetch();
$retreiveTemplate->close();
return $template;
}
}
?>
The small application should basically get the page variable from the url and use it to pull out the header, content items and footer of the page with the content items being rendered using a template which is also retreived from the database. When running my index file which contains the following code:
require ("/req/db_connection.php");
require ("/req/connection.php");
$dbConnection = new Connection($mysqli_host,$mysqli_user,$mysqli_pass, $mysqli_db);
$page = new Page();
I get the following output:
Database connection established
Warning: mysqli::prepare(): Couldn't fetch Page in C:\xampp\htdocs\forumeq\connection.php on line 39
Fatal error: Call to a member function bind_param() on a non-object in C:\xampp\htdocs\forumeq\connection.php on line 40
Could anyone point in the right direction as to why I am experiencing these errors?
Line 39 is the following statement:
$retrieveHead = $this->prepare("SELECT headers FROM pages WHERE page_id=?");
And line 40 is of course:
$retrieveHead->bind_param('i',$pageId);
Any help would be greatly appreciated.
N.B. The code above has now been corrected based on the answers below however I am still experiencing the following error at line 80:
Call to a member function bind_param() on a non-object in C:\xampp\htdocs\forumeq\connection.php on line 80
Which is this line:
$retreiveTemplate->bind_param('i',$template_id);
Any suggestions?
class Page extends Mysqli{
public function __construct() {
if(isset($_GET['id'])){
$id = $_GET['id'];
}else{
$id = 1;
}
$this->get_headers($id);
$this->get_content($id);
$this->get_footer($id);
}
This code above looks like the problem (amongst others). You extend mysqli, override the constructor and do not call the parent constructor. As a result none of the base mysqli constructor routines are invoked and you are left with no connection. Consider redesigning your objects and pass them your Connection
object as opposed to extending mysqli everywhere. I'd also try to avoid doing all that work in the constructor. For example your page class could look something like:
class Page{
private $con;
public function __construct(Connection $con) {
$this->con = $con;
}
public function get_headers($pageId){
$retrieveHead = $this->conn->prepare("SELECT headers FROM pages WHERE page_id=?");
//...
}
}
When I quipped "amongst others" (no discouragement intended) above I was hinting that -- while the actual cause of your error was the constructor -- there looks to be some confusion in the design of your classes and of OOP concepts in general.
class Connection extends Mysqli{
public function __construct($mysqli_host,$mysqli_user,$mysqli_pass, $mysqli_db) {
parent::__construct($mysqli_host,$mysqli_user,$mysqli_pass,$mysqli_db);
$this->throwConnectionExceptionOnConnectionError();
}
}
Here you have extended mysqli
with your Connection
class, and correctly called the parent constructor and passed all the required parameters to establish a connection. If you instantiate an object of this type $myConnection = new Connection(...)
it can act as your gateway to the database.
Other classes like Page
may need to perform operations on the database and they can do it via Connection
not by extending mysqli
itself. You can pass an instance of Connection
in the Page
constructor to achieve this. So your lines of code here:
$dbConnection = new Connection($mysqli_host,$mysqli_user,$mysqli_pass, $mysqli_db);
$page = new Page();
Would become (after some modification to Page class like shown above):
$dbConnection = new Connection($mysqli_host,$mysqli_user,$mysqli_pass, $mysqli_db);
$page = new Page($dbConnection);
This error message shows up because you haven't established a connection to the database server when you attempt to prepare the statement. A shorter reproduce code:
class Page extends Mysqli{
public function __construct(){
$this->prepare('SELECT CURRENT_TIMESTAMP');
}
}
new Page;
In mysql objects the connection is established in the constructor but you override such constructor and never call the parent one.
IMHO, it makes little sense that pages are instances of the database object. You should simply provide a DB object as parameter when required.
Update:
You can pass the DB object every time you need it:
class Page{
public function foo(Mysqli $db){
$db->prepare('SELECT CURRENT_TIMESTAMP');
}
}
... or store it for later usage:
class Page{
protected $db;
public function __construct(Mysqli $db){
$this->db = $db;
}
public function foo(){
$this->db->prepare('SELECT CURRENT_TIMESTAMP');
}
}
If you extend Mysqli
, use the correct class name as type hint:
public function foo(Connection $db){
}
There're other solutions like interfaces but it gets too complicate :)