So I have been doing some OOP and I hit a snag. I'm trying to extend a class in another PHP file. The extended classes should load dynamically. I got it set up like this:
bootstrap.php file:
<?php
class bootstrap{
public $lang;
public $page;
public $action;
public $id;
public $message;
public function __construct() {
$this->buildPage();
}
public function buildPage() {
if($this->page == 'home'){
require_once CONTR . 'view.php';
new view($this->lang, $this->page, $this->action, $this->id, $this->message);
}
}
}
and the view.php file:
class view extends bootstrap{
public $lang;
public $page;
public $action;
public $id;
public $message;
public function __construct($lang, $page, $action, $id, $message) {
parent::__construct();
$this->lang = $lang;
$this->page = $page;
$this->action = $action;
$this->id = $id;
$this->message = $message;
$this->showPage();
}
public function showPage() {
require_once VIEW . 'header.php';
//echo VIEW . 'header.php';
//prepare SQL statements
$page = $this->page;
$name = "name_" . $this->lang;
//SQL statement
$selectdata = mysql_query("SELECT * FROM pages WHERE $name = '$page'");
$result = mysql_fetch_array($selectdata);
//Echo content
if($result["content_$this->lang"] == NULL){
echo "no content";
} else {
echo $result["content_$this->lang"];
}
//Debugging
echo '<br>' . $this->lang;
echo '<br>' . $this->page;
echo '<br>' . $name;
echo '<br>' . $page;
require_once VIEW . 'footer.php';
}
}
--> Index.php
require('front/config.php');
require('front/bootstrap.php');
$site = new bootstrap();
After doing this I tried to call the view class through the bootstrap class by using extends.
I have been trying lots of different things but I think I am quite close. Does anyone have an idea how to solve this one?
The showpage function does actually work when I put it in the bootstrap class, but when I put it in the view class, nothing works.
Thanks in advance!
Related
I'm creating a simple PHP MVC system based on the "original" MVC principle, that is...
Controller instructs the model based on user input / queries and loads view
Model is a data layer is is unaware and independant from the controller or view
View creates own copy of data from Model for presentaion purposes
Everything I've done so far conforms to these principles, however, I've stumbled upon a problem as follows...
I set a default page title in the parent model
I inform the model to change its page title value using a model method based on the user page request in the correct controller method (index_controller/index)
I make a copy of models page title in the home view to b used on the template.
The problem is, the page title isnt updated because im setting the view variable in the index view constructor which happens before the model can be updated by the controller.
I can get the correct view variable by fetching and assigning it just before load the template, but the template loading is done in the parent view class which is an issue because different views require different variables.
Please let me know what I'm doing wrong! Any help is much appreciated!
It might help to see some of thecode in question...
lib/bootstrap.php
class Bootstrap {
function __construct() {
// Gets the request URL and seperate into array
$this->url = $this->getUrlArray();
// Generates the controller name based on URL array
$this->controller_name = $this->getControllerName();
// Loads controller file and creates new controller object
$this->controller = $this->getController();
// Calls requested methods based on URL array
$this->callMethods();
}
function getUrlArray() {
$url = isset($_GET['url']) ? rtrim($_GET['url'], '/') : 'index';
$url = explode('/', $url);
return $url;
}
function getControllerName() {
return $this->url[0] . '_controller';
}
function getController() {
$file = 'controllers/' . $this->controller_name . '.php';
if(file_exists($file)) {
require $file;
return new $this->controller_name($this->url[0]);
} else {
require 'controllers/error_controller.php';
return new Error_Controller('error');
}
}
function callMethods() {
if(!isset($this->url[1])) {
$this->controller->index();
} else {
$method_name = $this->url[1];
if(method_exists($this->controller, $method_name)) {
if(!isset($this->url[2])) {
$this->controller->$method_name();
} else {
$this->controller->$method_name($this->url[2]);
}
} else {
$this->controller->index();
}
}
}
}
lib/controller.php
class Controller {
protected $model;
public function __construct($name) {
$this->base_name = $name;
$this->model_name = $this->getModelName();
$this->model = $this->getModel();
$this->view_name = $this->getViewName();
$this->view = $this->getView();
//$this->creatView();
}
public function getModelName() {
return $this->base_name . '_model';
}
public function getViewName() {
return $this->base_name . '_view';
}
public function getModel() {
$file = 'models/' . $this->model_name . '.php';
if(file_exists($file)) {
require $file;
return new $this->model_name();
} else {
die('ERROR: This page is missing a model!');
}
}
public function getView() {
$file = 'views/' . $this->view_name . '.php';
if(file_exists($file)) {
require $file;
return new $this->view_name($this->model);
} else {
die('ERROR: This page is missing a view!');
}
}
}
lib/model.php
class Model {
public function __construct() {
$this->page_title = SITE_NAME;
}
public function setPageTitle($pre, $seperator) {
$this->page_title = $pre . ' ' . $seperator . ' ' . SITE_NAME;
}
}
lib/view.php
class View {
protected $model;
public function __construct(Model $model) {
$this->model = $model;
}
public function output($name, $noInclude = false) {
$file = 'templates/' . $name . '.php';
if(file_exists($file)) {
if($noInclude) {
require 'templates/' . $name . '.php';
} else {
require 'templates/header.php';
require 'templates/' . $name . '.php';
require 'templates/footer.php';
}
} else {
die('ERROR: This page is missing a template!');
}
}
}
controllers/index_controller.php
<?php
class Index_Controller extends Controller {
function __construct($name) {
parent::__construct($name);
}
function index() {
$this->model->setPageTitle('Home', '-');
var_dump($this);
$this->view->output('index/index');
}
function test($value = 'not set') {
echo 'You are in test and the value is ' . $value;
}
}
views/index_view.php
class Index_View extends View {
public function __construct(Model $model) {
parent::__construct($model);
$this->page_title = $this->model->page_title;
}
}
models/index_model.php
class Index_Model extends Model {
public function __construct() {
parent::__construct();
}
}
templates/header.php
<!DOCTYPE html>
<html>
<head>
<title><?php echo $this->page_title; ?></title>
</head>
<body>
<div id="header">
<!-- -->
</div>
<div id="content">
My var_dump in index_controller looks like this...
Cheers,
Tom
I have Apache running on port 81. My project folder is MyPhpProject. Inside it I have 2 folders: Domain and Testing.
In Domain folder I have 3 PHP files:
BaseDomain.php which contains an abstract class BaseDomain
Location.php which contains a concrete class Location inherited from BaseDomain
Employee.php which contains a concrete class Employee inherited from BaseDomain
Employee class has a reference of Location class.
This is the BaseDomain.php:
<?php
abstract class BaseDomain {
}
?>
This is the Location.php:
<?php
$returnRequire = require 'BaseDomain.php';
class Location extends BaseDomain {
private $locationIdInt;
private $codeNameString;
private $descString;
public function setLocationId($locationId) {
$this->locationIdInt = $locationId;
}
public function getLocationId() {
return $this->locationIdInt;
}
public function setCodeName($codeName) {
$this->codeNameString = $codeName;
}
public function getCodeName() {
return $this->codeNameString;
}
public function setDesc($desc) {
$this->descString = $desc;
}
public function getDesc() {
return $this->descString;
}
}
?>
This is Employee.php:
<?php
$returnRequire = require 'BaseDomain.php';
class Employee extends BaseDomain {
private $employeeIdString;
private $locationObject;
public function setEmployeeId($employeeId) {
$this->employeeIdString = $employeeId;
}
public function getEmployeeId() {
return $this->employeeIdString;
}
public function setLocation($location) {
$this->locationObject = $location;
}
public function getLocation() {
return $this->locationObject;
}
}
?>
Now in the Testing folder I created a Test_Employee.php and this is its code:
<?php
set_include_path('../Domain');
$getIncludePath = get_include_path();
echo "getIncludePath = " . $getIncludePath;
echo "<br>";
$returnRequire1 = require 'Location.php';
echo "returnRequire for Location.php = " . $returnRequire1;
echo "<br>";
$returnRequire2 = require 'Employee.php';
echo "returnRequire for Employee.php = " . $returnRequire2;
echo "<br>";
?>
When I try to run it http://localhost:81/MyPhpProject/Testing/Test_Employee.php I got a fatal error regarding cannot redeclare BaseDomain class. This is what I see in browser:
getIncludePath = ../Domain
returnRequire for Location.php = 1
Fatal error: Cannot redeclare class BaseDomain in C:\Program Files
(x86)\Apache Software
Foundation\Apache2.2\htdocs\MyPhpProject\Domain\BaseDomain.php on line
2
I have not created BaseDomain class more than once. So this error is bizarre. Can somebody please explain why I am getting error message? And how to fix it.
Thanks for your time.
The line $returnRequire1 = require 'Location.php'; loads Location.php, which in turns loads BaseDomain.php in the line $returnRequire = require 'BaseDomain.php';. Then, the line $returnRequire2 = require 'Employee.php'; loads Employee.php, which loads (again) BaseDomain.php (the line $returnRequire = require 'BaseDomain.php';). The second load of BaseDomain.php causes php to try to redefine the BaseDomain class, which is no allowed.
The easiest way to solve this problem is to change your require calls to require_once. This will ensure that each file is loaded exactly once per run, which will prevent the error you are experiencing.
BaseDomain.php:
<?php
abstract class BaseDomain {
}
?>
Location.php
<?php
class Location extends BaseDomain {
private $locationIdInt;
private $codeNameString;
private $descString;
public function setLocationId($locationId) {
$this->locationIdInt = $locationId;
}
public function getLocationId() {
return $this->locationIdInt;
}
public function setCodeName($codeName) {
$this->codeNameString = $codeName;
}
public function getCodeName() {
return $this->codeNameString;
}
public function setDesc($desc) {
$this->descString = $desc;
}
public function getDesc() {
return $this->descString;
}
}
?>
Employee.php:
<?php
class Employee extends BaseDomain {
private $employeeIdString;
private $locationObject;
public function setEmployeeId($employeeId) {
$this->employeeIdString = $employeeId;
}
public function getEmployeeId() {
return $this->employeeIdString;
}
public function setLocation($location) {
$this->locationObject = $location;
}
public function getLocation() {
return $this->locationObject;
}
}
?>
Test_Employee.php
<?php
set_include_path(__DIR__.'/MyPhpProject/Domain');
require 'BaseDomain.php';
$getIncludePath = get_include_path();
echo "getIncludePath = " . $getIncludePath;
echo "<br>";
$returnRequire1 = require 'Location.php';
echo "returnRequire for Location.php = " . $returnRequire1;
echo "<br>";
$returnRequire2 = require 'Employee.php';
echo "returnRequire for Employee.php = " . $returnRequire2;
echo "<br>";
?>
I have just a little progress in practicing. Most of my code works but I am not sure if I do things the right way?
Please, can you tell me if I do mistakes and correct me.
First, I create autoload functions:
function autoload_models($model) {
if (file_exists(MODELS_PATH . $model . '.php')) {
require_once MODELS_PATH . $model . '.php';
return true;
} else {
return false;
}
}
spl_autoload_register('autoload_models');
function autoload_controllers($controller) {
if (file_exists(CONTROLLERS_PATH . $controller . '.php')) {
require_once CONTROLLERS_PATH . $controller . '.php';
return true;
} else {
return false;
}
}
spl_autoload_register('autoload_controllers');
I have a class like this:
class Category {
public $db;
public $rows;
public $id;
public function build_category() {
global $db;
global $rows;
$db = new Database();
$db->query("SELECT * from categories");
$rows = $db->resultset();
}
public function category_items() {
global $db;
global $rows;
global $id;
$db = new Database();
$db->query("SELECT * from posts WHERE category_id = '$id'");
$rows = $db->resultset();
}
}
I extend with another class (still have some issues here. Nothing prints):
class Category_Items extends Category {
public $db;
public $rows;
public $id;
public function display_category_items() {
// Call the parent class function
parent::category_items();
global $rows;
global $id;
// Check if the page parameter is integer
if (ctype_digit($_GET['id'])) {
$id = $_GET['id'];
} else {
print "Illegal category page parameter";
}
foreach ($rows as $row) {
print "test";
print $row['post_title']; // This does not work yet. Nothing prints
}
}
}
Class for building a menu with categories (Everything works here):
class Categories_Menu extends Category {
public $db;
public $rows;
public function build_category_menu() {
parent::build_category();
global $rows;
foreach ($rows as $row) {
require VIEWS_PATH . 'categories/categories_menu.php';
}
}
}
And finally instances:
$category_menu = new Categories_Menu();
$category_menu->build_category_menu();
$category_items = new Category_Items();
$category_items->display_category_items();
Thank you for your time and help!
Where do the global variables come from?
Anyway, you should get rid of them.
I guess your rows var does not get changed, after any interaction. Using globals also will not be relevant in extending classes.
Your public properties and globals mentioned, does no interact each other. Thus, the object members seems to be totally useless.
What I would suggest in simple schems would be
class Model {
protected $_db;
public function __construct(Database $db) {
$this->_db = $db;
}
}
class Category extends Model {
public $_rows;
public $_id;
public function build_category() {
$this->_db->query("SELECT * from categories");
$this->_rows = $this->_db->resultset();
}
public function category_items() {
$this->_db->query("SELECT * from posts WHERE category_id = '{$this->_id}'");
$this->_rows = $this->_db->resultset(); // here you will overwrite $_rows ?
}
class Categories_Menu extends Category {
public $_rows;
public function build_category_menu() {
$this->build_category();
foreach ($this->_rows as $row) {
require VIEWS_PATH . 'categories/categories_menu.php';
}
}
}
class Category_Items extends Category {
public $_rows;
public $_id;
public function display_category_items() {
if (ctype_digit($_GET['id'])) { // just intval it, or use is_int?
$this->_id = $_GET['id'];
} else {
print "Illegal category page parameter";
}
// You assign value to $_id, then call the function that requires it
$this->category_items();
foreach ($this->_rows as $row) {
print "test";
print $row['post_title'];
}
}
}
I am writing my own MVC framework and has come to the view renderer. I am setting vars in my controller to a View object and then access vars by echo $this->myvar in the .phtml script.
In my default.phtml I call the method $this->content() to output the viewscript.
This is the way I do it now. Is this a proper way to do that?
class View extends Object {
protected $_front;
public function __construct(Front $front) {
$this->_front = $front;
}
public function render() {
ob_start();
require APPLICATION_PATH . '/layouts/default.phtml' ;
ob_end_flush();
}
public function content() {
require APPLICATION_PATH . '/views/' . $this->_front->getControllerName() . '/' . $this->_front->getActionName() . '.phtml' ;
}
}
Example of a simple view class. Really similar to yours and David Ericsson's.
<?php
/**
* View-specific wrapper.
* Limits the accessible scope available to templates.
*/
class View{
/**
* Template being rendered.
*/
protected $template = null;
/**
* Initialize a new view context.
*/
public function __construct($template) {
$this->template = $template;
}
/**
* Safely escape/encode the provided data.
*/
public function h($data) {
return htmlspecialchars((string) $data, ENT_QUOTES, 'UTF-8');
}
/**
* Render the template, returning it's content.
* #param array $data Data made available to the view.
* #return string The rendered template.
*/
public function render(Array $data) {
extract($data);
ob_start();
include( APP_PATH . DIRECTORY_SEPARATOR . $this->template);
$content = ob_get_contents();
ob_end_clean();
return $content;
}
}
?>
Functions defined in the class will be accessible within the view like this:
<?php echo $this->h('Hello World'); ?>
Here's an example of how i did it :
<?php
class View
{
private $data = array();
private $render = FALSE;
public function __construct($template)
{
try {
$file = ROOT . '/templates/' . strtolower($template) . '.php';
if (file_exists($file)) {
$this->render = $file;
} else {
throw new customException('Template ' . $template . ' not found!');
}
}
catch (customException $e) {
echo $e->errorMessage();
}
}
public function assign($variable, $value)
{
$this->data[$variable] = $value;
}
public function __destruct()
{
extract($this->data);
include($this->render);
}
}
?>
I use the assign function from out my controller to assign variables, and in the destructor i extract that array to make them local variables in the view.
Feel free to use this if you want, i hope it gives you an idea on how you can do it
Here's a full example :
class Something extends Controller
{
public function index ()
{
$view = new view('templatefile');
$view->assign('variablename', 'variable content');
}
}
And in your view file :
<?php echo $variablename; ?>
I am new to PHP OOP and would like to try to nest several classes within another class, to latter call them like so:
$sql = new SQL();
$sql->Head()->Description($_SESSION['page']);
//OR
$sql->Head()->Keywords($_SESSION['page'])
//OR
$sql->Body()->Clients($_SESSION['client'])
//ETC
$query = $sql->Run(); // equivalent to mysql_query("...");
As you can guess, I run into some problems and ended with this poor code:
<?php
require( $_SERVER['DOCUMENT_ROOT'] . '/#some_db_directory/database.php');
//This file contains $db['host'], $db['user'], etc...
class SQL {
public $sql;
public function __construct() {
global $db;
}
public class Head() {
public function Description($page) {
return "SELECT * FROM `$db['database']`.`desciption` WHERE `page` = '$page'";
}
public function Keywords($page) {
return "SELECT * FROM `$db['database']`.`keywords` WHERE `page` = '$page'";
}
}
}
$sql = new SQL();
echo $sql->Head()->Description('home'); //For testing
Is it possible to nest classes in PHP?
If so, how is it done?
What you are trying to do is called Encapsulation. Try google search on PHP encapsulation to learn more.
Here is a code example from http://www.weberdev.com/get_example.php3?ExampleID=4060,
<?php
class App {
private static $_user;
public function User( ) {
if( $this->_user == null ) {
$this->_user = new User();
}
return $this->_user;
}
}
class User {
private $_name;
public function __construct() {
$this->_name = "Joseph Crawford Jr.";
}
public function GetName() {
return $this->_name;
}
}
$app = new App();
echo $app->User()->GetName();
?>
I'm assuming that database.php is a database class. In that case you could do something like this.
head.php
Class Head{
private $_db;
private $_dbName;
public function __construct($db, $dbName){
$this->_db = $db;
$this->_dbName = $dbName;
}
public function Description($page) {
$results = $this->_db->query("SELECT `text` FROM `$this->_dbName`.`description` WHERE `page` = '$page'");
return '<meta name="description" content="' . $results['text'] . '">';
}
public function Keywords($page) {
$results = $this->_db->query("SELECT * FROM `$this->_dbName`.`keywords` WHERE `page` = '$page'");
$keywords = array();
foreach($results as $result){
array_push($keywords, $result['word']);
}
return '<meta name="keywords" content="' . implode(',', $keywords) . '">';
}
}
sql.php
require( $_SERVER['DOCUMENT_ROOT'] . '/#some_db_directory/database.php');
// Require head class file
require( $_SERVER['DOCUMENT_ROOT'] . '/#some_db_directory/head.php');
Class SQL{
public $Head;
public function __construct($dbName){
global $db;
$this->Head = new Head($db, $dbName);
}
}
You would then pass the name of the database into the SQL class (which propogates through to the Head class).
// Require the sql class file
require( $_SERVER['DOCUMENT_ROOT'] . '/#some_db_directory/sql.php');
$sql = new SQL('mydatabase');
echo $sql->Head->Description('home');
Again note that your database class might not return results the way I'm using them here. You will have to modify this to work with your particular database class.
Try it like this
<?php
require( $_SERVER['DOCUMENT_ROOT'] . '/#some_db_directory/database.php');
//This file contains $db['host'], $db['user'], etc...
class SQL {
public $sql;
private $_head;
public function __construct() {
global $db;
$_head = new HeadClass();
}
public function Head() {
return $this->_head;
}
}
class HeadClass { // Class cannot have a public access modifier
public function Description($page) {
return "SELECT * FROM `" . $db['database'] . "`.`desciption` WHERE page = $page";
}
public function Keywords($page) {
return "SELECT * FROM `" . $db['database'] . "`.`keywords` WHERE page = $page";
}
}
$sql = new SQL();
echo $sql->Head()->Description('home.html');
?>
I am moving the class declaration outside the class and creating an instance of the class with in SQL. This is then made available via the Head() function.
Note: For body you will need to create a separate class and use a reference in the SQL class to it like I have done for head.