Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:84659 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 39751 invoked from network); 12 Mar 2015 23:36:17 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 12 Mar 2015 23:36:17 -0000 X-Host-Fingerprint: 194.166.179.101 194-166-179-101.adsl.highway.telekom.at Received: from [194.166.179.101] ([194.166.179.101:14689] helo=localhost.localdomain) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id B9/68-24603-1F222055 for ; Thu, 12 Mar 2015 18:36:17 -0500 Message-ID: To: internals@lists.php.net Date: Fri, 13 Mar 2015 00:36:12 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.5.0 MIME-Version: 1.0 References: <6D.2C.32765.10EC0055@pb1.pair.com> <5500D967.5040800@gmail.com> <13.69.64353.73451055@pb1.pair.com> <5501876C.3020107@gmail.com> <3D.85.42021.3E7A1055@pb1.pair.com> <5501B77D.5010800@gmail.com> <85.D1.24603.247C1055@pb1.pair.com> <5501D328.9040800@gmail.com> <98.26.24603.C9DE1055@pb1.pair.com> <5501F823.3060805@gmail.com> In-Reply-To: <5501F823.3060805@gmail.com> Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit X-Posted-By: 194.166.179.101 Subject: Re: [PHP-DEV] static constructor From: mail@deroetzi.de (Johannes Ott) Am 12.03.2015 um 21:33 schrieb Rowan Collins: > Johannes Ott wrote on 12/03/2015 19:45: >> All of the magic methods are doing like this. > > I thought you might say that, but the only thing remotely similar I can > think of is a destructor, which gets called when an object goes out of > scope; the others are all the implementation of, or instead of, some > specific piece of code: > __construct() runs every time for new Foo > __get() and __set() runs every time for property access with -> on an > undefined property > __call() and __callStatic() runs every time for method access on an > undefined property > > The difference with this is that you never know which part of your code > will actually trigger it, because it only runs some of the time. > I cannot see any difference because the trigger is defined really clear for each of the functions For Example Class Foo implements all of those functions the triggers would be __construct -> triggered each time I write "new Foo()" __destruct -> triggered each time a instance loses his last reference and is removed by garbage collection. __get() and __set() -> triggered each time I try to access a property of my foo instance which is not accessible (not defined or private) from outside Foo writing $foo->notfromoutside __call() -> triggered each time I try to call a non-static method on a instance of Foo which is not accessible from outside writing $foo->notfromoutside() __callStatic() -> triggered each time I try to access static method of the class which is not accessible from outside writing Foo::notfromoutside() And finally the proposed new one __static() -> triggered on the first time I write either "new Foo()" or "Foo::eachStaticPublicOrProtectedMethodOrProperty()" I really don't see what __static() has more "magic" or unclear trigger then each of the other so called magic methods?! Do keep away any doubt, in my opinion none of the so called magic methods have any kind of magic. They are just language patterns you should understand how they work before you start to use them in your code! So "private static function __static() {...}" is just another pattern you have to understand before you use. If you don't understand how it works you should not use. That is the same for all language patterns and elements, for example: loops, conditions, scalar-operators, etc. And as I already told a static class constructor is a kind of well-known standard function in mostly all modern OOP-languages. So professional OOP-developer should be or become very fast familiar with this pattern. > >> Okay thats a point I have to clearify I see, maybe my examples where to >> much shorten by me. In my examples I'm showing only the initialize parts >> of the classes, the initialized properties are used all over the class >> in static as well in non-static context. > > OK, so these are classes which share certain resources between all > instances, represented by these static properties. In that case, would > it be reasonable to say that the initialization of these properties > needs to happen the first time an instance is created? If so, the > if(self::$initialized) check needs to run only as often as > __construct(), not every time any method is called. > If you look closer at my example you will see, that the property is not just shared between instances (then the normal constructor Could be indeed be the place to initialize), but it is shared although with different static methods inside the class. > If the static methods are public, and used for something other than > managing instances (Factory / Singleton / etc), then are they really the > responsibility of the same class? i.e. do you have a Utility class > hiding in there pretending to be part of the instance/factory class? > > See my example 2 for a Config Wrapper class: abstract class Config { private static $arIni; private static function __static() { self::$arIni = parse_ini_file('config.ini'); //For example check whether all required fields are in the file or throw Exception if not. } public static function getHostname() { return self::$arIni['hostname']; } public static function getDBConnectionString() { return ' self::$arIni['dbdriver'] . '://' . self::$arIni['dbuser'] . '@' .self::$arIni['dbhost'] ...; } ... } The Config class is in response for all config Parameters inside config.ini and needs to read in this file before it can to it's work correctly. No hiding of Factory/Singleton/instance in this. And i although see no DI or Singleton pattern to use here to get the same functionality, if you want to use like Config::getHostname() and not like Config::getInstance()->getHostname() which is really unnecessary abstraction level for nothing in my opinion! >> For use case 1 the LogAdapter for example is a singleton instance per >> class, that's why in my understanding of dependcy Injection it is not >> working with it (correct me please if I'm wrong). And this singleton is >> used to log in all methods of the class/object. > > In a system using Dependency Injection, all dependencies have to be > passed in somewhere; a static dependency could be injected into a static > property by a static method, but it still has to come from somewhere. > Your implementation is pulling it from a global variable (or a public > static, which is effectively the same thing) rather than waiting for it > to be provided. > > Note also that you don't need to create 5 loggers just because you have > 5 objects needing a logger - you pass in (or acquire from a global > variable) references to the same instance each time - so the fact that > it's an instance property rather than a static one is really no hardship. > > public function __construct() { > $this->logger = LogAdapter::getLogger(self::class); > } > As I said above, if I only use in instance context I agree with the normal constructor. > >> Surely I would be able to get the specializied Singleton instance with >> doing some code like the following two examples: >> [examples trimmed] > > If not using any injection, I would implement it as follows: > > class A { > > private static $LOG; > > private static function getLogger() { > if (self::$LOG = null) { > self::$LOG = LogAdapter::getLogger(self::class); > } > return self::$LOG; > } > > public static function a() { > self::getLogger()->debug(); > .... > self::getLogger()->error(); > } > > public function b() { > ... > self::getLogger()->error(); > } > > ... > > } > > > Not much more verbose than your version with __static, but a lot less > magic, and therefore easier to debug. Slightly worse performance, with > the extra method call, but I doubt if it would add up to enough to worry > about. It is not only about the extra method-call but although the additional Null check inside this method call. Let's do some calculation for that: in average I have 5 calls of the logger per method the message is used 10 times during the programm. Now we already have 49 unnecessary method calls (with all needed to do for the interpreter like preparing stack, copying the returns etc.) and 40 unnecassary null checks inside (as we agreed before that is not counted fully, because the evaluation of the flag will although take some time but can be more efficient inside the interpreter). Let's now think only about 10 such methods we are already at a count of 490 unnecessary method calls. Maybe only slightly worse performance but it is a performance issue! And there is this very old performance rule: a lot of small performance issues can become quickly to a huge one. I have counted the calls in code of self::$LOG-> inside one small private webproject of mine with less logging implemented yet. But there are already 128 calls. If you multiply by 10 you already have 1280 calls on runtime, I think that is a performance issue. > > Thinking about it, you could even leave the way open for a different > strategy by using $this->getLogger() instead of self::getLogger() - > right now, that's 100% equivalent, but it makes it easier to change > later to use injection, or be able to override the logger on particular > instances, etc. Sorry I'm confused now after reading this sentence and I have to ask now, because I'm not sure anymore, whether you really have understand the difference between static and non-static context and the inheritance of classes inside PHP, especially the difference between $this, static, self? How can $this->getLogger() be equivalent to self::getLogger(); First contra Example: class A { private function getLoggerNonStatic() { echo 'A->getLoggerNonStatic'; } private static function getLoggerStatic() { echo 'A::getLoggerStatic'; } public static function call() { $this->getLogger(); } public static function call2() { self::getLogger(); } } A::call() //Fatal error $this in static context? A::call2() //Output A::getLoggerStatic; Second contra Example: class A { protected function getLoggerNonStatic() { echo 'A->getLoggerNonStatic()'; } protected function getLoggerStatic() { echo 'A::getLoggerStatic'; } public function call() { $this->getLoggerNonStatic(); self::getLoggerStatic(); static::getLoggerStatic(); } } class B extends A { protected function getLoggerNonStatic() { echo 'B->getLoggerNonStatic()'; } protected function getLoggerStatic() { echo 'B::getLoggerStatic'; } } $b = new B(); $b->call(); Output: B->getLoggerNonStatic(); A::getLoggerStatic(); B::getLoggerStatic(); As you can see line 1 and 2 of the output not equivalent even by class, but I want to log functionality of A as functionality of A not of B! > > This is often the problem with magic methods, be they at the language > level or in a framework: they tend to lock you into working in a > particular way, where more explicit code can be more readily refactored. > Beside the performance for me and for each clean coder I know (btw as a Software Architect I'm in charge of code quality of a huge Java project at the moment, so I think I know what I'm talking about), to read 100 times self::getLogger()->... and have to think about whether the getLogger() method (which should be by "standard" oop-naming conventions ONLY a three lines of code getter and nothing else) is doing the magic of getting a instance first, is much more worse then understanding once what a static class constructor does and then just use self::$LOG->... A "magic" getter identified by prefix get doing anything beside giving back a class member is really far away from clean code! Regards, -- DerOetzi