I've been creating a small number of libraries / classes from scratch in php. I come from a codeigniter background, and I'm trying to make some libraries with similar functionality. I keep running into issues regarding objects.
Is the best way to create a super object $this somehow? My main issue is that I've created a view object and I run a function called load which looks like so:
class View {
public function __construct() {
}
public function load($file = NULL, $data = array()) {
if($file) {
$file .= '.php';
if(file_exists($file)) {
// Extract variables BEFORE including the file
extract($data);
include $file;
return TRUE;
} else {
echo 'View not found';
return FALSE;
}
} else {
return FALSE;
}
}
}
Then in my php file, I have at the top include 'libraries.php'; which looks like:
include 'database.php';
include 'view.php';
include 'input.php';
include 'form.php';
$config = array(
'host' => 'localhost',
'username' => 'username',
'password' => 'password',
'database' => 'database'
);
$database = new Database($config);
$view = new View();
$input = new Input();
$form = new Form();
From the file which I included the libraries, I am able to write something like $form->value('name'); without errors. However, if I do something like this:
$view->load('folder/index', array('var_name' => 'var_value')); then from the folder/index.php file I can access $var_name just fine, but not $form->value('name');. I get errors such as Call to a member function value() on a non-object in ...
My question is how can I organize my libraries and classes in a way that will be reusable. I don't want to use a front loader (an index.php file that everything runs through first). This may be an issue with the way I wrote my classes, but I imagine it's a larger issue regarding where things are located etc.
Put your library/class files in a common directory. Something like:
www
|_includes
| |_classes
| | |_view.php
| |_config
| |_database.php
|_other_folder
|_index.php
You can then set a common include path in your .htaccess file to this "includes" directory:
php_value include_path .:/path/to/www/includes
Then the other_folder/index.php file just needs something like:
require_once('config/database.php');
require_once('classes/view.php');
Related
The following code works and does what I want, but I'm pretty sure I'm doing something dumb\awful.
I'm learning OOP and there is a tutorial I started to follow that used a "Config" class to setup some parameters for the program to use. I've noticed something similar in other tutorials. This tutorial though only included a method to retrieve the configuration (it used the $GLOBALS array) not to update it during the run time of the program. I attempted to add this functionality, but resorted to using eval() which I think is a nono? Also it was never explained in the tutorial why the $GLOBALS array was used instead of just using a static variable so I'm confused about that as well.
Here is init.php which gets included in files needing to access the config options:
<?php
$GLOBALS['config'] = array(
'mysql' => array(
'host' => '127.0.0.1',
'username' => 'root',
'password' => '123456',
'db' => NULL
),
'shell' => array(
'exe' => 'powershell.exe',
'args' => array(
'-NonInteractive',
'-NoProfile',
'-NoLogo',
'-Command'
)
)
);
spl_autoload_register(function($class){
require_once 'classes/' . $class . '.php';
});
This is the Config.php class which has a get and (my) set method to access the config array. For the set method I build a string like "$GLOBALS['config']['someConfig']['someSubConfig'] = 'newVal';" and use eval to execute it. Ultimately I use it in the program like Config::set('mysql/host','zzzzz');
<?php
class Config {
public static function get($path=NULL) {
//return all configs if not specified
$config = $GLOBALS['config'];
if($path) {
//parse path to return config
$path = explode('/', $path);
foreach($path as $element) {
if(isset($config[$element])) {
$config = $config[$element];
} else {
//if config not exist
$config = false;
}
}
}
return $config;
}
public static function set($path=NULL,$value=NULL) {
if($path) {
//parse path to return config
$path = explode('/', $path);
//Start code string for eval
$globalPosition = '$GLOBALS['."'config'".']';
foreach($path as $element) {
$globalPosition .= "['$element']";
}
$globalPosition .= "='$value';";
//End code string
eval($globalPosition);
var_dump($GLOBALS);
}
}
}
First of all, here are a few caveats:
Global variables are rarely a good idea, especially in OOP design (mainly because they couple code very tightly).
Please don't use eval().
You can quite easily modify your code to set the variable (by reference using =&) without having to use eval() at all. For example:
public static function set($path = null,$value = null)
{
if($path)
{
//parse path to return config
$path = explode('/', $path);
//Start code string for eval
$setting =& $GLOBALS['config'];
foreach($path as $element)
{
$setting =& $setting[$element];
}
$setting = $value;
var_dump($GLOBALS);
}
}
I´m making a "very" simple MVC framework in order to learn, however I have trouble getting other pages than the index page to show. In views folder I have 2 files one index.php and one register.php that I´m trying on.
I have tried various ways but can´t get my head around it. I know it is probably best to put different controller classes in different files and maybe a loader controller page but I´m a beginner with php so would like to make it as simple as possible for me...
Any help appriciated!
I have a index.php as a landing file in the root folder to bind everything together:
<?php
/* index.php
*
*/
require_once 'model/load.php';
require_once 'controller/main.php';
new mainController();
In the controller folder i have a file called main.php:
<?php
/* controller/main.php
*
*/
class mainController
{
public $load;
public function __construct()
{
$urlValues = $_SERVER['REQUEST_URI'];
$this->urlValues = $_GET;
//index page
if ($this->urlValues['controller'] == "") {
$indexPage = array("key" => "Hello");
$this->load = new load();
$this->load->view('index.php', $indexPage);
}
//register page
if ($this->urlValues['controller'] == "register.php") {
$registerPage = array("key" => "Register");
$this->load = new load();
$this->load->view('register.php', $registerPage);
}
}
}
And then I have a file called load.php in the model folder:
<?php
/* model/load.php
*
*/
class load
{
/* This function takes parameter
* $file_name and match with file in views.
*/
function view($file_name, $data = null)
{
if (is_readable('views/' . $file_name)) {
if (is_array($data)) {
extract($data);
}
require 'views/' . $file_name;
} else {
echo $this->file;
die ('404 Not Found');
}
}
}
In your mainController class you don't have property with the name urlValues, but you use it: $this->urlValues = $_GET;. And what is more you have local variable with the same name, that you don't use: $urlValues = $_SERVER['REQUEST_URI'];
And how you URL for register.php looks like?
As the title says there is a problem accessing variable (associative array) inside class from included file. Here is the source code both class and include file:
require("applications/cw_database.php");
require("config/dbConfig.php");
require("config/appConfig.php");
class APP_ASSESMENTS
{
private $dbObj;
private $DisplayOutput = "";
public function __construct($PageParams)
{
try
{
$dbObj = new CW_DB($dbConfig['hostname'],$dbConfig['username'],$dbConfig['password'],$dbConfig['name'],$dbConfig['port']);
} catch (Exception $e) {
throw new ErrorException($e);
}
}
...
The other part has nothing to do with $dbConfig.
Also this is the included file (config/dbConfig.php):
/*
Testing configuration for MySQL database
*/
$dbConfig['username'] = "phpcoursework"; // changed on demand
$dbConfig['password'] = "phpcoursework"; // changed on demand
$dbConfig['hostname'] = "localhost"; // changed on demand
$dbConfig['name'] = "students"; // changed on demand
$dbConfig['port'] = 3306; // default for MySQL
First, $dbObj will not automatically assume class member scope, it will create a local copy of CW_DB and discard it when __construct returns. You need to explicitly reference the property;
$this->dbObj = ...
Anyway, global state using global as suggested by others will "work", but if you're using OOP practices you're best not to do that. You can actually return from an include(), so an option would be to do the following:
// your config file dbConfig.php
return array(
'username' => "phpcoursework",
'password' => "phpcoursework",
'hostname' => "localhost",
'name' => "students",
'port' => 3306,
);
And inject it into the object, via constructor or method (here's constructor)
class APP_ASSESMENTS
{
private $dbObj;
public function __construct($dbConfig, $PageParams)
{
$dbObj = new CW_DB($dbConfig['hostname'], $dbConfig['username'],
$dbConfig['password'], $dbConfig['name'], $dbConfig['port']);
// ...
}
}
// include() here, will actually return the array from the config file
$appAssesments = new \APP_ASSESMENTS(include('dbConfig.php'), $PageParams);
It would be recommended that you go another level up: instead, inject the database object itself, taking the dependency out of your APP_ASSESSMENTS class.
(Also, PascalCase is the typical convention of class naming, such as AppAssessments and CwDb)
$dbObj = new CwDb(include('dbConfig.php'));
$appAssessments = new AppAssessments($dbObj, $etc, $etc);
This simple change allows you to remove the dependency from AppAssessments on CwDb. That way, if you extend CwDb for some reason, you can just pass in an instance of the extended class without having to change any code in AppAssessments
You can change the AppAssessments constructor like so:
public function __construct(CwDb $db, $etc, $etc){
$this->db = $db;
// ...
}
This takes advantage of PHPs (limited, albeit still useful) type-hinting, ensuring the first argument is always of the correct type.
This plays into part of the open/closed principle: classes should be open to extension but closed for modification.
Includes are used in the scope of access. So, to access the variables you need to include the files within your class. As earlier mentioned global will let you access variables from another scope too. But global should be used with caution! See the documentation.
See the manual for more information.
Edit: I need to make it clear that globals are never a good alternative for handling these kind of critical variables..
public function __construct($PageParams){
global $dbConfig;
try{
$dbObj = new CW_DB($dbConfig['hostname'],$dbConfig['username'],$dbConfig['password'],$dbConfig['name'],$dbC onfig['port']);
} catch (Exception $e) {
throw new ErrorException($e);
}
}
or you could use $GLOBALS['dbConfig'].
I need to have some configuration options on my website.
I thought it would be easiest to maintain if different options are placed in different files.
Also I need to have a class to retrieve the options from different configuration files.
In the directory structure of my website I created a directory called /setup
In this directory I have several files for the different configuration options, eg: /setup/base.php
The contents of base.php will look something like the following:
$setup = new stdClass();
$setup->currencies = array('USD', 'EUR', );
$setup->locations = array('local', 'international', );
I would like to create a class which reads the file and returns the different options.
class Options
{
function __construct($option)
{
if (!is_file(SETUP_DIR.'/'.$option.'.php')) {
thrown new Exception('Configuration file not found.');
}
$options = // get information from file
return $options; // this should return the currencies and locations
}
}
$options = new Options('base');
However I don't know whether this is the correct way of doing it.
If so I cannot think of a way to retrieve the options from the setup files in the class.
Can you help me with this or at least point me in the right direction?
Well, I don't think there is a right way for this one: Zend uses .ini files, Codeigniter has a set of arrays, and Symfony uses YAML. Wordpress stores most everything in the database, and has one config file which it just includes.
Personally, I'm partial to ini files -- ini is something which is used all over the place, so it has a feeling of, "I can reuse this if necessary", but I think that the only "wrong" solution here is one which is inconsistent -- if you're using ini, use ini, if arrays, arrays, but don't mix.
In your case, there are a couple of options. These two seem to be among the most common. (both of these examples assumes that the stdClass object is named $options in the loaded file) You could create a wrapper:
class Options
{
private $_options;
function __construct($option)
{
if (!is_file(SETUP_DIR.'/'.$option.'.php')) {
thrown new Exception('Configuration file not found.');
}
require(SETUP_DIR.'/'.$option.'.php');
$this->_options = $options;
// you shouldn't put a return in a constructor
}
// this will point to the internal _options variable.
// making it a read-only access to the values from $option
public function __get($name){ return $this->_options->$name; }
}
Or, you could use a Singleton pattern and just return the objects in the individual classes:
class OptionsRetriever
{
private $_fetched;
private static $_instance;
private __construct(){}
public static function &getInstance()
{
if( !isset( self::$_instance ) ) self::$_instance = new OptionsRetriever();
return self::$_instance;
}
public function getConfig( $name )
{
if( !isset( $this->_fetched[ $name ] ) )
{
require(SETUP_DIR.'/'.$name.'.php');
$this->_fetched[ $name ] = $options;
}
return $this->_fetched[ $name ];
}
}
Or, you could combine them:
class Options
{
private $_options;
function __construct($options)
{
$this->_options = $options;
}
public function __get($name){ return $this->_options->$name; }
}
// replace getConfig with this
public function getConfig( $name )
{
if( !isset( $this->_fetched[ $name ] ) )
{
require(SETUP_DIR.'/'.$name.'.php');
$this->_fetched[ $name ] = new Options( $options );
}
return $this->_fetched[ $name ];
}
You can use the concept of ini files and the parse_ini_file function in PHP to accomplish this. There are clear examples on the php.net function page: parse_ini_file # PHP.NET
You could just include the base.php file, like so:
class Options
{
function __construct($option)
{
if (!is_file(SETUP_DIR.'/'.$option.'.php')) {
thrown new Exception('Configuration file not found.');
}
include_once(SETUP_DIR.'/'.$option.'.php');
$options = $setup; // make sure your variable in the config is allways named $setup
return $options; // this should return the currencies and locations
}
}
$options = new Options('base');
echo $options->currencies[0]; //should print 'USD' provided that your base.php file from the question was used.
If you wan't the values stored in PHP directly you would need to include PHP files with the values in them, for instance as variables, constants or classes.
A method i like to use is storing configuration in an XML file and then loading and parsing that. Same method could be used with JSON if you prefer that format. Just make sure you block any browser access to those files.
dir:
application
-controllers
-models
-views
-mobile_views
How do I auto load templates at mobile_views when I use $this->load->view and view by iphone or other mobile phone?
Check this
You can do it in two way.
Way 1: Its very simple. In the above answer (the link I have given) add following line in the end of MyController function
$this->load->_ci_view_path . = $this->view_type .'/';
You are done. You can simply load view like normal view load.
Way 2:
To autoload a view based on user agent, I think you can implement it using hooks. To implement this hooks you need to follow the following steps
Autoload user agent library in autoload.php
$autoload['libraries'] = array('user_agent');
Enable hooks in config.php
$config['enable_hooks'] = TRUE;
Not implement hooks on post_controller_constructor. Add following codes to hooks.php
$hook['post_controller_constructor'][] = array('class' => 'Loadview',
'function' => 'load',
'filename' => 'loadview.php',
'filepath' => 'hooks'
);
Now create a page named loadview.php under hooks directory having following code
class Loadview
{
public static $MOBILE_PLATFORM = 'mobile';
public static $DEFAULT_PLATFORM = 'default';
public function load(){
$this->CI =& get_instance();
$view_type = $this->CI->agent->is_mobile() ? self::$MOBILE_PLATFORM : self::$DEFAULT_PLATFORM;
$this->CI->load->_ci_view_path = $this->CI->load->_ci_view_path . $view_type .'/';
}
}
You are done now. You can simply load view like normal view load.
to load views from another dir aside from "views", i found this forum topic to be helpful
http://codeigniter.com/forums/viewthread/132960/
function external_view($path, $view, $vars = array(), $return = FALSE)
{
$full_path = $path.$view.'.php';
if (file_exists($full_path))
{
return $this->_ci_load(array('_ci_path' => $full_path, '_ci_view' => $view, '_ci_vars' => $this->_ci_object_to_array($vars), '_ci_return' => $return));
}
else
{
show_error('Unable to load the requested module template file: '.$view);
}
}
and you can work the rest from the controller.
I do this in my controller:
public function index()
{
if($this->agent->is_mobile())
{
$this->load_mobile();
}
else
{
$this->load_web();
}
}
public function load_mobile()
{
$this->load->view('mobile/home');
}
public function load_web()
{
$this->load->view('web/home');
}
In this way I can add different data to mobile and to web pages.
I also extend the default controller and add some useful extra features:
Enables the usage of master page/templates.
Can add css and javascript files.
Uses the _output method for controlling the controllers output.
Can load relative content with in the form of modules (views)
So I can manage better the different pages.
Bye!!