For MVC reasons, I want to be able to trigger a function to find when the function has been called, since Codeigniter has functions around their core, I want to hook a function such as setcookie and create a file when it's been called (from the triggered function) for example:
function call_me()
{
$file = fopen('setcookie.txt', 'a+');
fwrite($file, 'Called at ' . __CLASS__);
fclose();
}
So when setcookie is called, it should trigger the call_me function. Is there any specific function or method to do this? I know about debug_backtrace but that's not the purpose I want.
What you basically need to have a look at is Observers.
The observer pattern (aka. Dependents, publish/subscribe) is a
software design pattern in which an object, called the subject,
maintains a list of its dependents, called observers, and notifies
them automatically of any state changes, usually by calling one of
their methods. It is mainly used to implement distributed event
handling systems. Observer is also a key part in the familiar MVC
architectural pattern. In fact the observer pattern was first
implemented in Smalltalk's MVC based user interface framework.1
Why don't you try what is described here :
http://devzone.zend.com/1384/observer-pattern-in-php/
I know about debug_backtrace but that's not the purpose I want.
I see that you insist not to use backtrack function, but still I believe that when you want to log when a function is called backtrack can come in handy.
The idea is that you have a predifined piece pf code stored in a constant, whenever you want to debug an if condition evaluates this code.
If you are under prouction the if statement will prevent from evaluating anything so your code's speed is not affected. If it works for you you can modify it to your needs, to track down even more levels.
To make my point this is a full example, if I have not understood right and this is not what you're looking for, my apologies!
To check the example you have a file: test.php
<?php
define ('__SITE_PATH',realpath(dirname(__FILE__)).'/');
ini_set('log_errors', 1);
ini_set('error_log', __SITE_PATH.'my_error_log.log');
include 'test1.php';
include 'test2.php';
define (__DEBUG_EVAL, '
$dbt = debug_backtrace();
error_log(
"\n".
"Parent function file: " . $dbt[1]["file"] . "\n" .
"Parent function class: " . $dbt[2]["class"] . "\n" .
"Parent fiunction name: " . $dbt[2]["function"] . "\n" .
"Par. fiunc. called from line: " . $dbt[2]["line"] . "\n" .
"Child function file: " . $dbt[0]["file"] . "\n" .
"Child function class: " . $dbt[1]["class"] . "\n" .
"Child fiunction name: " . $dbt[1]["function"] . "\n" .
"Child fiunc. called from line: " . $dbt[1]["line"] . "\n" .
"\n"
);
');
test1::a();
?>
This is test1.php
<?PHP
class test1
{
public static function a()
{
test2::b();
}
}
?>
The last is test2.php
<?PHP
class test2
{
public static function b()
{
if(defined('__DEBUG_EVAL')) eval(__DEBUG_EVAL);
echo 'Hello!';
}
}
?>
This is the result:
[13-Apr-2012 14:37:18]
Parent function file: C:\PHP-GTK\MyProjects\Electre\test1.php
Parent function class: test1
Parent fiunction name: a
Par. fiunc. called from line: 29
Child function file: C:\PHP-GTK\MyProjects\Electre\test2.php
Child function class: test2
Child fiunction name: b
Child fiunc. called from line: 7
Related
I am migrating my PHP web application to use namespaces in preparation for putting it up on GitHub. Most of this is straightforward, but I have one spot where the code must obtain static information from a class when the name of the class is provided by a string, and I cannot find the correct syntax for this situation:
if ($className == '')
{
$className = 'Record';
$information['classname'] = 'Record';
}
if (!class_exists(__NAMESPACE__ . "\\" . $className))
{
print "<p>include '". __NAMESPACE__ . "/" . $className . ".inc'</p>\n";
include __NAMESPACE__ . "/" . $className . ".inc";
}
// BOTH of the following statements report Class 'Blog' not found
// when $className == "Blog"
$order = $className::$defaultOrder;
$order = __NAMESPACE__ . "\\" . $className::$defaultOrder;
What is the correct syntax for referencing a static member of a class within a namespace when the name of the class is provided in a variable? The output of a web page that invokes the above code is:
include 'Genealogy/Blog.inc'
Blog.inc included
Fatal error: Uncaught Error: Class 'Blog' not found in /home/jcobban/includes/Genealogy/Record.inc:2043 Stack trace: #0 /home/jcobban/includes/Genealogy/RecordSet.inc(418): Genealogy\Record::getInformation('Blogs') #1 /home/jcobban/includes/Genealogy/Template.inc(2898): Genealogy\RecordSet->__construct('Blogs', Array) #2 /home/jcobban/includes/Genealogy/Template.inc(764): Genealogy\FtTemplate->customization() #3 /home/jcobban/public_html/Genealogy/genealogy.php(82): Genealogy\Template->__construct('/home/jcobban/p...') #4 {main} thrown in /home/jcobban/includes/Genealogy/Record.inc on line 2043
The above code is in the base class Genealogy\Record which is implemented in "Genealogy/Record.inc". This base class is defined:
<?php
namespace Genealogy;
use \PDO;
use \Exception;
use \ArrayAccess;
use \Countable;
use \Iterator;
class Record implements Iterator, ArrayAccess
{
...
protected static $defaultOrder = '';
The derived class Genealogy\Blog is implemented in "Genealogy/Blog.inc" as follows:
<?php
namespace Genealogy;
use \PDO;
use \Exception;
require_once __NAMESPACE__ . '/Record.inc';
print "<p>Blog.inc included</p>\n";
class Blog extends Record
{
...
The class Blog does not override the base definition of static member $defaultOrder.
FYI I converted the code of the library to use namespaces by running the following PERL script:
use strict;
use warnings;
use 5.010;
use File::Find;
use File::Slurp;
my #content;
find( \&wanted, '/home/jcobban/includes/');
exit;
sub wanted {
if ((substr $File::Find::dir, -9) ne "Genealogy" && -f)
{
print "wanted: ", $File::Find::name, "\n";
my #lines = read_file($File::Find::name);
my $first = shift #lines;
if ((substr $first, 0, 5) eq '<?php')
{
foreach my $line (#lines){
$line =~ s#require_once(\s*)(['"])#require_once$1__NAMESPACE__ . $2/#;
$line =~ s#require(\s*)(['"])#require$1__NAMESPACE__ . $2/#;
$line =~ s#include(\s*)(['"])#include$1__NAMESPACE__ . $2/#;
$line =~ s#include(\s*)\$#include$1__NAMESPACE__ . "/" . \$#;
$line =~ s#class_exists\((['"])#class_exists(__NAMESPACE__ . $1\\\\#;
$line =~ s#class_exists\((\$)#class_exists(__NAMESPACE__ . "\\\\" . $1#;
}
my $newfile = $File::Find::dir . "/Genealogy/" . $_;
print "add namespace and write to $newfile\n";
unshift #lines, "use \\Iterator;\n";
unshift #lines, "use \\Countable;\n";
unshift #lines, "use \\ArrayAccess;\n";
unshift #lines, "use \\Exception;\n";
unshift #lines, "use \\PDO;\n";
unshift #lines, "namespace Genealogy;\n";
unshift #lines, $first;
write_file($newfile, #lines);
}
}
return;
}
I do not believe it is significant but the web site is running PHP Version 7.2.10.
The classes in this library implement an object-oriented interface to an SQL database. That is the application code accesses fields within the record using sub-script notation and updates the record by $record->save() which determines whether or not to use INSERT or UPDATE to apply the change and uses the proper syntax for the SQL server. The RecordSet class that appears in the exception encapsulates an SQL query and presents the appearance of an array of instances of Record and permits performing updates and deletes of all of the members of the set. These two classes, and the classes that are derived from them to support individual tables, completely insulate the application code from SQL.
The problem I was having would appear to be that the :: operator was processed before the concatenation. When I changed the code to:
$nsclass = __NAMESPACE__ . "\\" . $className;
if (!class_exists($nsclass))
{
include __NAMESPACE__ . "/" . $className . ".inc";
}
$order = $nsclass::$defaultOrder;
it worked.
Basically, I would like to have multiple versions of the same page, some which accept params, some which do not.
For example,
public function index()
{
require_once(APPPATH . 'views/sessiondata.inc.php');//sets up variables from session data
require_once(APPPATH . 'views/header.inc.php');//header
include(APPPATH . 'views/breadcrumbs.inc.php');//top breadcrumbs
echo ' <h1>Let\'s sell an item, '.$username.',</h1>';
//print_r($this->session->all_userdata());
include(APPPATH . 'views/breadcrumbs.inc.php');//bottom breadcrumbs
require_once(APPPATH . 'views/footer.inc.php');//footer
}
This is a default function for controller x.php, hypothetically. Can I also use:
public function index($item)
{
require_once(APPPATH . 'views/sessiondata.inc.php');//sets up variables from session data
require_once(APPPATH . 'views/header.inc.php');//header
include(APPPATH . 'views/breadcrumbs.inc.php');//top breadcrumbs
echo ' <h1>Let\'s sell an item, '.$username.',</h1>';
//DO SOMETHING WITH THE $item VARIABLE.
//print_r($this->session->all_userdata());
include(APPPATH . 'views/breadcrumbs.inc.php');//bottom breadcrumbs
require_once(APPPATH . 'views/footer.inc.php');//footer
}
Will the system automatically know which version to use? Is there something similar to Java's #OVERRIDE that I need to use?
No. PHP doesn't allow you to overload functions in this manner. That said, you can achieve similar functionality by overloading the __call method:
public function __call ( string $name , array $arguments )
{
if( $name === 'index' && sizeof($arguments) === 1 ) {
// do index($item) stuff
} else {
// do index() stuff
}
}
But to be honest, using __call in this manner is of no real value, and adds unnecessary performance overhead, and complexity. In PHP, if you want to reuse code, simply decompose your functions into smaller discrete functions and reuse them as needed.
You cannot have two separate functions (because PHP does not allow function overloading) but you can use a default parameter which CodeIgniter will honor when choosing controller functions. So instead of having two functions, do the following:
public function index($item=null)
{
require_once(APPPATH . 'views/sessiondata.inc.php');//sets up variables from session data
require_once(APPPATH . 'views/header.inc.php');//header
include(APPPATH . 'views/breadcrumbs.inc.php');//top breadcrumbs
echo ' <h1>Let\'s sell an item, '.$username.',</h1>';
if ($item !== null) {
//DO SOMETHING WITH THE $item VARIABLE.
}
//print_r($this->session->all_userdata());
include(APPPATH . 'views/breadcrumbs.inc.php');//bottom breadcrumbs
require_once(APPPATH . 'views/footer.inc.php');//footer
}
Currently, I have a pretty average php auto loader loading in my classes. I've come to a point in development where I will need to override a class with another class based on a variable. I'm running a custom SaaS application, and we have the occasional organization that will demand some weird change to the way the system functions. In the past, we've filled up our code with garbage by massive IF statements for orgs, such as
if(ORGID == 'ABCD'){
//do this insane thing
}else{
//Normal code here.
}
So, I've been toying with the idea of a dynamic auto loader. ORGID is one of the very first defines in the application. The entire application is running under a fixed namespace of COMPANY\PRODUCT; Here's a code sample of what I was thinking I could do.
class MyLoader {
static public function load($name) {
$temp = explode('\\',$name);
$class = array_pop($temp);
$name = str_replace('_',DIRECTORY_SEPARATOR,$class);
if(file_exists(ROOT.'includes/_classes/' . $name . '.php')){
include(ROOT.'includes/_classes/' . $name . '.php');
}
}
}
spl_autoload_register(__NAMESPACE__ .'\MyLoader::load');
Since ROOT and ORGID are defined before the autoloader comes into play, I thought about doing this
class MyLoader {
static public function load($name) {
$temp = explode('\\',$name);
$class = array_pop($temp);
$name = str_replace('_',DIRECTORY_SEPARATOR,$class);
if(file_exists(ROOT.'includes/_classes/' . ORGID . '/' . $name . '.php')){
include(ROOT.'includes/_classes/' . ORGID . '/' . $name . '.php');
}elseif(file_exists(ROOT.'includes/_classes/' . $name . '.php')){
include(ROOT.'includes/_classes/' . $name . '.php');
}
}
}
spl_autoload_register(__NAMESPACE__ .'\MyLoader::load');
While this works, I have to copy/paste my entire class into the org specific class file, then make changes. I can't extend the primary class, because the classes share the same name. The only option I've been able to come up with that would allow me to extend my classes in such a way is to never load the base class.
Instead of
$myObj = new myClass();
I call
$myObj = new customMyClass();
and I have a file called customMyClass(); which simply extends myClass without making any changes to it. This way, the auto loader will load customMyClass and then load myClass. If an organization has their own customMyClass in their organization folder, then it will load in that class, which will then properly load in myClass.
While this works, we have hundreds of class files, which would double if we had a custom file for each.
I've seen a couple of examples that use eval to handle similar situations. Is that really the only way to do this type of thing?
UPDATE:
Just so I'm clear, the end goal is so that the thousands of places we've called $myObj = new myClass(); doesn't need to be rewritten.
I'm building a script that got a static class used to load few things including files and views.
class load
{
public static function view($file_path, $area)
{
debug::log('getting view <b>' . $area . $file_path . '</b>.');
ob_start();
self::file($file_path, 'areas/' . $area . '/views');
debug::log('flushing view <b>' . $area . $file_path . '</b>.');
eturn ob_get_clean();
}
public static function file($file, $folder)
{
if(is_file($file_path = ROOT . '/' . $folder . '/' . $file))
{
if(require_once $file_path)
{
debug::log('file <b>' . $file_path . '</b> included.');
return true;
}
}
else
debug::kill('requested file <b>' . $file_path . '</b> does not exist.');
}
}
In the controller Im calling the view method to get a view:
$html = load::view('public', 'path/to/view/file.php');
Obviously, Im not able to access the variables from the controller at the view file using this practice, so I did a small modification on the view class to capture the vars:
public static function view($file_path, $area, $vars = array())
And added the following lines of codes to get the keys into vars:
while(list($n_list_var,$v_list_var)=each($vars))
$$n_list_var = $v_list_var;
But again I can't access the vars since Im using a method to load a file.
I have a method to load the files because I wanna test and log each file include attempt and not repeat the code every time I need include a file. And I have the loader view inside the loader class so I have all the methods of this kind together. Should I give up on using a class to load files? Should I use the loader view method on a extendable class from my controller?
Instead of going ahead and modify my entire script I would like to hear some opinions ... what would be the best practice to go? Or is there a way to solve my problem? Maybe using __set and __get magic methods?
Thanks,
Why not just pass a $vars argument to load::file() and extract( $vars ) (possibly moving the vars you use inside file() into class variables to prevent them from being overwritten)?
I'm suggesting using extract() instead of:
while(list($n_list_var,$v_list_var)=each($vars))
$$n_list_var = $v_list_var;
By the way, it would be a good idea to name your class Load.
When we include files in PHP, they are somehow cached. So, if one contains a class definition, when we try to include it twice, we will get an error saying "Can not redeclare class".
But is it possible to get include file code invoked with a scope set to current function, let's say?
E.g. if we have two files:
moo.php:
<?php
class Moo
{
function __construct()
{
echo "hello, world!" . PHP_EOL;
}
}
?>
main.php:
<?php
function foo()
{
include("moo.php");
new Moo();
}
foo();
new Moo(); // <-- here should be an error saying "could not find class Moo"
include("moo.php");
new Moo(); // <-- and here should not
?>
As far as i've tried, not eval(file_get_contents("moo.php"));, nor namespaces either gave no expected effect with a least of code...
Use require_once() and include_once(). They'll make PHP remember what files were included, and NOT include them again elsewhere in the code. After the first include/require, subsequent ones on the same file will essentially become a null-op.
Namespaces should fix this prob -> http://php.net/manual/en/language.namespaces.php
You should try implementing autoload for your classes. It will help prevent things like this.
Seems that monkey patching did the trick:
<?php
$code = <<<EOS
namespace monkeypatch;
\$s = "moofoo";
echo "This should six, but is it?: " . strlen(\$s) . PHP_EOL;
echo "Whoa! And now it is six, right?: " . \strlen(\$s) . PHP_EOL;
function strlen(\$x)
{
return -3.14;
}
EOS;
eval($code);
echo "While out of namespaces it is still six...: " . strlen("moofoo") . PHP_EOL;
Lots of thanks to Marc B. for the hint!