I'm currently in the process of moving from our own proprietary logging solution to log4php.
We use a lot of classes with only static methods in our project. The documentation defines the basic use case like:
class MyClass {
private $logger;
public function __construct() {
$this->logger = Logger::getLogger(__CLASS__);
$this->logger->debug('currently in constructor');
}
}
But I can't use that, cause I need $logger to be available in a static context as well. Making $logger static as well doesn't help either, because the constructor for my class is never called (as all its members are static).
The documentation tells me to use a static initializer for that member then. But then I would have to remember to call that for all classes I use. And that seems too error-prone.
So I came up with this:
class Foo {
private static $logger = null;
private static function logger() {
if( null == self::$logger ) self::$logger = Logger::getLogger( __CLASS__ );
return self::$logger;
}
public static function bar() {
self::logger()->debug( "test" );
}
}
Foo::bar();
But that seems like too much overhead as well. So, any suggestions?
I came up with one solution that works quite well but requires $logger to be public.
class Foo {
public static $logger = null;
public static function bar() {
self::$logger->debug( "test" );
}
}
$loggerName = "logger";
// Iterate over all declared classes
$classes = get_declared_classes();
foreach( $classes as $class ) {
$reflection = new ReflectionClass( $class );
// If the class is internally defined by PHP or has no property called "logger", skip it.
if( $reflection->isInternal() || !$reflection->hasProperty( $loggerName ) ) continue;
// Get information regarding the "logger" property of this class.
$property = new ReflectionProperty( $class, $loggerName );
// If the "logger" property is not static or not public, then it is not the one we are interested in. Skip this class.
if( !$property->isStatic() || !$property->isPublic() ) continue;
// Initialize the logger for this class.
$reflection->setStaticPropertyValue( $loggerName, Logger::getLogger( $class ) );
}
This I only have to define the $logger property once per class and run my initialization code once (I guess after the require_once section of the entry point of my application).
The performance impact of that code is negligible, especially since it is only run once (compared to my initial solution). This is what I measured inside a VirtualBox VM on an Intel Core2 Q9450 #2.66GHz:
10000 iterations for 157 classes completed in 2.6794s. Average per iteration: 0.00026794s
Related
I have only basic PHP knowledge and am reading the book "PHP 5 E-commerce Development.pdf" which code source can be found here: https://github.com/rogeriorps/books_demo.
I am right at the beginning, on the creation of the registry with "objects" such as database handling, authentication and template sending.
I have a problem with the last line of code of this function, in a class that is a singleton and has objects:
public function storeObject( $object, $key )
{
if( strpos( $object, 'database' ) !== false ) {
$object = str_replace( '.database', 'database', $object);
require_once('databaseobjects/' . $object
. '.database.class.php');
} else {
require_once('objects/' . $object . '.class.php');
}
self::$objects[ $key ] = new $object( self::$instance );
}
Well, for the authentication class for instance, the constructor is empty: public
function __construct() { }
So it would require authentication.class.php and then create a new authentification(self::$instance)... On a constructor has no arguments!
How is that possible? What bothers me is the use of the word new, which normally calls the empty constructor, and gives it arguments out of the blue.
Any further explanations about how this all works are welcome as well, thank you :-)
PHP is a quite forgiving language, in that certain language constructs and practices are not as strictly applied as in other programming languages.
PHP does not complain if you provide more parameters than a class method expects, whether that method is a costructor or regular method. See below, which outputs "Hello World!" just fine:
error_reporting(E_ALL);
ini_set('display_errors', TRUE);
ini_set('display_startup_errors', TRUE);
class Foo
{
public function __construct() {}
public function hello($input) { return 'Hello ' . $input . '!'; }
}
$foo = new Foo(123);
echo $foo->hello('World', 'Universe');
I'm still trying to understand what your question is.
While the link to the source code is good, can you be more specific as to what files you are talking about.
Is this the line you are referring too? :
//I will call this class 'Singleton',as I have no idea what it's name is.
class Singleton
{
protected static $instance;
protected static $objects = [];
...
public function storeObject( $object, $key )
{
...
self::$objects[ $key ] = new $object( self::$instance ); //<--- this line
...
}
}
And then you say you have a class like this ( with an empty constructor )
class authenticate{
public function __construct() {} //empty
}
IF I follow that right, the extra argument is ignored in this case. However, consider having another class that can be stored in Singleton
class user{
//instance of singleton
protected $singleton;
public function __construct( $Singleton ) {
$this->singleton = $Singleton;
}
}
So in this case the same class Singlton calls a different class that does accept an instance of Singlton.
This is what I would call a form of Polymorphism.
Polymorphism is the provision of a single interface to entities of different types.
Personally I would prefer they actually have an interface for this. I will try to explain this as best I can. An interface can be thought of like a contract, it guarantees that any class implementing it will expose some public methods, with given arguments, in a specific order.
For example this
interface StorableObjectInterface{
//interfaces do not contain a method body, only the definition
//here I am using type hinting to tell it to only accept instance of Singleton
public function __construct( Singleton $Singleton );
}
So what this does is require that every class that implements this interface requires an instance of singleton as it's constructors first argument. They can still ignore it, but it should be a contractual obligation of being called from Singleton (IMO).
Then your clasess would look like this
class authenticate implements StorableObjectInterface{
public function __construct(Singleton $Singleton) {} // still empty
}
class user implements StorableObjectInterface{
//instance of singlton
protected $singlton;
public function __construct(Singleton $Singleton ) {
$this->singlton = $Singleton;
}
}
And then to lock it all together in Singleton you would check that $Object implements the interface.
public function storeObject( $object, $key )
{
...
if( is_a( $object, StorableObjectInterface::class ){
self::$objects[ $key ] = new $object( self::$instance );
}else{
//throw exception
throw new Exception("Class {$object} must implement interface ".StorableObjectInterface::class);
}
...
}
This is the way I would do it... It wasn't clear if you are just Using someones system, or creating your own.
So you may wonder why go through all this trouble, I'll give you an example.
Say later on you may need something like a path to a config file in authenticate, so you can easily load you credentials etc.
So you look in that class and see this ( we'll forget we know what we know )
class authenticate{
public function __construct() {} // still empty
}
So you figure you can just tack it in the constructor ( say you were using this class somewhere outside of Singlton ). So you change it.
class authenticate{
protected $config;
public function __construct($configFile = null) {
if( $configFile )
$this->config = include $configFile;
}
}
//then you call it for you new code
$z = new authenticate('passwordsAndStuf.php');
This is all fine until Singleton calls that constructor with an instance of itself. Now everything blows up. The main issue is that just looking at authenticate there is no way to tell this is going to happen. So by adding an Interface we are making a contract with Singleton any class implementing this interface will always accept an instance ofSingleton` as the first argument.
Hope that makes sense.
Previously a class which I'm now rebuilding had a member variable $settings which was an array of settings, strangely enough.
class MyClass {
public $settings = array();
public function __construct() {
if( empty( $this->settings ) ) {
$this->settings = require( 'settings.php' ); // e.g. return array('setting1'=>4);
}
}
}
These settings were accessed by $object->settings['keyname'];
The means by which these keys are accessed has been moved into a method now. However, the application itself is riddled with calls to $object->settings['keyname']. I was wondering is there a way which I can catch any calls to the $settings member variable and return it using the new function.
I've looked at __get($name) but $name only contains settings rather than the array key which I need. What I'd need to pass would be the keyname to the my $object->get() method.
The reason I want to do this is so that I can trigger errors in a log file showing me where the deprecated calls to $object->settings[] are without breaking the application. Obviously setting $setting to private would give me lots of fatal errors and I could work through but there are multiple developers working on this codebase which I'd prefer not to break. If I could implement this as a temporary solution it'd help.
I realise there are repositories etc which we could use so that I could work on it separately and check it in afterwards but I'm looking for a quick, temporary solution as we're porting our codebase to Git soonish.
Totally possible:
<?php
class Logger {
public function log( $name, $backtrace ) {
/**
* $name is the name of the array index that was called on
* MyClass->settings( ). $backtrace contains an array in which
* you can find and determine which file has accessed that index,
* and on which line.
*/
$last = array_shift( $backtrace );
$message = sprintf( "Setting '%s' was called by file '%s' on line %d",
$name,
$last['file'],
$last['line']
);
echo $message;
}
}
class MyClass {
protected $settings;
public function __construct( ) {
$this->settings = new Settings( new Logger( ), array( 'foo' => 'bar' ) );
}
public function __get( $name ) {
if( $name === 'settings' ) {
return $this->settings;
}
}
}
class Settings extends ArrayObject {
protected $logger;
protected $settings;
public function __construct( Logger $logger, array $settings ) {
$this->logger = $logger;
parent::__construct( $settings );
}
public function offsetGet( $name ) {
$backtrace = debug_backtrace( );
$this->logger->log( $name, $backtrace );
return parent::offsetGet( $name );
}
}
$myClass = new MyClass( );
echo $myClass->settings['foo'] . "\n";
Sure, it's hackish, and you probably don't want to keep this in your codebase, but for logging deprecated uses, it might be extremely helpful. Just log for a set period of time, and then replace the $this->settings = new Settings( ) with $this->settings = array( ).
By the way, the output of that exact code is the following:
berry#berry-pc:~/Desktop% php foo.php
Setting 'foo' was called by file '/home/berry/Desktop/foo.php' on line 53
bar
To find where scripts SET a variable in settings:
I would suggest putting a tiny dirty hack in the constructor and the destructor of your MyClass class. In the constructor scan the $settings and check if a deprecated value exists (save it somewhere temp). In the destructor do the same check, and cross match the two results. When you have new variables during the destruction you know that the $_SERVER['PHP_SELF'] has set it.
To find where scripts access it:
Brutal, but I would recommend just converting all deprecated values to new ones, and watch things stop working. You'll spend less time tracking down what broke, and reading the "This used to work and doesn't" complaints, than you will modifying your class to use a logger
I have a webapplication where I use a registry class. The registry class holds important classes that I need troughout my application.
I have made the registry class a Singleton class and this class is static.
The content of the registry class is here:
<?php
class registry
{
private static $objects = array();
private static $instance;
private function __construct(){}
private function __clone(){}
public static function singleton()
{
if( !isset( self::$instance ) )
{
self::$instance = new registry();
}
return self::$instance;
}
public function storeObjects()
{
$Objects = array_merge($GLOBALS['EXTRA_LIBS'],array('data','page','session','uri','loader','modules'));
foreach($Objects as $name)
{
$this->$name = $name;
}
}
public function __set( $object, $key )
{
require_once(__LIBRARIES . DS . $object . '.class.php');
self::$objects[ $key ] = new $object( self::$instance );
}
public function __get( $key )
{
if( is_object ( self::$objects[ $key ] ) )
{
return self::$objects[ $key ];
}
}
public function returnAllObjects()
{
return self::$objects;
}
}
?>
Now whenever I want to use one of the classes here in my application I do:
registry::singleton()->data->adddata('somedata');
I use the classes a lot in my application, minimum of 1 time in every method.
Now my question is what is the best thing to do:
1) call the whole thing everytime
2) Class $registry = registry::singleton(); one time in every method and then just use the local variable. (I do not know if this will work)
Or is there a more elegant method to tackle this problem.
Whether you assign the registry instance to a local variable or not is irrelevant. If this is your only concern, use what makes the code more readable in the specific block of code.
I'd be more concerned about the implications of having to hardcode the Registry classname into my other classes and whether I really need a Singleton for the Registry. Have a look at Dependency Injection.
(2) would work just fine, try it. If you're using it a few times in each method it will save you some typing.
You could consider adding some sanity checking in __set() to make sure the file exists before require_once, as well as logging and/or returning an error if it doesn't (same for __get()).
I have an helper class with some static functions. All the functions in the class require a ‘heavy’ initialization function to run once (as if it were a constructor).
Is there a good practice for achieving this?
The only thing I thought of was calling an init function, and breaking its flow if it has already run once (using a static $initialized var). The problem is that I need to call it on every one of the class’s functions.
Sounds like you'd be better served by a singleton rather than a bunch of static methods
class Singleton
{
/**
*
* #var Singleton
*/
private static $instance;
private function __construct()
{
// Your "heavy" initialization stuff here
}
public static function getInstance()
{
if ( is_null( self::$instance ) )
{
self::$instance = new self();
}
return self::$instance;
}
public function someMethod1()
{
// whatever
}
public function someMethod2()
{
// whatever
}
}
And then, in usage
// As opposed to this
Singleton::someMethod1();
// You'd do this
Singleton::getInstance()->someMethod1();
// file Foo.php
class Foo
{
static function init() { /* ... */ }
}
Foo::init();
This way, the initialization happens when the class file is included. You can make sure this only happens when necessary (and only once) by using autoloading.
Actually, I use a public static method __init__() on my static classes that require initialization (or at least need to execute some code). Then, in my autoloader, when it loads a class it checks is_callable($class, '__init__'). If it is, it calls that method. Quick, simple and effective...
NOTE: This is exactly what OP said they did. (But didn't show code for.) I show the details here, so that you can compare it to the accepted answer. My point is that OP's original instinct was, IMHO, better than the answer he accepted.
Given how highly upvoted the accepted answer is, I'd like to point out the "naive" answer to one-time initialization of static methods, is hardly more code than that implementation of Singleton -- and has an essential advantage.
final class MyClass {
public static function someMethod1() {
MyClass::init();
// whatever
}
public static function someMethod2() {
MyClass::init();
// whatever
}
private static $didInit = false;
private static function init() {
if (!self::$didInit) {
self::$didInit = true;
// one-time init code.
}
}
// private, so can't create an instance.
private function __construct() {
// Nothing to do - there are no instances.
}
}
The advantage of this approach, is that you get to call with the straightforward static function syntax:
MyClass::someMethod1();
Contrast it to the calls required by the accepted answer:
MyClass::getInstance->someMethod1();
As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.
If you are NOT using PHP 7.4's opcode.cache, then use Victor Nicollet's answer. Simple. No extra coding required. No "advanced" coding to understand. (I recommend including FrancescoMM's comment, to make sure "init" will never execute twice.) See Szczepan's explanation of why Victor's technique won't work with opcode.cache.
If you ARE using opcode.cache, then AFAIK my answer is as clean as you can get. The cost is simply adding the line MyClass::init(); at start of every public method. NOTE: If you want public properties, code them as a get / set pair of methods, so that you have a place to add that init call.
(Private members do NOT need that init call, as they are not reachable from the outside - so some public method has already been called, by the time execution reaches the private member.)
There is a way to call the init() method once and forbid it's usage, you can turn the function into private initializer and ivoke it after class declaration like this:
class Example {
private static function init() {
// do whatever needed for class initialization
}
}
(static function () {
static::init();
})->bindTo(null, Example::class)();
I am posting this as an answer because this is very important as of PHP 7.4.
The opcache.preload mechanism of PHP 7.4 makes it possible to preload opcodes for classes. If you use it to preload a file that contains a class definition and some side effects, then classes defined in that file will "exist" for all subsequent scripts executed by this FPM server and its workers, but the side effects will not be in effect, and the autoloader will not require the file containing them because the class already "exists". This completely defeats any and all static initialization techniques that rely on executing top-level code in the file that contains the class definition.
If you don't like public static initializer, reflection can be a workaround.
<?php
class LanguageUtility
{
public static function initializeClass($class)
{
try
{
// Get a static method named 'initialize'. If not found,
// ReflectionMethod() will throw a ReflectionException.
$ref = new \ReflectionMethod($class, 'initialize');
// The 'initialize' method is probably 'private'.
// Make it accessible before calling 'invoke'.
// Note that 'setAccessible' is not available
// before PHP version 5.3.2.
$ref->setAccessible(true);
// Execute the 'initialize' method.
$ref->invoke(null);
}
catch (Exception $e)
{
}
}
}
class MyClass
{
private static function initialize()
{
}
}
LanguageUtility::initializeClass('MyClass');
?>
Some tests of assigning static public properties :
settings.json :
{
"HOST": "website.com",
"NB_FOR_PAGINA": 8,
"DEF_ARR_SIZES": {
"min": 600,
"max": 1200
},
"TOKEN_TIME": 3600,
"WEBSITE_TITLE": "My website title"
}
now we want to add settings public static properties to our class
class test {
/** prepare an array to store datas */
public static $datas = array();
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::$datas[$key] = $value;
}
}
/**
*
*/
/**
* test::get_static_properties($class_name);
*
* #param {type} $class_name
* #return {log} return all static properties of API object
*/
public static function get_static_properties($class_name) {
$class = new ReflectionClass($class_name);
echo '<b>infos Class : '.$class->name.'</b><br>';
$staticMembers = $class->getStaticProperties();
foreach( $staticMembers as $key => $value ){
echo '<pre>';
echo $key. ' -> ';
if( is_array($value) ){
var_export($value);
}
else if( is_bool($value) ){
var_export($value);
}
else{
echo $value;
}
echo '</pre>';
}
// end foreach
}
/**
* END test::get_static_properties();
*/
}
// end class test
ok now we test this code :
// consider we have the class test in API folder
spl_autoload_register(function ($class){
// call path to API folder after
$path_API = dirname(__DIR__).'/API/' . $class . '.php';
if( file_exists($path_API) ) require $path_API;
});
// end SPL auto registrer
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
var_dump(test::$datas['HOST']);
this return :
infos Class : test
datas -> array (
'HOST' => 'website.com',
'NB_FOR_PAGINA' => 8,
'DEF_ARR_SIZES' =>
array (
'min' => 600,
'max' => 1200,
),
'TOKEN_TIME' => 3600,
'WEBSITE_TITLE' => 'My website title'
)
// var_dump(test::$HOST);
Uncaught Error: Access to undeclared static property:
test::$HOST
// var_dump(test::$datas['HOST']);
website.com
Then if we modify the class test like this :
class test {
/** Determine empty public static properties */
public static $HOST;
public static $NB_FOR_PAGINA;
public static $DEF_ARR_SIZES;
public static $TOKEN_TIME;
public static $WEBSITE_TITLE;
/**
* test::init();
*/
public static function init(){
// get json file to init.
$get_json_settings =
file_get_contents(dirname(__DIR__).'/API/settings.json');
$SETTINGS = json_decode($get_json_settings, true);
foreach( $SETTINGS as $key => $value ){
// set public static properties
self::${$key} = $value;
}
}
/**
*
*/
...
}
// end class test
// init class test with dynamics static properties
test::init();
test::get_static_properties('test');
var_dump(test::$HOST);
this return :
infos Class : test
HOST -> website.com
NB_FOR_PAGINA -> 8
DEF_ARR_SIZES -> array (
'min' => 600,
'max' => 1200,
)
TOKEN_TIME -> 3600
WEBSITE_TITLE -> My website title
// var_dump(test::$HOST);
website.com
I actually need to initialize an object with public static properties that I will reuse in many other classes, which I think is supposed to, I don't want to do new api() in every method where I would need, for example to check the host of the site or indicate it. Also I would like to make things more dynamic so that I can add as many settings as I want to my API, without having to declare them in my initialization class.
All other methods I've seen no longer work under php > 7.4
I keep looking for a solution for this problem.
Note - the RFC proposing this is still in the draft state.
class Singleton
{
private static function __static()
{
//...
}
//...
}
proposed for PHP 7.x (see https://wiki.php.net/rfc/static_class_constructor )
I am working with PHP classes and objects now. In this question the names of fields and methods are made up just so you get an idea of what I am talking about.
It is related to using the singleton and registry design patterns.
Now lets say I need to access a Database object, Cache Object, Core Settings object, Session object in almost every other class I will need to have access to these. SO I would use a registry to store all 4 of those object into 1 registry class object. I could then easiyl just pass in my 1 object into any other object that needs to access these. So that sounds great so far but what if I have some classes that do not need all 4 of those objects, what If I ONLY need to access the Database object or the Session object in some of my other classes? For perfromance would it be best to just use a singleton inside these other objects or would it be the same to go ahead and use my registry in these?
I do not know well enough how the objects work in PHP to know if there would be any sort of performnce gain (less memory usage, cpu usage, load time).
So anyone with experience in this maybe can tell me if there would be any gain using one or the other, I am at the stage where I can go ether way without it affecting production time or anything. I would like to use the best method if I can now.
That depends on your application. If you still need 3 out of the 4 classes, then it'd be more ideal to use the Registry than to handle the 3 independently only because you don't need the fourth. Loading the classes lazily would be one approach to reduce memory footprint, but then you need to instruct the registry when to create the objects and that's not much different than handling singletons. Alternatively, you could create an n-parameter constructor or use an array to instruct your Registry which classes to instantiate during construction.
class Registry {
public $class1;
public $class2;
function __construct($uses) {
foreach($uses as $class) {
$this->{$class} = new {$class}();
}
}
}
Then instantiate your Registry by specifying which classes to instantiate.
$reg = new Registry(array('class1'));
You would obviously want your constructor to handle zero parameters to account for instantiating all classes by default.
You can implement lazy loading to only load the objects you really need:
class Registry
{
private static $database = null;
private static function connectDatabase($key)
{
[... do connection stuff ...]
}
public static function getDatabase($key)
{
if (Registry::$database == null)
{
Registry::connectDatabase($key);
}
return Registry::$database;
}
}
The code to register the database connection parameters is left as exercise to the reader.
Perhaps this is the proper Singleton-Registry pattern. OFC, you can implement different things, SplFixedArray, ArrayAccess interface, and others. Also it is not bad to add the destruct and destroy inner objects to ensure no leak possible.
class oRegistry{
private static $instance = null;
private $storage = array();
private function __construct(){}
private function __clone(){}
public static function getInstance(){
if( self::$instance === null ){
self::$instance = new self();
}
return self::$instance;
}
public function attach($name, $o) {
if( true === isset($this->storage[$name]) ) {
throw new Exception('The instance with name '.$name.' already exists in registry.');
}
if( !empty( $name ) ) {
$this->storage[ $name ] = $o;
}
}
public function detach( $name ){
if( isset( $this->storage[ $name ] ) ) {
$this->storage[ $name ] = null;
unset( $this->storage[ $name ] );
}
}
public function get( $name ){
if( false === isset( $this->storage[$name] ) ) {
throw new Exception('Invalid instance requested');
}
return $this->storage[ $name ];
}
}
// usage example
$storage = oRegistry::getInstance();
$obj = new stdClass;
$obj2 = new stdClass;
$obj->test1 = 'test';
$obj2->test2 = 't2';
$storage->attach( 'test1', $obj );
$storage->attach( 'test2', $obj2 );
$got = $storage->get( 'test2' );
var_dump($got); // object(stdClass)#3 (1) { ["test2"]=> string(2) "t2" }
$storage->detach( 'test2' );
$got = $storage->get( 'test2' );
var_dump($got); // bool(false)