Php конструктор статического класса

Обновлено: 08.05.2024

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.

Future readers: Here are code details and a discussion of the approach user258626 said he was thinking of doing. Please compare it to the accepted answer. Decide which you'd rather have. Or do one of the other answers; I am suggesting you not blindly adopt the accepted answer. Key point: As a general principle, it is best to pay the coding price once, when you code a class, to keep callers simpler.

I wish we could refactor SO, put the accepted answer into a new question “What does the Singleton pattern look like in PHP?” (for which it’s an excellent answer) and make user258626’s answer (or something like it) the accepted answer to this question.

9 Answers 9

Sounds like you'd be better served by a singleton rather than a bunch of static methods

And then, in usage

I want to -1 (but I won't) for private constructor and getInstance() . You're going to make it VERY hard to test effectively. At least make it protected so that you have options.

@ircmaxell - you're just talking about issues with the singleton pattern itself, really. And code posted by anybody on SO shouldn't be considered authoritative - especially simple examples that are only meant to be illustrative. Everyone's scenarios and situations are different

20 whole lines. Jeez, doesn't the author of this answer know that lines of code are a precious resource. They don't grow on trees ya know!

@PeterBailey Lines of code that don't accomplish anything but glue are a distraction and makes code less maintainable.

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.

@VictorNicollet, this is ugly. Your code makes init a public method, and it wouldn't work if it's private. Isn't there a cleaner way like the java static class initializer?

@Pacerier if init() does nothing the second time it is invoked, it really doesn't matter if it is public. static function init() < if(self::$inited) return; /* . */ >

@Pacerier the end result of any constructor or initializer that accepts arguments is ingesting out-of-scope data into the class. you've got to handle it somewhere.

This is incompatible with opcache.preload of PHP 7.4. If the file is preloaded in the preload script, the class will "exist", but not the effects of top-level code in that file - and autoload will NOT require the file because the class exists, and you won't require it either because it would cause the class to be redefined!

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.

That would be my suggestion too. I did the same in the past but called it __initStatic() . It feels like a thing PHP needs, knowing Java.

@iautomation Didn't tried it but this is worth to be placed in an own answer! It is a straightforward and modern approach.

For those working in a professional production environment where composer is bile of the internet. this answer works very well.

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.

The advantage of this approach, is that you get to call with the straightforward static function syntax:

Contrast it to the calls required by the accepted answer:

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.)

Objects are stateful by the state of all of their non-static and static properties. The same is for classes themselve for being stateful by the state of all of their static properties. Everytime you have a state, there may be the need to initialize this state before usage. While there is already a language method for object state to be initialised with the object constructor, there is no such method for the state of a class.

While the creation of a instance is explicitly done from outside or inside the class by calling the public constructor, the creation of a class itself is implicitly done by the language on loading time. That's why the initialization of the state of a class, which is needed by the class to work correctly, should be in the responsibility of the class itself, called by the language not by any user-code.

As there a several implementations in user-land, which load all classes not on demand but at the beginning of run, the loading time itself of each class is not the correct moment to do the initialization, because of very much unnecessary calculation and resources usage maybe done for never used classes in current run. To avoid such unnecessary things, the right moment to initialize is directly before the first call to a class.

As I will show next, there are several user-land patterns, which more or less meet all of these requirements, but in my opinions they could be done a nicer way by introducing a static class constructor (cctor) to PHP as known by other OOP-languages as well:

Example 1

Fullfills all requirements, but can be done with less user-code and a little bit better performance by moving the “be initialized” checks to the interpreter:

Example 2

Fullfills the requirements except that the responsibility of needed initialization is outside of the class and only works in applications using autoloader

Example 3

Gives again the responsibility of initialization to outside of the class and does not avoid unnecessary calls to never used classes for application with no autoloader.

Discussions

For already having a huge amount of discussion points on this proposal on the internal mailing list I decided to make a seperated discussion section below. There I will try to list all already discussed points and my opinion to them.

Proposal

The proposal is to introduce a new magic method to classes implementing the above defined requirements for a static class constructor.

Code example of the method:

Method details and explanation for decisions:

- Accesibility: private To keep the responsibility encapsulated into the class

- Context: static For being inside class context

- Parameters: void Because the language calling it, does not know any parameters

- Throws: Exception in case of an error

- Trigger for “magic” method call: First call to class, either first call to __construct(. ) or first call to any public or protected static method or property of the class

Proposed PHP Version(s)

RFC Impact

There should be no impact to any libraries or frameworks that follows the userland coding conventions.

Open Issues

At pre-rfc-discussion there was the question whether following code is valid:

What is sure for me that the static class constructor should be able to throw a catchable Exception for user error handling, if it is not possible to initialize. The open issue for me is, what the behavior on recalling the static class constructor afterward is. There are two possible behaviors I do not know which one I really would prefer so far.

Two possible behaviors:

1. On recall of the static class constructor a fatal error is raised to ensure it is only called once and initialized before exception was thrown properties will not be reinitialized. This will prohibit programmers to repair the situation and afterwards to retry.

2. The static class constructor is recalled as long as it ends normally, without throwing a exception. This could lead to deadlock situations in user-code.

As long as behavior two gives more opportunties to the programmer I slightly prefer that one, but there may be enough arguments for behavior one as well. If we cannot clearify this in pre-vote discussion, I will do a seperated vote on this implementation detail.

Unaffected PHP Functionality

Global functions and non-static class methods beside the __construct(. )

Future Scope

- As you can see in discussion section, may be there is the need of a class destructor as well, but at the moment this is not part of this proposal.

- Maybe it can be useful to make a classloading ini-file, which is evaluated directly by the interpreter, for example for factory-classes, giving back different types of instances on such configuration. Or past some configuration from the php.ini to some libraries or extensions. For this purpose it may be useful in future to have input parameters.

Proposed Voting Choices

1. Proposal requires 2/3 majority for being a language change.

(2. Optional see open issues) For being a implementation detail of the proposal 50%+1 majority

Discussions

In this section I will try to summarize all discussion points and will try to figure out how in my personal opinion they really touch the proposal or not.

1. Crucial code and complexitiy argument

An argument I read several times is that within being inside the static context there could be some “crucial” things be done, for example opening and storing some resource handles to static properties, which means they couldn't be done explicit closed.

- First of all I used words like “crucial” or “horrific” during discussion as well, this was just to polarize inside the discussion. In my opinion there is no crucial code at all, beside the code which is not doing what programmer expected to do. There is only code, which is not as suitable for a special use case as another one would be for different reasons (less dependencies, performance, better readable code, side-effects, etc.). But this mainly depends on the special use case and should always be inside the decision of each programmer or a software architect analyzing the special use case.

- Second the simple fact that anyone could use a language method to do some unsuitable things, while it is helpful for others, who fully understand the usage and especially the side-effects of a feature, is in my opinion absolutely no contra argument for a RFC .

- But as I am an instructor for beginners for several years I agree to the fact that the concept of static-context is one of the concepts in oop-programming which seems to be really hard to understand with all its side-effects to beginners. A suggestion, which may help is to create an extra documentation page about static context with a DOs and DONTs list on it to help everyone to get familiar with this concept. But as being a common problem of static-context I think that should not be part of this proposal. The documentation of the static class constructor can then although refer to this page.

2. Inheritance of the class constructor

A class should have as less dependencies to another as possible. To give the possibility to inherit the class constructor will produce a huge amount of relationships not only between the superclass and it's subclasses but although between each subclass. For that reason this is no suitable feature of a static class constructor in my point of view.

For a simple example:

3. Error and Exception handling

See open issues section for this topic.

4. Need of a class destructor to handle unload order dependencies

- The only order dependency between class A and class B should be an usage dependency either class B extends A or A is used static or non-static inside class B. In both cases as I can see for now, the language itself should do already everything correct by not unloading class A before class B, for doing unload by reverse creation time.

- All other use cases I can imagine now, you should consider about using a instance instead of a direct static usage, which will trigger the object-destructor on unset the static property.

- If someone can give me a suitable other example, I will think about this again. Until then this is just a possible future scope feature for me.

5. Close method of unused ResourcesPool

Using explicit close feature on an example ResourcesPool class to close all opened resources (like ExamplePool::cleanupResources()) will break lazy-unloading, if class was not used before by calling the static constructor as well.

- That is partly true, if called on a never used before Pool it will make an “unnecessary” call to static constructor. But on the one hand that is why I would use this feature only on pool-classes I surely know that they are used mostly on each page for example DatabaseConnectionPool. On the other hand it may be even necessary to call the static constructor to initialize some static properties necessary for the unload.

- And for still being inside the user-code phase and not in the shutdown phase of the run nothing will break, by this “unnecessary” call.

6. Stateful things should be handled in instance not in class

Everywhere you have to store a state of something you should use an instance instead of a class, by using for example a singleton pattern.

- As I already tried to explain in introduction section the static properties are the state of the class. If the explained pattern always would be true the static context would be obsolete.

- Even suggested singleton pattern depends on the static state intialized or not initialized state of the class.

- Singleton pattern is indeed a nice one but it doesnt make any sense to double each method to a private non-static and a public static method just for using an instance.

For an example as suggested on mailing list:

For config.ini content is already a kind of “static” data it is in my opinion much more suitable to do like this:

Как реализовать аналог статического конструктора в классе PHP.
Чтобы статические методы могли обращаться к статическим свойствам?

  • Вопрос задан более трёх лет назад
  • 2322 просмотра

27cm

Похоже, что никак. Чтобы перед обращением к статическому свойству отработал какой-либо метод (статический конструктор), нужен аналог "магического" метода __get() для статических свойств, а такой в PHP отсутствует. Более того, даже если бы такой метод был, он бы не срабатывал если статическое свойство уже определено в классе, пришлось бы принудильно делать unset() для всех статических свойств, что приведёт к ошибке.

Поэтому вижу только один выход: обращайтесь к статическим свойствам через статические get-методы, в которых уже и вызывайте "статический конструктор".

jcmax

OnYourLips

Просто под классом пишете нужный вам код.
Статические конструкторы нужны в языках, в которых код не может существовать вне класса.

Однако я хочу предупредить, что это плохая практика, и нарушает не только ООП, но и стандарты PSR (PSR-1, PSR-2).

Dier_Sergio_Great

Вот именно если писать определения статических свойств после класса то это и не постандарту PSR, а сделать правильно по стандарту PHP не может.

OnYourLips

Dier_Sergio_Great: избегайте использования статических свойств и методов. Они нужны крайне редко (методы).
Относитесь к ним так же, как к глобальным переменным.

Dier_Sergio_Great

OnYourLips: А если нужно сделать класс Helper , вот и чтобы сгруппировать их по удобству использовать в классе.

OnYourLips

Dier_Sergio_Great: расскажите подробнее, какой helper надо сделать? Обычно в хелперы скидывают всю грязь, когда лень нормально делать.

Dier_Sergio_Great

OnYourLips

Dier_Sergio_Great: обычно используют DI для управления зависимостями. С ним все зависимости явные и код отлично поддается тестированию.
И вам рекомендую изучить PSR-2 и настроить его контроль с помощью phpcs, ваш код сложно читать.

Dier_Sergio_Great

OnYourLips: Да, спасибо большое, я исправлю на днях, но подскажите пожалуйста что такое DI и что такое PHPCS. Я пользуюсь NetBeans для PHP, у вас есть опыт для этой IDE и PHPCS с ней?.
.
еще вопрос не по теме, а существует ли для CSS наподобии PSR-2?

OnYourLips

Dier_Sergio_Great: DI- внедрение зависимости. Начните с pimple, а потом переходите на symfony/dependency-injection или то, что используется в вашем фреймворке.

> а существует ли для CSS наподобии PSR-2
Существуют препроцессоры. Попробуйте Sass.

PHP позволяет использовать только один конструктор в классе, что довольно раздражительно. Вероятно, мы никогда не получим нормальную возможность перегрузки конструкторов в PHP, но кое-что сделать все же можно. Для примера возьмем простой класс, хранящий значение времени. Какой способ создания нового объекта лучше:


Правильным ответом будет «в зависимости от ситуации». Оба способа могут являются корректным с точки зрения полученного результата. Реализуем поддержку обоих способов:

Выглядит отвратительно. Кроме того поддержка класса будет затруднена. Что произойдет, если нам понадобится добавить еще несколько способов создания экземпляров класса Time?


Также, вероятно, стоит добавить поддержку числовых строк (защита от дурака не помешает):

Реорганизация кода с использованием именованных конструкторов

Добавим несколько статичных методов для инициализации Time. Это позволит нам избавиться от условий в коде (что зачастую является хорошей идеей).


Теперь каждый метод удовлетворяет принцип Единой ответственности. Публичный интерфейс прост и понятен. Вроде бы закончили? Меня по прежнему беспокоит конструктор, он использует внутреннее представление объекта, что затрудняет изменение интерфейса. Положим, по какой-то причине нам необходимо хранить объединенное значение времени в строковом формате, а не по отдельности, как раньше:


Это некрасиво: нам приходится разбивать строку, чтобы потом заново соединить её в конструкторе. А нужен ли нам конструктор для конструктора?

Мы встроили тебе конструктор в конструктор.

Нет, обойдемся без него. Реорганизуем работу методов, для работы с внутренним представлением напрямую, а конструктор сделаем приватным:

Единообразие языковых конструкций

Наш код стал чище, мы обзавелись несколькими полезными методами инициализации нового объекта. Но как часто случается с хорошими конструктивными решениями — ранее скрытые изъяны выбираются на поверхность. Взгляните на пример использования наших методов:

  • fromString — использует в названии детали реализации PHP;
  • fromValues ​​- использует своего рода общий термин программирования;
  • fromMinutesSinceMidnight - использует обозначения из предметной области.
  • fromString => fromTime
  • fromValues => fromHoursAndMinutes


Возможно, такой подход будет не всегда оправдан, и такой уровень детализации излишен. Мы можем использовать любой из вариантов, но самое важное то, что именованные конструкторы дают нам возможность выбирать.

Часть 2: Когда использовать статические методы

Не секрет, что на собеседованиях любят задавать каверзные вопросы. Не всегда адекватные, не всегда имеющие отношение к реальности, но факт остается фактом — задают. Конечно, вопрос вопросу рознь, и иногда вопрос, на первый взгляд кажущийся вам дурацким, на самом деле направлен на проверку того, насколько хорошо вы знаете язык, на котором пишете.

image

Попробуем разобрать «по косточкам» один из таких вопросов — что значит слово «static» в PHP и зачем оно применяется?

Ключевое слово static имеет в PHP три различных значения. Разберем их в хронологическом порядке, как они появлялись в языке.

Значение первое — статическая локальная переменная


В PHP переменные локальны. Это значит, что переменная, определенная и получившая значение внутри функции (метода), существует только во время выполнения этой функции (метода). При выходе из метода локальная переменная уничтожается, а при повторном входе — создается заново. В коде выше такой локальной переменной является переменная $a — она существует только внутри функции foo() и каждый раз при вызове этой функции создается заново. Инкремент переменной в этом коде бессмысленен, поскольку на следующей же строчке кода функция закончит свою работу и значение переменной будет потеряно. Сколько бы раз мы не вызвали функцию foo(), она всегда будет выводить 0…

Однако всё меняется, если мы перед присваиванием поставим ключевое слово static:

  1. Присваивание выполняется только один раз, при первом вызове функции
  2. Значение помеченной таким образом переменной сохраняется после окончания работы функции
  3. При последующих вызовах функции вместо присваивания переменная получает сохраненное ранее значение
Подводные камни статических переменных

Разумеется, как всегда в PHP, не обходится без «подводных камней».

Камень первый — статической переменной присваивать можно только константы или константные выражения. Вот такой код:

с неизбежностью приведет к ошибке парсера. К счастью, начиная с версии 5.6 стало допустимым присвоение не только констант, но и константных выражений (например — «1+2» или "[1, 2, 3]"), то есть таких выражений, которые не зависят от другого кода и могут быть вычислены на этапе компиляции

Камень второй — методы существуют в единственном экземпляре.
Тут всё чуть сложнее. Для понимания сути приведу код:

Вопреки интуитивному ожиданию «разные объекты — разные методы» мы наглядно видим на этом примере, что динамические методы в PHP «не размножаются». Даже если у нас будет сто объектов этого класса, метод будет существовать лишь в одном экземпляре, просто при каждом вызове в него будет пробрасываться разный $this.

Такое поведение может быть неожиданным для неподготовленного к нему разработчика и послужить источником ошибок. Нужно заметить, что наследование класса (и метода) приводит к тому, что всё-таки создается новый метод:

Вывод: динамические методы в PHP существуют в контексте классов, а не объектов. И только лишь в рантайме происходит подстановка "$this = текущий_объект"

Значение второе — статические свойства и методы классов

В объектной модели PHP существует возможность задавать свойства и методы не только для объектов — экземпляров класса, но и для класса в целом. Для этого тоже служит ключевое слово static:


Для доступа к таким свойствам и методам используются конструкции с двойным двоеточием («Paamayim Nekudotayim»), такие как ИМЯ_КЛАССА::$имяПеременной и ИМЯ_КЛАССА:: имяМетода().

Само собой разумеется, что у статических свойств и статических методов есть свои особенности и свои «подводные камни», которые нужно знать.

Особенность первая, банальная — нет $this. Собственно это проистекает из самого определения статического метода — поскольку он связан с классом, а не объектом, в нём недоступна псевдопеременная $this, указывающая в динамических методах на текущий объект. Что совершенно логично.

Однако, нужно знать, что в отличие от других языков, PHP не определяет ситуацию «в статическом методе написано $this» на этапе парсинга или компиляции. Подобная ошибка может возникнуть только в рантайме, если вы попытаетесь выполнить код с $this внутри статического метода.

Код типа такого:

не приведет ни к каким ошибкам, до тех пор, пока вы не попытаетесь использовать метод foo() неподобающим образом:
(и сразу получите «Fatal error: Using $this when not in object context»)

Особенность вторая — static не аксиома!

Вот так, да. Статический метод, если он не содержит в коде $this, вполне можно вызывать в динамическом контексте, как метод объекта. Это не является ошибкой в PHP.

Обратное не совсем верно:

Динамический метод, не использующий $this, можно выполнять в статическом контексте. Однако вы получите предупреждение «Non-static method A::foo() should not be called statically» уровня E_STRICT. Тут решать вам — или строго следовать стандартам кода, или подавлять предупреждения. Первое, разумеется, предпочтительнее.

И кстати, всё написанное выше относится только к методам. Использование статического свойства через "->" невозможно и ведет к фатальной ошибке.

Значение третье, кажущееся самым сложным — позднее статическое связывание

Разработчики языка PHP не остановились на двух значениях ключевого слова «static» и в версии 5.3 добавили еще одну «фичу» языка, которая реализована тем же самым словом! Она называется «позднее статическое связывание» или LSB (Late Static Binding).

Понять суть LSB проще всего на несложных примерах:


Ключевое слово self в PHP всегда значит «имя класса, где это слово написано». В данном случае self заменяется на класс Model, а self::$table — на Model::$table.
Такая языковая возможность называется «ранним статическим связыванием». Почему ранним? Потому что связывание self и конкретного имени класса происходит не в рантайме, а на более ранних этапах — парсинга и компиляции кода. Ну а «статическое» — потому что речь идет о статических свойствах и методах.

Немного изменим наш код:

Теперь вы понимаете, почему PHP ведёт себя в этой ситуации неинтуитивно. self был связан с классом Model тогда, когда о классе User еще ничего не было известно, поэтому и указывает на Model.

Для решения этой дилеммы был придуман механизм связывания «позднего», на этапе рантайма. Работает он очень просто — достаточно вместо слова «self» написать «static» и связь будет установлена с тем классом, который вызывает данный код, а не с тем, где он написан:


Это и есть загадочное «позднее статическое связывание».

Нужно отметить, что для большего удобства в PHP кроме слова «static» есть еще специальная функция get_called_class(), которая сообщит вам — в контексте какого класса в данный момент работает ваш код.

Читайте также: