I'm trying to write my site in Controller-Model-View convention without using any framework but I have a problem with __autoload function.
Here's my source_folder/model/database.class.php file:
<?php
function __autoload($sName) {
$aName = explode('_',$sName);
if($aName[0] == 'Model')
include '/model/' . strtolower($aName[1]) . '.class.php';
else if($aName[0] == 'View')
include '/view/' . strtolower($aName[1]) . 'class.php';
else if($aName[0] == 'Controller')
include '/controller' . strtolower($aName[1]) . 'class.php';
}
/**
* Description of database
*
* #author Lysy
*/
class Model_Database {
private $oConfig = new Model_Config();
$this->oConfig->getConfigArray('database');
}
?>
And here's my source_folder/model/config.class.php file:
<?php
include_once '../config.php';
/**
* Description of config
*
* #author Lysy
*/
class Model_Config {
static function GetConfigArray($name) {
return $config[$name];
}
}
?>
The problem appears in line
private $oConfig = new Model_Config();
and it says syntax error, unexpected T_NEW.
I don't understand where lies the problem, because I'm using Netbeans IDE and when I type in private $oConfig = new M the program gives me a hint with Model_Config.
I'd be very glad to read some advices and probable solution to this problem, regards :)
EDIT:
I rewrited the class Model_Config to look like this:
class Model_Database {
private $oConfig;
__construct() {
$oConfig = new Model_Config();
};
$this->oConfig->getConfigArray('database');
}
But now there is a problem in line
__construct() {
Netbeans says that __construct() is unexpected, the error on the site stays the same, unexpected T_NEW
EDIT2: Oh sorry, too much C++, I keep forgeting about function word
You can't assign values to member variables using expressions. You have to do that in your constructor or inside of a method.
Does your Model_Config contains more than that static method or why do you use "new Model_Config()" ?
That error message and "private" looks like you want to create an object direcly in your property. If you want to add an Model_Config object to the property of a specific class, you should use dependency injection. Here in your constructor for excample:
<?php
class YourClass
{
private $oConfig;
public function __construct(Model_Config $oConfig)
{
$this->oConfig = $oConfig;
}
}
A nicer way of injection would be the usage of an interface as type hint. Maybe that method in your Model_Config shouldn't be static.
Related
I'm learning PHP OOP and right now I built a basic calculator.
Here is my code at index.php:
require_once 'Calculator.class.php';
require_once 'Adder.class.php';
require_once 'Substract.class.php';
require_once 'Operator.interface.php';
require_once 'Multiplier.class.php';
require_once 'Devider.class.php';
$c = new Calculator;
$c->setOperation(new Adder);
$c->calculate(10,50); // 60
echo $c->getResult();
And this is the Calculator class file:
class Calculator
{
protected $result;
protected $operation;
public function setOperation(OperatorInterface $operation)
{
$this->operation = $operation;
// var_dump($operation);
}
public function calculate()
{
foreach(func_get_args() as $number)
{
$this->result = $this->operation->run($number,$this->result);
}
}
public function getResult()
{
return $this->result;
}
}
And this is the interface that is being called within this class file:
interface OperatorInterface
{
public function run($number,$result);
}
And this is the class Adder which is called from the index.php:
class Adder implements OperatorInterface
{
public function run($number,$result)
{
return $result + $number;
}
}
As you can see it looks nice and okay... however I get this weird error:
Fatal error: Interface 'OperatorInterface' not found on line 2 Adder.php
So line 2 of Adder Class is this:
class Adder implements OperatorInterface
Which means I have not include the interface properly. But I did include that.
So why am I getting this error?
Where did I make my mistake?
You need to include the Operator.interface.php file before the Adder.class.php file, otherwise when the compiler gets to the Adder class, it hasn't yet encountered anything called OperatorInterface, so it doesn't recognise it and can't verify that it's valid to declare that the Adder class implements it. Since it's also referenced in the Calculator class, you should include it before that as well.
require_once 'Operator.interface.php';
require_once 'Calculator.class.php';
require_once 'Adder.class.php';
require_once 'Substract.class.php';
require_once 'Multiplier.class.php';
require_once 'Devider.class.php';
It should be that simple - for future reference you should always order your includes so that dependencies between them can be satisfied, because they get processed in the order you supply them.
I'm currently trying to remove all errors and warnings I have in my project the Inspection tool from my PHPStorm give to me.
I encounter a snippet PHPStorm says "Unused private method _xxx" while it's actually used, but in a dynamical way. Here is a simplifyed snippet:
<?php
class A
{
private function _iAmUsed()
{
//Do Stuff...
}
public function run($whoAreYou)
{
$methodName = '_iAm' . $whoAreYou;
if (method_exists($this, $methodName)) {
$this->$methodName();
}
}
}
$a = new A();
$a->run('Used');
?>
In this snippet, PHPStorm will tell me "Unused private method _iAmUsed" while, in fact, it is used...
How can I, by adding PHPDocs or something, whatever, make my IDE understand my method is actually used?
Note that I give to my "run" call, a static string, but we can imagine also this:
<?php
$a->run($_POST['whoYouAre']); //$_POST['whoYouAre'] == 'Used'
?>
Thanks a lot!
mark used methods in phpdoc as #used
example
/**
* #uses _iAmUsed()
* #param string $whoAreYou
*/
public function run($whoAreYou)
{
$methodName = '_iAm' . $whoAreYou;
if (method_exists($this, $methodName)) {
$this->$methodName();
}
}
Add a noinspection annotation above the method:
/** #noinspection PhpUnusedPrivateMethodInspection */
private function _iAmUsed()
{
//Do Stuff...
}
Or after running code analysis you can right-click any inspection in the results window and choose Suppress for statement to have PHPStorm add the proper annotation itself. For more information see http://www.jetbrains.com/phpstorm/webhelp/suppressing-inspections.html
I'm trying to create a system that it has a GeneralObj. The GeneralObj allows user to initiate special objects for database's tables with a String of the table name. So far, it works perfect.
class GeneralObj{
function __construct($tableName) {
//...
}
}
However, it is too tired to type new GeneralObj(XXX) every time.
I am wondering is that possible to simplify the process to like new XXX(), which is actually running the same as new GeneralObj(XXX)?
I spot PHP provided __autoload method for dynamic loading files in the setting include_path but it requires a the actually definition file existing. I really don't want to copy and copy the same definition files only changing a little.
For cause, eval is not an option.
Maybe you can just auto-create the files in the autoloader:
function __autoload($class_name) {
// check for classes ending with 'Table'
if (preg_match('/(.*?)Table/', $class_name, $match)) {
$classPath = PATH_TO_TABLES . '/' . $match[1] . '.php';
// auto-create the file
if (!file_exists($classPath)) {
$classContent = "
class $class_name extends GeneralObj {
public __construct() {
parent::__construct('{$match[1]}');
}
}";
file_put_contents($classPath, $classContent);
}
require_once $classPath;
}
}
Use inheritance. Make GeneralObj the superclass of the table specific classes. This way you can dynamically derive class names and instantiate objects. Example:
class someTable extends GeneralObj {
}
$tableName = 'some';
$className = $tableName . 'Table';
$obj = new $className;
No, this is not possible.
The runkit extension allows programmatic manipulation of the PHP runtime environment, but it cannot do this. Even if it could, it would IMHO be a very bad idea, greatly impacting the requirements and complexity of the application in exchange for saving a few keystrokes.
In an unrelated note, your GeneralObj class has functionality that sounds suspiciously like that of a dependency injection container. Perhaps you should consider replacing it with one?
Something like this autoloader:
myAutoloader::Register();
class myAutoloader
{
/**
* Register the Autoloader with SPL
*
*/
public static function Register() {
if (function_exists('__autoload')) {
// Register any existing autoloader function with SPL, so we don't get any clashes
spl_autoload_register('__autoload');
}
// Register ourselves with SPL
return spl_autoload_register(array('myAutoloader', 'Load'));
} // function Register()
/**
* Autoload a class identified by name
*
* #param string $pClassName Name of the object to load
*/
public static function Load($pClassName){
if (class_exists($pClassName,FALSE)) {
// Already loaded
return FALSE;
}
$pClassFilePath = str_replace('_',DIRECTORY_SEPARATOR,$pClassName) . '.php';
if (file_exists($pClassFilePath) === FALSE) {
// Not a class file
return new GeneralObj($pClassName);
}
require($pClassFilePath);
} // function Load()
}
And it's up to GeneralObj to throw an exception if the table class can't be instantiated
Last week I learned that classes can be included in your project by writing an __autoload() function. Then I learned that using an autoloader isn't only a technique but also a pattern.
Now I'm using the autoloader in my project and I've found it very very useful. I was wondering if it could be possible to do the same thing with functions. It could be very useful to forget about including the right PHP file with functions inside it.
So, is it possible to create a function autoloader?
There is no function auto-loader for functions. You have four realistic solutions:
Wrap all functions into namespacing classes (context appropriate). So let's say you have a function called string_get_letters. You could add that to a class called StringFunctions as a static function. So instead of calling string_get_letters(), you'd call StringFunctions::get_letters(). You would then __autoload those namespaced classes.
Pre-load all functions. Since you're using classes, you shouldn't have that many functions, so just pre-load them.
Load functions prior to using them. In each file, require_once the function files that are going to be used in that file.
Don't use functions in the first place. If you are developing OOP code (which it seems like you are anyway), there should be little to no need for functions at all. Everything you would need a function (or multiple) for, you could build in a OO manner and avoid the need for functions.
Personally, I'd suggest either 1, 2 or 4 depending on your exact need and the quality and size of your codebase...
If you are using Composer in your Project, you can add a files directive to the autoload section.
This will than actually generate a require_once in the autoloader, but it feels like real autoloading, because you dont have to take care of that.
Its not lazy loading though.
Example taken from Assetic:
"autoload": {
"psr-0": { "Assetic": "src/" },
"files": [ "src/functions.php" ]
}
I read something a while back about an ugly hack that caught fatal errors and tried to include and execute the missing function(s), but I definitely wouldn't go that road.
The closest thing you have is the __call() magic method, which is sort of a __autoload() for methods, not functions. It might be good enough for your needs; if you can afford to call a class and require each different function separately. Since PHP 5.3.0, you also have __callStatic().
An example using __callStatic():
class Test
{
public function __callStatic($m, $args)
{
if (function_exists($m) !== true)
{
if (is_file('./path/to/functions/' . $m . '.php') !== true)
{
return false;
}
require('./path/to/functions/' . $m . '.php');
}
return call_user_func_array($m, $args);
}
}
Test::functionToLoad(1, 2, 3);
This would call the functionToLoad() function defined in ./path/to/functions/functionToLoad.php.
Well, as usual there is a PECL extension for that:
automapPECL
(via: http://phk.tekwire.net/joomla/support/doc/automap.htm)
It's supposed to autoload functions as well as classes. Which however doesn't work with the current PHP interpreter yet.
(An alternative option btw, is generating stub functions that load and run namespaced counterparts.)
That being said. Autoloading is not universally considered a good practice. It leads to overly fractured class hierarchies and object happiness. And the real reason PHP has autoloading is because include and dependency management systems are inmature.
namespace MyNamespace;
class Fn {
private function __construct() {}
private function __wakeup() {}
private function __clone() {}
public static function __callStatic($fn, $args) {
if (!function_exists($fn)) {
$fn = "YOUR_FUNCTIONS_NAMESPACE\\$fn";
require str_replace('\\', '/', $fn) . '.php';
}
return call_user_func_array($fn, $args);
}
}
And using namespaces, we can do: Fn::myFunc() and spl_autoload_register(). I've used this code with examples at: https://goo.gl/8dMIMj
I use a Class and __invoke. The __invoke method is called when a script calls a class as a function. I often do something like this:
<?php
namespace API\Config;
class Slim {
function __invoke() {
return [
'settings' => [
'displayErrorDetails' => true,
'logger' => [
'name' => 'api',
'level' => Monolog\Logger\Logger::DEBUG,
'path' => __DIR__ . '/../../logs/api.log',
],
]
];
}
}
I can then call like a function:
$config = API\Config\Slim;
$app = Slim\App($config())
new Functions\Debug() will load functions to root namespace.
namespace Functions
{
class Debug
{
}
}
namespace
{
if (! function_exists('printr')) {
/**
*
* #param mixed $expression
*/
function printr()
{
foreach (func_get_args() as $v) {
if (is_scalar($v)) {
echo $v . "\n";
} else {
print_r($v);
}
}
exit();
}
}
}
Here is another rather complex example, based on the suggestions in this discussion.
The code can also be seen here: lib/btr.php
<?php
/**
* A class that is used to autoload library functions.
*
* If the function btr::some_function_name() is called, this class
* will convert it into a call to the function
* 'BTranslator\some_function_name()'. If such a function is not
* declared then it will try to load these files (in this order):
* - fn/some_function_name.php
* - fn/some_function.php
* - fn/some.php
* - fn/some/function_name.php
* - fn/some/function.php
* - fn/some/function/name.php
* The first file that is found will be loaded (with require_once()).
*
* For the big functions it makes more sense to declare each one of them in a
* separate file, and for the small functions it makes more sense to declare
* several of them in the same file (which is named as the common prefix of
* these files). If there is a big number of functions, it can be more
* suitable to organize them in subdirectories.
*
* See: http://stackoverflow.com/questions/4737199/autoloader-for-functions
*/
class btr {
/**
* Make it TRUE to output debug info on '/tmp/btr.log'.
*/
const DEBUG = FALSE;
/**
* The namespace of the functions.
*/
const NS = 'BTranslator';
/**
* Relative directory where the functions are located.
*/
const FN = 'fn';
private function __construct() {}
private function __wakeup() {}
private function __clone() {}
/**
* Return the full name (with namespace) of the function to be called.
*/
protected static function function_name($function) {
return self::NS . '\\' . $function;
}
/**
* Return the full path of the file to be loaded (with require_once).
*/
protected static function file($fname) {
return dirname(__FILE__) . '/' . self::FN . '/' . $fname . '.php';
}
/**
* If a function does not exist, try to load it from the proper file.
*/
public static function __callStatic($function, $args) {
$btr_function = self::function_name($function);
if (!function_exists($btr_function)) {
// Try to load the file that contains the function.
if (!self::load_search_dirs($function) or !function_exists($btr_function)) {
$dir = dirname(self::file($fname));
$dir = str_replace(DRUPAL_ROOT, '', $dir);
throw new Exception("Function $btr_function could not be found on $dir");
}
}
return call_user_func_array($btr_function, $args);
}
/**
* Try to load files from subdirectories
* (by replacing '_' with '/' in the function name).
*/
protected static function load_search_dirs($fname) {
do {
self::debug($fname);
if (file_exists(self::file($fname))) {
require_once(self::file($fname));
return TRUE;
}
if (self::load_search_files($fname)) {
return TRUE;
}
$fname1 = $fname;
$fname = preg_replace('#_#', '/', $fname, 1);
} while ($fname != $fname1);
return FALSE;
}
/**
* Try to load files from different file names
* (by removing the part after the last undescore in the functin name).
*/
protected static function load_search_files($fname) {
$fname1 = $fname;
$fname = preg_replace('/_[^_]*$/', '', $fname);
while ($fname != $fname1) {
self::debug($fname);
if (file_exists(self::file($fname))) {
require_once(self::file($fname));
return TRUE;
}
$fname1 = $fname;
$fname = preg_replace('/_[^_]*$/', '', $fname);
}
return FALSE;
}
/**
* Debug the order in which the files are tried to be loaded.
*/
public static function debug($fname) {
if (!self::DEBUG) {
return;
}
$file = self::file($fname);
$file = str_replace(DRUPAL_ROOT, '', $file);
self::log($file, 'Autoload');
}
/**
* Output the given parameter to a log file (useful for debugging).
*/
public static function log($var, $comment ='') {
$file = '/tmp/btr.log';
$content = "\n==> $comment: " . print_r($var, true);
file_put_contents($file, $content, FILE_APPEND);
}
}
While you can't autoload functions and constants, you can use something like jesseschalken/autoload-generator which will automatically detect what files contain things which can't be autoloaded and load them eagerly.
The solution I came up with. As lightweight as I could come up with.
class functions {
public static function __callstatic($function, $arguments) {
if (!function_exists($function)) {
$file = strtok($function, '_') .'.php';
include '/path/to/functions/'.$file;
}
return call_user_func_array($function, $arguments);
}
}
Use it by calling functions::foo_bar($anything).
I try to use the autoloading of classes to my advantage. So, when a class is auto-loaded, the class file is executed. Therefore, I create a class with a static method called 'boot' that does nothing. When I invoke that method, the class will be autoloaded, hence every function in that file will be defined in the global scope. What's even more interesting is that the functions will be defined in the namespace of the class, so there are no clashes.
For example:
<?PHP
namespace Functions;
// functions are defined in this file
class GlobalFunctions{
public static function boot(){};
}
function foo(){ // code... }
?>
// the client file
<?php
// VS Code automatically does this.
use Functions\GlobalFunctions;
use function Functions\foo;
// I usually put this in the bootstrap file
GlobalFunctions::boot();
// call foo() from the Functions namespace
foo();
?>
Include all functions file in one file and then include it
//File 1
db_fct.php
//File 2
util_fct.php
//In a functions.php include all other files
<?php
require_once 'db_fct.php';
require_once 'util_fct.php';
?>
Include functions.php whenever you need functions ..
try this
if ($handle = opendir('functions')) {
while (false !== ($entry = readdir($handle))) {
if (strpos($entry, '.php') !== false) {
include("functions/$entry");
}
}
closedir($handle);
}
I am looking for feedback on my control architecture script (included below). Specifically, I am looking for feedback regarding the script's design, organization, commenting, and formatting. I enjoy php programming as a hobby, and am looking to learn where I can improve my code.
Thanks in advance!
class FrontController extends ActionController {
//Declaring variable(s)
private static $instance;
protected $controller;
//Class construct method
public function __construct() {}
//Starts new instance of this class with a singleton pattern
public static function getInstance() {
if(!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
public function dispatch($throwExceptions = false) {
/* Checks for the GET variables $module and $action, and, if present,
* strips them down with a regular expression function with a white
* list of allowed characters, removing anything that is not a letter,
* number, underscore or hyphen.
*/
$regex = '/[^-_A-z0-9]+/';
$module = isset($_GET['module']) ? preg_replace($regex, '', $_GET['module']) : 'home';
$action = isset($_GET['action']) ? preg_replace($regex, '', $_GET['action']) : 'frontpage';
/* Generates Actions class filename (example: HomeActions) and path to
* that class (example: home/HomeActions.php), checks if $file is a
* valid file, and then, if so, requires that file.
*/
$class = ucfirst($module) . 'Actions';
$file = $this->pageDir . '/' . $module . '/' . $class . '.php';
try {
//Checks for existance of file
if (!is_file($file)) {
throw new Exception('File not found!');
}
//Includes file
require_once $file;
/* Creates a new instance of the Actions class (example: $controller
* = new HomeActions();), and passes the registry variable to the
* ActionController class.
*/
$controller = new $class();
$controller->setRegistry($this->registry);
//Trys the setModule method in the ActionController class
$controller->setModule($module);
/* The ActionController dispatchAction method checks if the method
* exists, then runs the displayView function in the
* ActionController class.
*/
$controller->dispatchAction($action);
} catch(Exception $error) {
/* An exception has occurred, and will be displayed if
* $throwExceptions is set to true.
*/
if($throwExceptions) {
echo $error;
}
}
}
}
abstract class ActionController {
//Declaring variable(s)
protected $registry;
protected $module;
protected $registryItems = array();
//Class construct method
public function __construct(){}
public function setRegistry($registry) {
//Sets the registry object
$this->registry = $registry;
/* Once the registry is loaded, the controller root directory path is
* set from the registry. This path is needed for the controller
* classes to work properly.
*/
$this->setPageDir();
}
//Sets the controller root directory from the value stored in the registry
public function setPageDir() {
$this->pageDir = $this->registry->get('pageDir');
}
//Sets the module
public function setModule($module) {
$this->module = $module;
}
//Gets the module
public function getModule() {
return $this->module;
}
/* Checks for actionMethod in the Actions class (example: doFrontpage()
* within home/HomeActions.php) with the method_exists function and, if
* present, the actionMethod and displayView functions are executed.
*/
public function dispatchAction($action) {
$actionMethod = 'do' . ucfirst($action);
if (!method_exists($this, $actionMethod)) {
throw new Exception('Action not found!');
}
$this->$actionMethod();
$this->displayView($action);
}
public function displayView($action) {
if (!is_file($this->pageDir . '/' . $this->getModule() . '/' . $action . 'View.php')) {
throw new Exception('View not found!');
}
//Sets $this->actionView to the path of the action View file
$this->actionView = $this->pageDir . '/' . $this->getModule() . '/' . $action . 'View.php';
//Sets path of the action View file into the registry
$this->registry->set('actionView', $this->actionView);
//Includes template file within which the action View file is included
require_once $this->pageDir . '/default.tpl';
}
}
class Registry {
//Declaring variables
private $store;
//Class constructor
public function __construct() {}
//Sets registry variable
public function set($label, $object) {
$this->store[$label] = $object;
}
//Gets registry variable
public function get($label) {
if(isset($this->store[$label])) {
return $this->store[$label];
} else {
return false;
}
}
//Adds outside array of registry values to $this->store array
public function addRegistryArray($registryItems) {
foreach ($registryItems as $key => $value) {
$this->set($key, $value);
}
}
//Returns registry array
public function getRegistryArray() {
return $this->store;
}
}
Without having a detailed look on your code:
Try to write code that is self-explanatory by using meaningful function and variable names. Only use comments where the purpose or the functioning of your code is not clear. E.g.
//Declaring variable(s)
//Class construct method
//Checks for existance of file
//Includes file
are useless comments because the code itself is already clear enough.
A book worth reading: Clean Code
I am torn between closevoting as too localized and wanting to comment on the code. Also, it's too much code to wade through now, so I will comment only a few things:
1) Documentation style
Why not use an established documentation format, like PHPDoc?
2) Formatting
is consistent as far as I can see, but I suggest to use a coding convention that is in wide use, like that of PEAR or ZF (which is based on PEAR) instead of doing your own (yours is close to PEAR anyway, so you might as well adopt it completely).
3) Singleton pattern
In order for the Singleton to work it has to have a private __contruct and __clone method. But I suggest not to use it at all. Many people believe the Singleton is an AntiPattern. There are a number of questions on SO discussing the disadvantages of the Singleton pattern, so have a look around. If there should be only one instance, then simply dont instantiate a second one. If you need to access the FrontController in other classes, inject it.
4) Method length
I probably would try to shorten the dispatch method. Basically, if you describe what a method does and you have to use an and to do so, that part should go into it's own method. Try to make methods into small discrete units. That will make UnitTesting easier as well.
5) Separation of Concerns
I am not sure why the FrontController extends from ActionController. There is no other classes that extent it, butI suppose the classes the FrontController instantiates are subclasses of ActionController too. But the FrontController, while named a controller, does different things than the PageControllers, so I'd probably keep them separated.
On a sidenote, if you are interested in increasing the quality of your code, have a look at the slides and tools given at http://phpqatools.org/
Your code is very remeiscent of CakePhp. I'd recommend checking that out, it does all of this work with App::import() and a File class that wraps the filesystem. It's tested by a community on different versions and OSes, it will in fact save you time.