Cannot extend a class located in another file, PHP - php

I am trying to set up a class with commonly used tasks, such as preparing strings for input into a database and creating a PDO object. I would like to include this file in other class files and extend those classes to use the common class' code.
However, when I place the common class in its own file and include it in the class it will be used in, I receive an error that states the second class cannot be found. For example, if the class name is foo and it is extending bar (the common class, located elsewhere), the error says that foo cannot be found. But if I place the code for class bar in the same file as foo, it works.
Here are the classes in question -
Common Class
abstract class coreFunctions {
protected $contentDB;
public function __construct() {
$this->contentDB = new PDO('mysql:host=localhost;dbname=db', 'username', 'password');
}
public function cleanStr($string) {
$cleansed = trim($string);
$cleansed = stripslashes($cleansed);
$cleansed = strip_tags($cleansed);
return $cleansed;
}
}
Code from individual class
include $_SERVER['DOCUMENT_ROOT'] . '/includes/class.core-functions.php';
$mode = $_POST['mode'];
if (isset($mode)) {
$gallery = new gallery;
switch ($mode) {
case 'addAlbum':
$gallery->addAlbum($_POST['hash'], $_POST['title'],
$_POST['description']);
}
}
class gallery extends coreFunctions {
private function directoryPath($string) {
$path = trim($string);
$path = strtolower($path);
$path = preg_replace('/[^ \pL \pN]/', '', $path);
$path = preg_replace('[\s+]', '', $path);
$path = substr($path, 0, 18);
return $path;
}
public function addAlbum($hash, $title, $description) {
$title = $this->cleanStr($title);
$description = $this->cleanStr($description);
$path = $this->directoryPath($title);
if ($title && $description && $hash) {
$addAlbum = $this->contentDB->prepare("INSERT INTO gallery_albums
(albumHash, albumTitle, albumDescription,
albumPath)
VALUES
(:hash, :title, :description, :path)");
$addAlbum->execute(array('hash' => $hash, 'title' => $title, 'description' => $description,
'path' => $path));
}
}
}
The error when I try it this way is
Fatal error: Class 'gallery' not found in /home/opheliad/public_html/admin/photo-gallery/includes/class.admin_photo-gallery.php on line 10

You'll need to include or require the file with the original class. Otherwise PHP won't see it.
Make sure the include is successful, enable error reporting to see errors, or use require to trigger a fatal error on fail.

Still learning the ins and outs of OOP. After a few minutes of research I came across spl_autoload_register in the PHP documentation.
I placed the coreFunctions class in /includes/classes/coreFunctions.class.php and the gallery class in /includes/classes/gallery.class.php
My code then became:
function cvfdAutoloader($class) {
include $_SERVER['DOCUMENT_ROOT'] . '/includes/classes/' . $class . '.class.php';
}
spl_autoload_register('cvfdAutoloader');
$mode = $_POST['mode'];
if (isset($mode)) {
$gallery = new gallery;
switch ($mode) {
case 'addAlbum':
$gallery->addAlbum($_POST['hash'], $_POST['title'],
$_POST['description']);
}
}
And it works! Would someone care to shed some light on what exactly is happening here that is different from just including coreFunctions?

Related

How does a php program decide what function is called first when I go to a php page?

I am trying to understand how a php application that is called with a POST to this URL works:
transliterator/romaji
The romaji.php looks like this:
<?php
namespace JpnForPhp\Transliterator;
class Romaji extends TransliterationSystem
{
private $latinCharacters = array();
public function __construct($system = '')
{
$file = __DIR__ . DIRECTORY_SEPARATOR . 'Romaji' . DIRECTORY_SEPARATOR . (($system) ? $system : 'hepburn') . '.yaml';
parent::__construct($file);
}
public function __toString()
{
return $this->configuration['name']['english'] . ' (' . $this->configuration['name']['japanese'] . ')';
}
TransliterationSystem looks like this:
<?php
namespace JpnForPhp\Transliterator;
use Symfony\Component\Yaml\Yaml;
abstract class TransliterationSystem
{
public $configuration = array();
public function __construct($file)
{
$this->configuration = Yaml::parse(file_get_contents($file));
}
public function transliterate($str)
{
$str = $this->preTransliterate($str);
foreach ($this->configuration['workflow'] as $work) {
if (!method_exists($this, $work['function'])) {
continue;
}
$params = array($str);
if (isset($work['parameters'])) {
$params[] = $work['parameters'];
}
$str = call_user_func_array(array($this, $work['function']), $params);
}
$str = $this->postTransliterate($str);
return $str;
}
Can someone explain to me the sequence of events for when I POST to romaji.php? Below is a link to the github if there is something that I should have included but didn't.
For reference here's the link to github
Normally a PHP file is read (and evaluated) from top to bottom. As pointed out in a comment above, these are just class declarations - there's no code there to actually instantiate the classes or do anything with them, so there's really nothing happening here as such.
For something to happen, there would need to be some code to make use of these classes, for example:
$r = new Romaji();
// Do something with $r ....
EDIT:
I just had a look at the GitHub link, and apparently this is a library; so you'll call it from your own code - it won't do anything by itself.
I'm the one who wrote this library :)
JpnForPhp exposes various helper and functions, that's why you don't see any instanciation like = new Romaji() ; the transliterator component doesn't call himself :)
If you want to see some sample to understand how to use it, please check the test files or the demo website source code
Hope this help.

Load configuration file just once

I'm working on my script to convert legacy links to seo friendly urls.
index.php
require 'AltoRouter.php';
$router = new AltoRouter();
$router->setBasePath('/router');
$urls = [
'index.php?option=com_index&task=articles&id=1',
'index.php?option=com_index&task=articles&slug=1-article-title',
'index.php?option=com_index&task=articles.category&cid=100-category1',
'index.php?option=com_shop&task=products&slug=100-amazing-product',
];
foreach($urls as $i=>$url) {
echo $router->getSefUrl($url);
}
AltoRouter.php
...
public function getSefUrl($url) {
$url_clean = str_replace('index.php?', '', $url);
parse_str($url_clean, $output);
$component = empty($output['option']) ? 'com_index' : $output['option'];
$task = empty($output['task']) ? 'index' : $output['task'];
$path = 'components/'.$component.'/routes/routes.json';
$data = json_decode(file_get_contents($path));
if (!empty($data)) {
foreach($data as $route) {
$this->map($route[0], $route[1], $route[2], $route[2]);
}
}
$route_info = $this->findUrlFromRoutes($task);
return empty($route_info) ? $url : $this->generate($route_info->task, $output);
}
...
My question: Every time when I'm using getSefUrl method I'm loading routes from external file. Is it ok? Or can I optimize code above some kind? If yes - how to?
Thanks!
You could avoid multiple fetches and decodes in your loop by breaking that out.
In AltoRouter.php
private $routes = array();
function getComponentRoutes($component)
{
if(! isset($this->routes[$component])) {
$path = 'components/'.$component.'/routes/routes.json';
$this->routes[$component] = json_decode(file_get_contents($path));
}
return $this->routes[$component];
}
You can replace that require with require_once or better use autoloading :
You may define an __autoload() function which is automatically called
in case you are trying to use a class/interface which hasn't been
defined yet. By calling this function the scripting engine is given a
last chance to load the class before PHP fails with an error.
Create a folder and put all your required classs in this folder:
function __autoload($class) {
require_once "Classes" . $class . '.php';
}

Cannot redeclare class: how to autoload a class if exists already in a folder?

How can I check if a class exists already in a folder then do not load this class again from another folder?
I have this folder structure for instance,
index.php
code/
local/
And I have these two identical classes in code/ and local/
from local/
class Article
{
public function getArticle()
{
echo 'class from local';
}
}
from core,
class Article
{
public function getArticle()
{
echo 'class from core';
}
}
So I need a script that can detects the class of Article in local/ - if it exits already in that folder than don't load the class again from core/ folder. Is it possible?
This is my autoload function in index.php for loading classes,
define ('WEBSITE_DOCROOT', str_replace('\\', '/', dirname(__FILE__)).'/');
function autoloadMultipleDirectory($class_name)
{
// List all the class directories in the array.
$main_directories = array(
'core/',
'local/'
);
// Set other vars and arrays.
$sub_directories = array();
// When you use namespace in a class, you get something like this when you auto load that class \foo\tidy.
// So use explode to split the string and then get the last item in the exloded array.
$parts = explode('\\', $class_name);
// Set the class file name.
$file_name = end($parts).'.php';
// List any sub dirs in the main dirs above and store them in an array.
foreach($main_directories as $path_directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator(WEBSITE_DOCROOT.$path_directory), // Must use absolute path to get the files when ajax is used.
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileObject)
{
if ($fileObject->isDir())
{
// Replace any backslash to '/'.
$pathnameReplace = str_replace('\\', '/', $fileObject->getPathname());
//print_r($pathnameReplace);
// Explode the folder path.
$array = explode("/",$pathnameReplace);
// Get the actual folder.
$folder = end($array);
//print_r($folder);
// Stop proccessing if the folder is a dot or double dots.
if($folder === '.' || $folder === '..') {continue;}
//var_dump($fileObject->getPathname());
// Must trim off the WEBSITE_DOCROOT.
$sub_directories[] = preg_replace('~.*?(?=core|local)~i', '', str_replace('\\', '/', $fileObject->getPathname())) .'/';
}
}
}
// Mearge the main dirs with any sub dirs in them.
$merged_directories = array_merge($main_directories,$sub_directories);
// Loop the merge array and include the classes in them.
foreach($merged_directories as $path_directory)
{
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name))
{
// There is no need to use include/require_once. Autoload is a fallback when the system can't find the class you are instantiating.
// If you've already included it once via an autoload then the system knows about it and won't run your autoload method again anyway.
// So, just use the regular include/require - they are faster.
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
}
}
// Register all the classes.
spl_autoload_register('autoloadMultipleDirectory');
$article = new Article();
echo $article->getArticle();
of course I get this error,
Fatal error: Cannot redeclare class Article in C:\wamp\...\local\Article.php on line 3
class_exists seems to be the answer I should look into, but how can I use it with the function above, especially with spl_autoload_register. Or if you have any better ideas?
Okay, I misunderstood your question. This should do the trick.
<?php
function __autoload($class_name) {
static $core = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "core";
static $local = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "local";
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR):
$file_local = "{$local}{$file_name}.php";
require is_file($file_local) ? $file_local : "{$core}{$file_name}.php";
}
This is easily solved by using namespaces.
Your core file goes to /Core/Article.php:
namespace Core;
class Article {}
Your local file goes to /Local/Article.php:
namespace Local;
class Article {}
And then use a very simple autoloader, e.g.:
function __autoload($class_name) {
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR);
require "/var/www{$file_name}.php";
}
PHP loads your classes on demand, there's no need to load the files up front!
If you want to use an article simply do:
<?php
$coreArticle = new \Core\Article();
$localArticle = new \Local\Article();

php OOP: Using eval() to build a string to access $GLOBALS

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);
}
}

PHP custom class loader

i made a custom class loader function in php
something like..
load_class($className,$parameters,$instantiate);
its supposed to include the class and optionally instantiate the class specified
the problem is about the parameters. ive been trying to pass the parameters all day
i tried
load_class('className',"'param1','param2'",TRUE);
and
load_class('className',array('param1','param2'),TRUE);
luckily nothing works xD
is it possible to pass the params?
i even tried..
$clas = new MyClass(array('param1','param2'));
here it is..
function load_class($class, $param=null, $instantiate=FALSE){
$object = array();
$object['is_required'] = require_once(CLASSES.$class.'.php');
if($instantiate AND $object['is_required']){
$object[$class] = new $class($param);
}
return $object;
}
if you are in PHP 5.x I really really recommend you to use autoload. Prior to PHP 5.3 you should create sort of "namespace" (I usually do this with _ (underscore))
autoload allows you to include classes on the fly and if your classes are well designed the overhead is minimun.
usually my autoload function looks like:
<?php
function __autoload($className) {
$base = dirname(__FILE__);
$path = explode('_', $className);
$class = strtolower(implode('/',$path));
$file = $base . "/" . $class;
if (file_exists($file)) {
require $file;
}
else {
error_log('Class "' . $className . '" could not be autoloaded');
throw new Exception('Class "' . $className . '" could not be autoloaded from: '.$file);
}
}
this way calling
$car = new App_Model_Car(array('color' => 'red', 'brand' => 'ford'));
the function will include the class
app/model/car.php
Seems to me that you should be using __autoload() to just load classes as they are referenced and circumvent having to call this method manually. This is exactly what __autoload() is for.

Categories