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.
Related
I see and use this syntax alot in Laravel, I wonder how it works, cause I try to integrate it in my own project but I get errors like this
FATAL ERROR Uncaught TypeError: Argument 1 passed to getVal() must be an instance of Man, none given, called in'
now here is Laravel type code as you all know
public function join(Request $request){
echo $request['name'];
//for instance
}
and it works fine, now here is my code:
class Man {
public $child,
$wife;
public function _construct(){
$this->child = 1;
$this->wife = 2;
}
}
function getVal(Man $man){
echo $man->wife;
}
getVal();
Please help me understand this better.
As the error says, the value passed to getVal() must be an instance of Man, so your last line of getVal() needs to have an instance of Man passed to it.
It can be any instance of man, or itself:
$man = new Man();
getVal($man)
would also work
as would:
$man = new Man();
$man2 = new Man();
getVal($man2)
This is a normal way of writing PHP code.
However, there may be something else going on in Laravel, if you could link the location in the source code, that'd be great.
Possibly wrong, but its piece of code, that could help you to find direction. Its not working, dunno how to return back to normal code.
<?php
set_exception_handler('myExcHandler');
function myExcHandler(Throwable $exception) {
$fr = new ReflectionFunction($exception->getTrace()[0]['function']);
$parameters = [];
$st = new MyStaticClasses();
foreach ($fr->getParameters() as $parameter) {
$parameters[] = $st->getClass($parameter->name);
}
return call_user_func_array($exception->getTrace()[0]['function'], $parameters);
}
class Man {
public $wife = 1;
}
class MyStaticClasses {
public $storage;
public function __construct() {
$this->storage['man'] = new Man();
}
public function getClass($name) {
return $this->storage[$name];
}
}
function getVal(Man $man) {
return $man->wife;
}
echo getVal();
I want to create an object of a class from a returned string but I am getting error Class **test_report** not found. My code:
public function display_report_builder($report_name = null)
{
$column_listing = new $report_name;// gets the test_report
return view('column_list')->with(['column_list_names' => $column_listing->columns]);
}
This isn't the better approach here. What you should do is to use a Factory design pattern:
class ReportFactory
{
public static function create($report_name)
{
switch($report_name) {
case 'test_report': return new TestReport();
default: throw new Exception('report not found');
}
}
}
Then you call with $column_listing = ReportFactory::create($report_name);
Why? Because you avoid "magic variables" with unknown data; you can trace errors properly; you can use namespace; you can extend functionalities easily, and easily activate or deactivate objects (or reports in this case); you have a cleaner code, and so on...
test if the class name (string) really is a valid class :
public function display_report_builder($report_name = null)
{
$column_list_names = null;
if (class_exists($report_name) && is_a($report_name, App\reports\test_report::class, true)) {
$column_listing = new $report_name;
$column_list_names = $column_listing->columns;
}
return view('column_list', compact('column_list_names'));
}
is_a() : Checks if the given object is of this class or has this class
as one of its parents.
I am trying to add functions to class from a separate file, I wonder if this could be possible!
$mClass = new MyClass();
$mClass->new_Functions[0](10); // Is there a way to have it in this form?
class myClass
{
private $Pvar = 5;
$new_Fcuntions;
function __construct()
{
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
}
}
[additional.functions.php] file
function operate($y)
{
return $this->Pvar * $y;
}
----- Edited ------- as it wasn't clear!
"additional.functions.php" is a module and there will be multiple modules to be added to the application, and every module could have more than single function and modules could call one another!
additional.functions.php [module file]
function operate($y)
{
return $this->Pvar * $y;
}
function do-more($foo)
{
return $this->operate(20) + $foo;
}
another.functions.php [another module]
function do-another($foo)
{
return $this->do-more(30) - $foo;
}
function add($foo, $bar)
{
return $foo + $bar;
}
appreciate every participation, its been a while since I am trying to maneuver around with it!
Is this possible or should I give up!
It looks to me like you are looking for Traits, which are a new feature as of PHP 5.4.0. Using traits, you can have snippets of code "mixed in" to other classes, a concept known as "horizontal reuse".
If you are not looking for traits, it's possible that you could do what you wanted with Runkit, however I would suggest staying as far away from it as possible, if you are not genuinely interested in PHP internals as well.
In any event, whatever you are trying to do is very interesting
I got it to work with dependency injection. The pvar has to be public or create a __get method to return the private variable. I also used the function name because it seems cleaner to me to use it via name rather than it's position in the list but if you want to keep that then just put $key where you see $value from the line: $this->function_list[$value] = ...
function operate($y, $that)
{
return $that->Pvar * $y;
}
class Example {
public $function_list = array();
private $Pvar = 5;
public function __construct()
{
$list = get_defined_functions();
$that = $this;
foreach ($list['user'] as $key => $value) {
$this->function_list[$value] = function() use ($value, $that) {
print call_user_func_array($value, array_merge(func_get_args(), array($that )));
};
}
}
public function __get($key)
{
if (isSet($this->$key)) {
return $this->$key;
} else {
throw new \Exception('Key "'.$key.'" does not exist');
}
}
}
$Ex = new Example();
$Ex->function_list['operate'](10);
If you want to extend MyClass from your modules (and not to initialize it, like in your example code), than you could do it in a way like this:
<?php
namespace modules\MyModuleA;
class MyClassExtension
{
private $MyObject;
public function __construct(\MyClass $MyObject)
{
$this->MyObject = $MyObject;
}
public function doSomething($anyParameter)
{
return $this->MyObject->doSomethingElse($anyParameter * 5, 42, 'foo');
}
}
And MyClass:
<?php
class MyClass extends \Extensible
{
// some code
}
abstract class Extensible
{
private $extensions = [];
public function extend($extension)
{
$this->extensions[] = $extension;
}
public function __call($methodName, $parameters)
{
foreach ($this->extensions as $Extension) {
if (in_array($methodName, get_class_methods($Extension))
return call_user_func_array([$Extension, $methodName], $parameters);
}
throw new \Exception('Call to undefined method ' . $methodName . '...');
}
public function hasExtension($extensionName)
{
return in_array($this->extensions, $extensionName);
}
}
And put it all together:
<?php
$moduleNames = ['MyModuleA', 'MyModuleB'];
$MyObject = new \MyClass;
foreach ($moduleNames as $moduleName) {
$className = '\\modules\\' . $moduleName . '\\MyClassExtension';
$module = new $className($MyObject);
$MyObject->extend($module);
}
// Now you can call a method, that has been added by MyModuleA:
$MyObject->doSomething(10);
You should add an interface for the extension classes of course...
The problem is: What happens if any code in your application calls a method of $MyObject, that is not there, because the module has not been loaded. You would always have to check if ($MyObject->hasExtension('ModuleA')) { ... }, but, of course, the application shouldn't be aware of any module. So I would not design an application in such a way.
I would suggest to use traits (mix-ins). See PHP reference
If you can have another class in that file instead of file with functions
- the best solution will be Traits
http://php.net/manual/en/language.oop5.traits.php
or using inheritance
If you move that code to class you can avoid a lot of unnecessary code. I mean:
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
It'll be e.g.:
class myClass extends MyBaseClassWithMyAwesomeFunctions
{
private $Pvar = 5;
}
Maybe this approach helps you:
In the files with the additional functions, don't define named functions, but return a closure, that expects (at least) the object (instance of MyClass) as parameter:
<?php
// additional.functions.php
return function ($myObject) {
$Object->multiplyPvar($myObject->getTheNumber());
$Object->doSomethingElse(42, 'foo');
};
The client, that builds MyClass collects those functions from the files into the array:
<?php
$files = [
'/path/to/my/additional.functions1.php',
'/path/to/my/additional.functions2.php'
];
$initFunctions = [];
foreach ($files as $path)
$initFunctions[] = include $path;
$MyObject = new \MyClass($initFunctions);
The constructor then calls those functions:
<?php
class MyClass
{
public function __construct(array $additionalInitFunctions)
{
foreach ($additionalInitFunctions as $additionalInitFunction)
$additionalInitializerFunction($this); // you can also add parameters of course
}
}
This way the class keeps very well testable as well as the function files. Maybe this could help you in any way. You should never ever think about modifying the internal (private) state of an object directly from any code from outside of the class. This is not testable! Think about writing tests before you implement your code (called "test driven development"). You will see, it is not possible to test a class, if you allow any code outside of that class to modify the internal (private) state of the class instance. And you don't want to have this. If you change some internal implementation detail in your class without breaking the unit test of that class, you will anyways probably break some code in any of your additional.functions.php files and no test will tell you: "Hey: you've broken something right now".
I'm just very slowly starting to sink into object-oriented programming, so please be gentle on me.
I have a custom class for Smarty that was partially borrowed. This is how the only example reflects the basic idea of using it across my current project:
class Template {
function Template() {
global $Smarty;
if (!isset($Smarty)) {
$Smarty = new Smarty;
}
}
public static function display($filename) {
global $Smarty;
if (!isset($Smarty)) {
Template::create();
}
$Smarty->display($filename);
}
Then in the PHP, I use the following to display templates based on the above example:
Template::display('head.tpl');
Template::display('category.tpl');
Template::display('footer.tpl');
I made the following example of code (see below) work across universally, so I wouldn't repeat the above lines (see 3 previous lines) all the time in each PHP file.
I would just like to set, e.g.:
Template::defauls();
that would load:
Template::display('head.tpl');
Template::display('template_name_that_would_correspond_with_php_file_name.tpl');
Template::display('footer.tpl');
As you can see Template::display('category.tpl'); will always be changing based on the PHP file, which name is corresponded with the template name, meaning, if for example, PHP file is named stackoverflow.php then the template for it would be stackoverflow.tpl.
I've tried my solution that have worked fine but I don't like it the way it looks (the way it's structured).
What I did was:
Assigned in config a var and called it $current_page_name (that derives the current PHP page name, like this: basename($_SERVER['PHP_SELF'], ".php"); ), which returned, for e.g.: category.
In PHP file I used Template::defaults($current_page_name);
In my custom Smarty class I added the following:
public static function defaults($template) {
global $Smarty;
global $msg;
global $note;
global $attention;
global $err;
if (!isset($Smarty)) {
Templates::create();
}
Templates::assign('msg', $msg);
Templates::assign('note', $note);
Templates::assign('attention', $attention);
Templates::assign('err', $err);
Templates::display('head.tpl');
Templates::display($template . '.tpl');
Templates::display('footer.tpl');
}
Is there a way to make it more concise and well structured? I know about Code Review but I would like you, guys, to take a good look at it.
This looks like you haven't loaded Smarty, that's why the error happens. You need to start by including Smarty before the class starts. If you follow my other config suggestion you should start by including that one as well.
In you Template class, just add the following function:
function defaults() {
// Don't know if you need the assignes, havn't used Smarty, but if so, insert them here...
Template::display( Config::get('header_template') ); //header_template set in the Config file
Template::display( basename($_SERVER['PHP_SELF'], ".php") . '.tpl' );
Template::display( Config::get('footer_template') ); //footer_template set in the Config file
}
Now you should be able to use it in any file:
$template = new Template();
$template->defaults();
EDIT:
A singleton is in every sense the same as a global, that will keep your same problem.
But your problem is that if you try to use one of the Template's static functions you are in the "static" mode, which means the constructor have not been run. And Smarty has not been assigned. If you want to go this road, you can do one of two thinks:
Make the Template a real singleton, meaning set the constructor to private add a function getInstance, that returns a instance of the class, and then use that object to call the functions in it (which should not be static), or
Make all those static functions check if smarty is set, and if it's not, create a new instance of smarty, otherwise use the one that already is instantiated to run its function.
EDIT 2:
Here's the proper way to make a singleton:
class Singleton {
private static $instance = null;
// private static $smarty = null;
private function __construct() {
//self::$smarty = new Smarty();
}
public static function getInstance() {
if( self::$instance === null ) {
self::$instance = self();
}
return self::$instance;
}
public function doSomething() {
//self::$smarty->doSomething();
}
}
It's used like this:
$singleton = Singletong::getInstance();
$singleton->doSomething();
I commented out the things you probably want do to to make this a singleton wrapper around a singleton Smarty object. Hope this helps.
EDIT 3:
Here's a working copy of your code:
class Template {
private static $smarty_instance;
private static $template_instance;
private function Template() {
self::$smarty_instance = new Smarty();
$this->create();
}
public static function getInstance() {
if( ! isset( self::$template_instance ) ) {
self::$template_instance = new self();
}
return self::$template_instance;
}
private function create() {
self::$smarty_instance->compile_check = true;
self::$smarty_instance->debugging = false;
self::$smarty_instance->compile_dir = "/home/docs/public_html/domain.org/tmp/tpls";
self::$smarty_instance->template_dir = "/home/docs/public_html/domain.org";
return true;
}
public function setType($type) {
self::$smarty_instance->type = $type;
}
public function assign($var, $value) {
self::$smarty_instance->assign($var, $value);
}
public function display($filename) {
self::$smarty_instance->display($filename);
}
public function fetch($filename) {
return self::$smarty_instance->fetch($filename);
}
public function defaults($filename) {
global $user_message;
global $user_notification;
global $user_attention;
global $user_error;
self::$smarty_instance->assign('user_message', $user_message);
self::$smarty_instance->assign('user_notification', $user_notification);
self::$smarty_instance->assign('user_attention', $user_attention);
self::$smarty_instance->assign('user_error', $user_error);
self::$smarty_instance->assign('current_page', $filename);
self::$smarty_instance->display('head.tpl');
self::$smarty_instance->display($filename . '.tpl');
self::$smarty_instance->display('footer.tpl');
}
}
When using this function, you should use it like this:
$template = Template::getInstance();
$template->defaults($filename);
Try it now.
You can get current file name in your defaults() function. Use this piece of code:
$currentFile = $_SERVER['REQUEST_URI'];
$parts = explode('/', $currentFile);
$fileName = array_pop($parts);
$viewName = str_replace('.php', '.tpl', $fileName);
$viewName is the name that you need.
This is a quick wrapper I made for Smarty, hope it gives you some ideas
class Template extends Smarty
{
public $template = null;
public $cache = null;
public $compile = null;
public function var($name, $value, $cache)
{
$this->assign($name, $value, $cache);
}
public function render($file, $extends = false)
{
$this->prep();
$pre = null;
$post = null;
if ($extends)
{
$pre = 'extends:';
$post = '|header.tpl|footer.tpl';
}
if ($this->prep())
{
return $this->display($pre . $file . $post);
}
}
public function prep()
{
if (!is_null($this->template))
{
$this->setTemplateDir($this->template);
return true;
}
if (!is_null($this->cache))
{
$this->setCacheDir($this->cache);
}
if (!is_null($this->compile))
{
$this->setCompileDir($this->compile);
return true;
}
return false;
}
}
Then you can use it like this
$view = new Template();
$view->template = 'path/to/template/';
$view->compile = 'path/to/compile/'
$view->cache = 'path/to/cache';
$view->assign('hello', 'world');
// or
$view->var('hello', 'world');
$view->render('index.tpl');
//or
$view->render('index.tpl', true); // for extends functionality
I did this kinda fast, but just to show you the basic ways you can use smarty. In a more complete version you could probably want to check to see if compile dir is writable, or if file templates exist etc.
After trying for few days to solve this simple problem, I have finally came up with working and fully satisfying solution. Remember, I'm just a newby in object-oriented programming and that's the main reason why it took so long.
My main idea was not to use global $Smarty in my initial code that worked already fine. I like to use my Smarty as just simple as entering, e.g.: Template::assign('array', $array). To display defaults, I came up with the trivial solution (read my initial post), where now it can be just used Template::defaults(p()); to display or assign anything that is repeated on each page of your project.
For doing that, I personally stopped on the following fully working solution:
function p() {
return basename($_SERVER['PHP_SELF'], ".php");
}
require('/smarty/Smarty.class.php');
class Template
{
private static $smarty;
static function Smarty()
{
if (!isset(self::$smarty)) {
self::$smarty = new Smarty();
self::Smarty()->compile_check = true;
self::Smarty()->debugging = false;
self::Smarty()->plugins_dir = array(
'/home/docs/public_html/domain.com/smarty/plugins',
'/home/docs/public_html/domain.com/extensions/smarty');
self::Smarty()->compile_dir = "/home/docs/public_html/domain.com/cache";
self::Smarty()->template_dir = "/home/docs/public_html/domain.org";
}
return self::$smarty;
}
public static function setType($type)
{
self::Smarty()->type = $type;
}
public static function assign($var, $value)
{
self::Smarty()->assign($var, $value);
}
public static function display($filename)
{
self::Smarty()->display($filename);
}
public static function fetch($filename)
{
self::Smarty()->fetch($filename);
}
public static function defaults($filename)
{
Template::assign('current_page_name', $filename);
Template::display('head.tpl');
Template::display($filename . '.tpl');
Template::display('footer.tpl');
}
}
Please use it if you like it in your projects but leave comments under this post if you think I could improve it or you have any suggestions.
Initial idea of doing all of that was learning and exercising in writing a PHP code in object-oriented style.
I'm running PHP 5.3.6 with MAMP 2.0.1 on Mac OS X Lion and I'm using Kohana 3.1.3.1.
I try to override the __set() and __get() methods of the standard Kohana ORM module with my own methods. Some of my models extend my class which extends the ORM class.
<?php
class ORM_Localization extends ORM
{
protected static $_language_cache = array();
protected $_languages = array();
public function __construct($id = NULL)
{
parent::__construct($id);
// Load languages which are accepted by the client
$this->_languages = array_keys(Application::languages());
}
public function __get($name)
{
/* Debugging #1 */ echo 'GET'.$name;
/* Debugging #2 */ // exit;
return $this->localization($name);
}
public function __set($name, $value)
{
/* Debugging #3 */ var_dump($this->_table_columns);
/* Debugging #4 */ echo 'SET::'.$name;
/* Debugging #5 */ // exit;
$this->localization($name, $value);
}
public function localization($name, $value = NULL)
{
if (!array_key_exists($name, $this->_table_columns)) {
// Load avaiable languages from database if not already done
if (empty(ORM_Localization::$_language_cache)) {
ORM_Localization::$_language_cache = ORM::factory('languages')
->find_all()
->as_array('id', 'identifier');
}
$languages = array();
// Find the IDs of the languages from the database which match the given language
foreach ($this->languages as $language) {
$parts = explode('-', $language);
do {
$identifier = implode('-', $parts);
if ($id = array_search($identifier, ORM_Localization::$_language_cache)) {
$languages[] = $id;
}
array_pop($parts);
} while($parts);
}
// Find localization
foreach ($languages as $language) {
$localization = $this->localizations->where('language_id', '=', $language)->find();
try {
if ($value === NULL) {
return $localization->$name;
} else {
$localization->$name = $value;
}
} catch (Kohana_Exception $exception) {}
}
}
if ($value === NULL) {
return parent::__get($name);
} else {
parent::__set($name, $value);
}
}
}
But in PHP 5.3.6, I get the following error message:
Exception [ 0 ]: Could not execute Model_Hotel::__construct()
Model_Hotel extends this class and does not have an own construct.
This is the code of Model_Hotel, a standard Kohana ORM model: http://pastie.org/private/vuyig90phwqr9f34crwg
With PHP 5.2.17, I get another one:
ErrorException [ Warning ]: array_key_exists() [function.array-key-exists]: The second argument should be either an array or an object
In Kohana, my model which extends my class is called by mysql_fetch_object(), somewhere deep in the orm modules code.
However, if I echo the called property in __set() and exit (#4 and #5) afterwards, it outputs "SET::id" and doesn't show the error message.
If I var_dump() $this->_table_columns (or any other property of this class, #3), I get "NULL" which is the value that this property has before it's initialized.
If I repeat the same with $this->_languages, I get an empty array which should be filled with several languages. It's like the class was never initialized with the __construct. This explains the error which I get in PHP 5.2.17, because $this->_table_columns is NULL and not an array.
I can uncomment my __construct and I still get the same error, the fault has to be in my localization() method.
I searched for several days now and I have absolutely no idea what could be wrong.
ORM::__set() is expected to load cast data from mysql_fetch_object() call. You must allow it to set casted value for this cause:
public function __set($column, $value)
{
if ( ! isset($this->_object_name))
{
// Object not yet constructed, so we're loading data from a database call cast
$this->_cast_data[$column] = $value;
}
else
{
// Set the model's column to given value
$this->localization($column, $value);
}
}
Altough I don't like this solution either, you should be overriding ORM::set() instead.
__construct() is called automatically after initialization of an object.
$class = new Object;
I have a bit more information on this question, as I ran into the same problem.
1) I found a PHP bug report that documents that in mysqli and mysql_fetch_object __set() is called before __construct(). The report is from 2009 and supposedly was fixed, but in the version of 5.3 I'm running it still seems to happen. Here it is: https://bugs.php.net/bug.php?id=48487
2) If, in the process of executing your __set() method you throw an exception or some other error occurs you'll get this vague "can't call X::__construct()" message instead of the real exception/error message.
I could resolve my problem thusly:
public function __set($key, $val){
if($constructor_has_not_run){
$this->__construct();
}
//do stuff
}
I'm a bit lucky in my case because I can let the constructor run twice without any problems, but you might not be in the same boat.