Lazily load classes through registry system - php

I've been trying to optimise a backend system to make it just load classes as and when they're required, since there's no point loading every class when they're not required. I know we have the spl_autoload_register in PHP. At present I have my registry using __get and __set to access and set variables lazily, along with a loading function to add new classes to the registry as an object.
class registry {
public function __set($index, $value){
if(!isset($this->$index)){
$this->vars[$index] = $value;
} else {
die("The variable ".__CLASS__."->".$index." is already in use and cannot be redefined.");
}
}
public function __get($index){
if(isset($this->vars[$index])){
return $this->vars[$index];
} else if(isset($this->$index)){
return $this->$index;
} else {
$debug_backtrace = debug_backtrace();
$callee = next($debug_backtrace);
die("The variable \$".__CLASS__."->".$index." does not exist!");
}
}
public function load($class){
if(isset($class) && !empty($class) && file_exists('/_class/'.$class.'.class.php')){
include_once('/_class/'.$class.'.class.php');
$this->$class = new $class();
if(is_object($this->$class)){
} else {
die('Not found!');
}
}
} else if(isset($class) && !empty($class)){
die('The class `'.$class.'` does not exist!');
}
}
}
The above works great, at the moment, so all I need to do is the following:
$registry = new registry();
$registry->load('router');
$registry->load('mysql');
$registry->load('settings');
//etc
However, to make it use everything as and when needed, say settings doesn't appear on every page, I thought I could just change the __get section of the class as follows:
public function __get($index){
if(isset($this->vars[$index])){
return $this->vars[$index];
} else if(isset($this->$index)){
return $this->$index;
} else if($this->load($index)){ // additional line
return $this->$index; // additional line
} else {
$debug_backtrace = debug_backtrace();
$callee = next($debug_backtrace);
die("The variable \$".__CLASS__."->".$index." does not exist!");
}
}
However, all I keep getting now is Notice: Undefined property: registry::$settings in ... which is the if(is_object($this->$class)){ line, which I do not understand as it works normally via the function route as outlined previously, so an object success gets created through the current method, but not in the new one. Even as basic as the below, the same error appears (and hi there appears in the browser`):
class settings {
function __construct(){
echo 'hi there!';
}
}
I know I'm probably missing something very small, but an extra pair of eyes may help :o)

Seems I forgot to return the class via the load function :o(

Related

How can I pass a constructor as a parameter? PHP

say I have a block of repeated code (I'll keep the example simple, although consider that the actual repeated functionality is considerably longer, but goes through the same logic, but with different error codes and messages)
public function firstThing($working) {
if(!$working){
$message = "The first things not working";
throw new Throwable\FirstThingNotWorking($message);
}
return true;
}
public function secondThing($working) {
if(!$working){
$message = "number two not working";
throw new Throwable\NumTwoThingNotWorking($message);
}
return true;
}
public function thirdThing($working) {
if(!$working){
$message = "number three not working";
throw new Throwable\NumeroTresThingNotWorking($message);
}
return true;
}
I'm bugged because it seems I could change these to call a single function, which could then be called by any of the above functions with 1 line, something like
private function checkIfWorking($working, $message, $throwableClass){
if(!$working) {
throw new $throwableClass($message);
}
return true;
}
I'd rather not instantiate the throwable object unless it is used, but I'm unsure how to pass just the throwable class in such a way that it can be instantiated by the function.

In __destruct(), how can you see if an exception is currently in flight?

How can I see if an exception is currently in flight, i.e. the stack is unwinding?
In the example below how would you implement isExceptionInFlight()?
<?php
class Destroyer
{
function __destruct() {
if (isExceptionInFlight()) {
echo 'failure';
} else {
echo 'success';
}
}
}
function isExceptionInFlight() {
// ?????
}
function createAndThrow()
{
$var = new Destroyer;
throw new \Exception;
}
createAndThrow();
The purpose of this would be to implement D's scope statement, which is available as a library in multiple other languages. This allows you to get rid of nested try-catch blocks, which in turn makes it easier to do transactions with rollbacks correctly.
Addendum1:
I've looked around in the Zend PHP Engine and executor_globals.exception seems to be what I'm looking for (https://github.com/php/php-src/blob/master/Zend/zend_globals.h). However this value is always nullptr when I inspect it during __destruct(). Any idea where I should look next?
Addendum2:
Inspecting executor_globals.opline_before_exception has led to some progress. However it is not reset to nullptr when the exception has been caught.
Addendum3:
I've found the following code (line 135)
/* Make sure that destructors are protected from previously thrown exceptions.
* For example, if an exception was thrown in a function and when the function's
* local variable destruction results in a destructor being called.
*/
old_exception = NULL;
if (EG(exception)) {
if (EG(exception) == object) {
zend_error_noreturn(E_CORE_ERROR, "Attempt to destruct pending exception");
} else {
old_exception = EG(exception);
EG(exception) = NULL;
}
}
zend_call_method_with_0_params(&obj, object->ce, &destructor, ZEND_DESTRUCTOR_FUNC_NAME, NULL);
if (old_exception) {
if (EG(exception)) {
zend_exception_set_previous(EG(exception), old_exception);
} else {
EG(exception) = old_exception;
}
}
This seems to actively PREVENT me from doing what I want, and explains why executor_globals.exception is always nullptr.
Although I don't recommend, I have implemented it in the past. My approach was (simply put) like this:
Implement custom Exception class
class MyException extends Exception {
public static $exceptionThrown = false;
public function __construct($your parameters) {
self::$exceptionThrown = true;
}
}
Now, every exception should be your own exception implementation instead of default Exception.
class Destroyer {
public function __destruct() {
if(MyException::exceptionThrown() {
Database::rollback();
} else {
Database::commit();
}
}
}

How to include a php and then remove it?

Well, I don't know if this post have the correct title. Feel free to change it.
Ok, this is my scenario:
pluginA.php
function info(){
return "Plugin A";
}
pluginB.php
function info(){
return "Plugin B";
}
Finally, I have a plugin manager that is in charge of import all plugins info to pool array:
Manager.php
class Manager
{
protected $pool;
public function loadPluginsInfo()
{
$plugin_names = array("pluginA.php", "pluginB.php");
foreach ($plugin_names as $name)
{
include_once $name;
$this->pool[] = info();
}
}
}
The problem here is that when I print pool array it only show me the info on the first plugin loaded. I supposed that the file inclusing override the info because it still calling the info() method from the first include.
Is there a way to include the info of both plugins having the info() function with the same name for all plugins files?
Thank you in advance
PS: a fatal cannot redeclare error is never hurled
you can use the dynamic way to create plugin classes
plugin class
class PluginA
{
public function info()
{
return 'info'; //the plugin info
}
}
manager class
class Manager
{
protected $pool;
public function loadPluginsInfo()
{
$plugin_names = array("pluginA", "pluginB"); //Plugin names
foreach ($plugin_names as $name)
{
$file = $name . '.php';
if(file_exists($file))
{
require_once($file); //please use require_once
$class = new $name(/* parameters ... */); //create new plugin object
//now you can call the info method like: $class->info();
}
}
}
}
Are you sure the interpreter isn't choking w/ a fatal error? It should be since you're trying to define the info function twice here.
There are many ways to achieve what you want, one way as in #David's comment above would be to use classes, eg.
class PluginA
{
function info() { return 'Plugin A'; }
}
class PluginB
{
function info() { return 'Plugin B'; }
}
then the Manager class would be something like this:
class Manager
{
protected $pool;
public function loadPluginsInfo()
{
$plugin_names = array("PluginA", "PluginB");
foreach ($plugin_names as $name)
{
include_once $name . '.php';
$this->pool[] = new $name();
}
}
}
Now you have an instance of each plugin class loaded, so to get the info for a plugin you would have $this->pool[0]->info(); for the first plugin. I would recommend going w/ an associative array though so you can easily reference a given plugin. To do this, the assignment to the pool would become:
$this->pool[$name] = new name();
And then you can say:
$this->pool['PluginA']->info();
for example.
There are many other ways to do it. Now that 5.3 is mainstream you could just as easily namespace your groups of functions, but I would still recommend the associative array for the pool as you can reference a plugin in constant time, rather than linear.

variable string in child variable of object is ignored

I'm trying to load libraries dynamically with a loop then add them to $this and I'm trying to use instanceof to see if it's already been initiated before trying to initiate it again:
$libraries = array('strings');
foreach ($libraries as $lib) {
require_once('lib/'.$lib.'.lib.php');
if(!($this->lib_{$lib} instanceof $lib)){
$this->lib_{$lib} = new $lib();
} else {
continue;
}
}
But when I try this I just get an 500 internal server error from apache.
The error I get is:
[15-Feb-2012 10:31:21] PHP Notice: Undefined property: load::$lib_ in /home/wwwsrv/public_html/system/core.lib.php on line 57
(line 57 refers to the line with the if statement)
Any ideas?
Looks like you don't have a member variable initialized which matches YOUR_CLASS::$lib_SOMELIB, so PHP is throwing a notice.
I would try something like:
if ((!isset($this->lib_{$lib})) ||
(!($this->lib_{$lib} instanceof $lib))) {
Also, you may want to look into using magic methods; __set and __get to dynamically set member variables of YOUR_CLASS, so you don't need to maintain a laundry list.
-- Edit #2 --
Not sure why you are getting the stdObject error, might be that already tried initializing the value earlier. Take a look at this snippet, and see if it helps. I tested it and works.
class strings
{
public function __toString()
{
return __CLASS__;
}
}
class MyClass
{
private $dynamicMembers = array();
public function loadLibs()
{
$libraries = array('strings');
foreach ($libraries as $lib) {
// Had to do it this way, b/c it was resulting in lib_ and not lib_strings
$memberName = 'lib_' . $lib;
if ((!isset($this->$memberName)) ||
(!($this->$memberName instanceof $lib))) {
var_dump(sprintf('I would have required: (%s)', 'lib/'.$lib.'.lib.php'));
$this->$memberName = new $lib();
} else {
continue;
}
}
}
public function __set($name, $value)
{
$this->dynamicMembers[$name] = $value;
}
public function __get($name)
{
if (array_key_exists($name, $this->dynamicMembers)) {
return $this->dynamicMembers[$name];
}
}
}
$myClass = new MyClass();
$myClass->loadLibs();
var_dump((string) $myClass->lib_strings);
var_dump($myClass->lib_strings);
exit;
-- Edit #1 --
After closer inspection of your code, it appears you are trying to create an autoloader coupled with a lazy-load pattern. Lazy-loading works well in some situations, but keep in mind that it will be difficult for calling code to set specific member attributes of the objects being instantiated, if this is a non-issue, then disregard.
Also, I would recommend reading up on PHP's built-in spl_autoload capabilities.
Would the following work better for you?
class Library_Loader {
protected $_loadedLibraries = array();
public function load($library) {
if (!array_key_exists($library, $this->_loadedLibraries)) {
$path = realpath('lib/' . $library . '.lib.php');
if (false === $path) {
throw new Exception("Invalid library $library");
}
require_once $path;
$this->_loadedLibraries[$library] = new $library();
}
return $this->_loadedLibraries[$library];
}
}
$libraries = array('strings');
$loader = new Library_Loader;
foreach ($libraries as $library) {
$loader->load($library);
}
You could also look at wrapping this up into an autoloader so that you could simply call new strings(); in your code and have the class automatically loaded for you.

Using Interfaces in Kohana 3.1.3

I'm trying to build a form wizard in Kohana and am learning a bit as I go. One of the things that I've learn might work best is utilizing a state pattern in my class structure to manage the different steps a user can be in during the form process.
After doing some research, I've been thinking that the best approach may be to use an interface and have all of the steps act as states that implement the interface. After a state validates, it will change a session variable to the next step, which can be read upon the initial load of the interface and call the correct state to use.
Does this approach make sense? If so, how the heck do I make it happen (how do I best structure the filesystem?)
Here is the rough start I've been working on:
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Project_Builder #state
* Step_One #state
* Step_Two #state
**/
interface Project_Builder
{
public function do_this_first();
public function validate();
public function do_this_after();
}
class Step_One implements Project_Builder {
public function __construct
{
parent::__construct();
// Do validation and set a partial variable if valid
}
public function do_this_first()
{
echo 'First thing done';
// This should be used to set the session step variable, validate and add project data, and return the new view body.
$session->set('step', '2');
}
public function do_this_after()
{
throw new LogicException('Have to do the other thing first!');
}
}
class Step_Two implements Project_Builder {
public function do_this_first()
{
throw new LogicException('Already did this first!');
}
public function do_this_after()
{
echo 'Did this after the first!';
return $this;
}
}
class Project implements Project_Builder {
protected $state;
protected $user_step;
protected $project_data
public function __construct()
{
// Check the SESSION for a "step" entry. If it does not find one, it creates it, and sets it to "1".
$session = Session::instance('database');
if ( ! $session->get('step'))
{
$session->set('step', '1');
}
// Get the step that was requested by the client.
$this->user_step = $this->request->param('param1');
// Validate that the step is authorized by the session.
if ($session->get('step') !== $this->user_step)
{
throw new HTTP_Exception_404('You cannot skip a step!');
}
// Check if there is user data posted, and if so, clean it.
if (HTTP_Request::POST == $this->request->method())
{
foreach ($this->request->post() as $name => $value)
{
$this->project_data["$name"] = HTML::chars($value);
}
}
// Trigger the proper state to use based on the authorized session step (should I do this?)
$this->state = new Step_One;
}
public function doThisFirst()
{
$this->state = $this->state->do_this_first();
}
public function doThisAfter()
{
$this->state = $this->state->do_this_after();
}
}
$project = new Project;
try
{
$project->do_this_after(); //throws exception
}
catch(LogicException $e)
{
echo $e->getMessage();
}
$project = new Project;
$project->do_this_first();
$project->validate();
$project->do_this_after();
//$project->update();
Your way certainly looks possible, however I would be tempted to keep it simpler and use some of Kohanas build in features to take care of what you want. For example, I would use Kostache (mustache) and have separate View classes (and potentially templates) for each step. Then the controller becomes quite simple. See the example below (missing session stuff and validation of the step_number). All of the validation is handled in the model. If there is a validation error, an exception can be thrown which can then pass error messages back to the View.
<?php
class Wizard_Controller {
function action_step($step_number = 1)
{
$view = new View_Step('step_' + $step_number);
if ($_POST)
{
try
{
$model = new Model_Steps;
$model->step_number = $step_number;
if ($model->save($_POST))
{
// Go to the next step
$step_number++;
Request::current()->redirect('wizard/step/'.$step_number);
}
}
catch (Some_Kind_Of_Exception $e)
{
$view->post = $_POST;
$view->errors = $e->errors();
}
}
$view->render();
}
}
?>
Hope this makes sense.

Categories