I've read what I could find on this issue, but I'm pretty much baffled at the lack of alternatives. If you have an OOP project in PHP, using consts is as ugly as it can get. Here's the problem:
class Invisible {
CONST youCantSeeMe = "";
}
class Ugly {
$invisible;
static $invisible2;
function __construct() {
$this->$invisible = new Invisible();
self::$invisible2 = New Invisible();
$this->invisible::youCantSeeMe; (illegal, crashes project)
self::$invisible2::youCantSeeMe; (illegal, crashes project)
}
function uglyFunction() {
//this is the only, ugly way to do it
$invisible = $this->invisible;
$invisible::youCantSeeMe (this works)
}
}
The only other way I've found around it, is to make a public get for each const you have (cumbersome, waste of time), or with a magic __get using a reflection class (expensive).
There HAS to be a better way than these options?
I believe your complaint really has to do with incomplete dereferencing support.
:: only accepts simple reference variables on the left hand side
This is fixed in the Uniform Variable Syntax RFC which is implemented in PHP 7.
Related
After upgrade to PHP 7 the logs almost choked on this kind of errors:
PHP Warning: Declaration of Example::do($a, $b, $c) should be compatible with ParentOfExample::do($c = null) in Example.php on line 22548
How do I silence these and only these errors in PHP 7?
Before PHP 7 they were E_STRICT type of warnings which could be easily dealt with. Now they're just plain old warnings. Since I do want to know about other warnings, I can't just turn off all warnings altogether.
I don't have a mental capacity to rewrite these legacy APIs not even mentioning all the software that uses them. Guess what, nobody's going to pay for that too. Neither I develop them in the first place so I'm not the one for blame. (Unit tests? Not in the fashion ten years ago.)
I would like to avoid any trickery with func_get_args and similar as much as possible.
Not really I want to downgrade to PHP 5.
I still want to know about other errors and warnings.
Is there a clean and nice way to accomplish this?
1. Workaround
Since it is not always possible to correct all the code you did not write, especially the legacy one...
if (PHP_MAJOR_VERSION >= 7) {
set_error_handler(function ($errno, $errstr) {
return strpos($errstr, 'Declaration of') === 0;
}, E_WARNING);
}
This error handler returns true for warnings beginning with Declaration of which basically tells PHP that a warning was taken care of. That's why PHP won't report this warning elsewhere.
Plus, this code will only run in PHP 7 or higher.
If you want this to happen only in regard to a specific codebase, then you could check if a file with an error belongs to that codebase or a library of interest:
if (PHP_MAJOR_VERSION >= 7) {
set_error_handler(function ($errno, $errstr, $file) {
return strpos($file, 'path/to/legacy/library') !== false &&
strpos($errstr, 'Declaration of') === 0;
}, E_WARNING);
}
2. Proper solution
As for actually fixing someone else's legacy code, there is a number of cases where this could be done between easy and manageable. In examples below class B is a subclass of A. Note that you do not necessarily will remove any LSP violations by following these examples.
Some cases are pretty easy. If in a subclass there's a missing default argument, just add it and move on. E.g. in this case:
Declaration of B::foo() should be compatible with A::foo($bar = null)
You would do:
- public function foo()
+ public function foo($bar = null)
If you have additional constrains added in a subclass, remove them from the definition, while moving inside the function's body.
Declaration of B::add(Baz $baz) should be compatible with A::add($n)
You may want to use assertions or throw an exception depending on a severity.
- public function add(Baz $baz)
+ public function add($baz)
{
+ assert($baz instanceof Baz);
If you see that the constraints are being used purely for documentation purposes, move them where they belong.
- protected function setValue(Baz $baz)
+ /**
+ * #param Baz $baz
+ */
+ protected function setValue($baz)
{
+ /** #var $baz Baz */
If you subclass has less arguments than a superclass, and you could make them optional in the superclass, just add placeholders in the subclass. Given error string:
Declaration of B::foo($param = '') should be compatible with A::foo($x = 40, $y = '')
You would do:
- public function foo($param = '')
+ public function foo($param = '', $_ = null)
If you see some arguments made required in a subclass, take the matter in your hands.
- protected function foo($bar)
+ protected function foo($bar = null)
{
+ if (empty($bar['key'])) {
+ throw new Exception("Invalid argument");
+ }
Sometimes it may be easier to alter the superclass method to exclude an optional argument altogether, falling back to func_get_args magic. Do not forget to document the missing argument.
/**
+ * #param callable $bar
*/
- public function getFoo($bar = false)
+ public function getFoo()
{
+ if (func_num_args() && $bar = func_get_arg(0)) {
+ // go on with $bar
Sure this can become very tedious if you have to remove more than one argument.
Things get much more interesting if you have serious violations of substitution principle. If you do not have typed arguments, then it is easy. Just make all extra arguments optional, then check for their presence. Given error:
Declaration of B::save($key, $value) should be compatible with A::save($foo = NULL)
You would do:
- public function save($key, $value)
+ public function save($key = null, $value = null)
{
+ if (func_num_args() < 2) {
+ throw new Exception("Required argument missing");
+ }
Note that we couldn't use func_get_args() here because it does not account for default (non-passed) arguments. We are left with only func_num_args().
If you have a whole hierarchies of classes with a diverging interface, it may be easier diverge it even further. Rename a function with conflicting definition in every class. Then add a proxy function in a single intermediary parent for these classes:
function save($arg = null) // conforms to the parent
{
$args = func_get_args();
return $this->saveExtra(...$args); // diverged interface
}
This way LSP would still be violated, although without a warning, but you get to keep all type checks you have in subclasses.
For those who want to actually correct your code so it no longer triggers the warning: I found it useful to learn that you can add additional parameters to overridden methods in subclasses as long as you give them default values. So for example, while this will trigger the warning:
//"Warning: Declaration of B::foo($arg1) should be compatible with A::foo()"
class B extends A {
function foo($arg1) {}
}
class A {
function foo() {}
}
This will not:
class B extends A {
function foo($arg1 = null) {}
}
class A {
function foo() {}
}
If you must silence the error, you can declare the class inside a silenced, immediately-invoked function expression:
<?php
// unsilenced
class Fooable {
public function foo($a, $b, $c) {}
}
// silenced
#(function () {
class ExtendedFooable extends Fooable {
public function foo($d) {}
}
})();
I would strongly recommend against this, though. It is better to fix your code than to silence warnings about how it is broken.
If you need to maintain PHP 5 compatibility, be aware that the above code only works in PHP 7, because PHP 5 did not have uniform syntax for expressions. To make it work with PHP 5, you would need to assign the function to a variable before invoking it (or make it a named function):
$_ = function () {
class ExtendedFooable extends Fooable {
public function foo($d) {}
}
};
#$_();
unset($_);
PHP 7 removes the E_STRICT error level. Info about this can be found in the PHP7 compatibility notes. You might also want to read the proposal document where it was discussed while PHP 7 was being developed.
The simple fact is this: The E_STRICT notices were introduced a number of versions ago, in an attempt to notify developers that they were using bad practice, but initially without trying to force any changes. However recent versions, and PHP 7 in particular, have become more strict about these things.
The error you're experiencing is a classic case:
You have defined a method in your class that overrides a method of the same name in the parent class, but your override method has a different argument signature.
Most modern programming languages would not actually allow this at all. PHP used to allow developers to get away with stuff like this, but the language is becoming more strict with every version, especially now with PHP 7 -- they went with a new major version number specifically so that they could justify making significant changes that break backward compatibility.
The problem you have is because you've already been ignoring the warning messages. Your question implies that this is the solution you want to continue with, but messages like "strict" and "deprecated" should be treated as an explicit warning that your code is likely to break in future versions. By ignoring them for the past number of years, you have effectively placed yourself in the situation you have now. (I know that's not what you want to hear, and doesn't really help the situation now, but it's important to make it clear)
There really isn't a work around of the kind you're looking for. The PHP language is evolving, and if you want to stick with PHP 7 your code will need to evolve too. If you really can't fix the code, then you will either have to suppress all warnings or else live with these warnings cluttering up your logs.
The other thing you need to know if you plan to stick with PHP 7 is that there are a number of other compatibility breaks with this version, including some that are quite subtle. If your code is in a state where it has errors like the one you're reporting, it means that it's probably been around for quite a while, and likely has other issues that will cause you problems in PHP 7. For code like this, I would suggest doing a more thorough audit of the code before committing to PHP 7. If you're not prepared to do that, or not prepared to fix the bugs that are found (and the implication from your question is that you are not), then I'd suggest that PHP 7 is probably an upgrade too far for you.
You do have the option of reverting to PHP 5.6. I know you said you don't want to do that, but as a short-to-medium term solution it will make things easier for you. Frankly, I think it might be your best option.
I agree: the example in the first post is bad practice.
Now what if you have that example :
class AnimalData {
public $shout;
}
class BirdData extends AnimalData {
public $wingNumber;
}
class DogData extends AnimalData {
public $legNumber;
}
class AnimalManager {
public static function displayProperties(AnimalData $animal) {
var_dump($animal->shout);
}
}
class BirdManager extends AnimalManager {
public static function displayProperties(BirdData $bird) {
self::displayProperties($bird);
var_dump($bird->wingNumber);
}
}
class DogManager extends AnimalManager {
public static function displayProperties(DogData $dog) {
self::displayProperties($dog);
var_dump($dog->legNumber);
}
}
I believe this is a legitimate code structure, nevertheless this will raise a warning in my logs because the displayProperties() do not have the same parameters. Moreover I can't make them optional by adding a = null after them...
Am I right thinking this warning is wrong in this specific example please?
I had this issue as well. I have a class that overrides a function of the parent class, but the override has different num of parameters. I can think of a few easy work arounds - but do require minor code change.
change the name of the function in the subclass (so it no longer overrides parent function)
-or-
change the parameters of the parent function, but make the extra parameters optional (e.g., function func($var1, $var2=null) - this may be easiest and require less code changes. But it may not be worth to change this in the parent if its used so many other places. So I went with #1 in my case.
If possible, instead of passing the extra params in the subclass function, use global to pull in the extra params. This is not ideal coding; but a possible band-aid anyway.
You can remove the parent class method definition altogether and intercept it with a magic method.
public function __call($name, $args)
{
if($name == 'do') {
// do things with the unknown # of args
} else {
throw new \Exception("Unknown method $name", 500);
}
}
I just ran into this problem and went this route
If the base class has fewer arguments than the derived class, it is possible to add additional argument(s) to the derived class like this:
$namespace = 'default';
if (func_num_args() > 2) {
$namespace = func_get_arg(2);
}
In this way, you add a 3rd "defaulted" argument, but do not change the signature.
I would only suggest this if you have a substantial amount of code that calls this and are unable to change that code, and want to maintain backward compatibility.
I found this situation in some old Joomla code (v1.5) where JSession::set added a $namespace param, but has JObject as a base class, where JObject::set has no such parameter.
First of all: I tried to google it, but I mostly only found discussions about how to define arrays in constants and other unrelated information.
I have a question regarding a solution to make my code more readable (and pretty) that just occured to me. Basically I have most functions return a status code that indicates success or, in case something went wrong, an error code. For this, I made a class called "StatusCode" that contains only constants, like so:
<?php
class StatusCode {
const success = 0;
const badArgument = -1;
const badQuery = -2;
const outOfMana = -3; //Really just for demonstration purposes
...
}
The purpose is to make magic numbers disappear from my code and make it clear what went wrong without having to look for an explaination somewhere:
if (mana > 10) {
//Do some magic
return StatusCode::success;
}
else {
//Oh god this is not good!
return StatusCode::outOfMana;
}
It should also eliminate the possibility of accidently using duplicate error codes.
I'm pretty sure this adds a minor overhead to my application, but has made my code easier to understand in return. Is there some earth shattering reason not to do this? Maybe an even better way to go about it?
(I have avoided the define(CONSTANT, "value") approach because it seems less pretty and it's a hassle to write on my German keyboard :))
In Java and other languages this is a commonly used way to namespace constants to avoid naming collisions. See here;
The way that I would implement such a class is like this"
// make this final so no one can extend it
final class Errors{
const SUCCESS = 0;
const BAD_ARGUMENT = -1;
const BAD_QUERY = -2;
const OUT_OF_MANA = -3;
// make this private so noone can make one
private function __construct(){
// throw an exception if someone can get in here (I'm paranoid)
throw new Exception("Can't get an instance of Errors");
}
}
This has the advantage of namespacing and grouping constants. You can use reflection on that class to iterate over defined constants, which allows you, for example, to validate that a value is a value of a certain constant group (enabling a poor man's constant type hinting).
The disadvantage is that you're kind of abusing a class (though only slightly). Purists may not like that. Constants which are not used in the same class should be global constants; you can even namespace them into something like \StatusCodes\SUCCESS in PHP 5.3+.
The choice is yours, really.
Creating an static class will solve your problem and avoid creating multiple instances of StatusCode
Namespaces can be used if you think your application can have multiple StatusCode classes but still the StatusCode will be static.
If you want to use singleton pattern this will work too
Choice is yours!
You can use an interface, so an instance cannot be created:
interface StatusCode {
public const success = 0;
public const badArgument = -1;
public const badQuery = -2;
public const outOfMana = -3;
}
I was trying to find a way to execute some code to alter the results of an objects methods without actually touching the object's code. One way I came up is using a decorator:
class Decorator {
private $object;
public function __construct($object) {
if (!is_object($object)) {
throw new Exception("Not an object");
}
$this->object = $object;
}
protected function doSomething(&$val) {
$val .= "!!";
}
public function __call($name, $arguments) {
$retVal = call_user_func_array(array($this->object, $name), $arguments);
$this->doSomething($retVal);
return $retVal;
}
}
class Test extends BaseTest {
public function run() {
return "Test->run()";
}
}
$o = new Decorator(new Test());
$o->run();
That way it will work properly but it has one disadvantage which makes it unusable for me right now - it would require replacing all lines with new Test() with new Decorator(new Test()) and this is exactly what I would like to avoid - lots of meddling with the existing code. Maybe something I could do in the base class?
One does not simply overload stuff in PHP. So what you want cannot be done. But the fact that you are in trouble now is a big tell your design is flawed. Or if it is not your code design the code you have to work with (I feel your pain).
If you cannot do what you want to do it is because you have tightly coupled your code. I.e. you make use of the new keyword in classes instead of injecting them (dependency injection) into the classes / methods that need it.
Besides not being able to easily swap classes you would also have a gard time easily testing your units because of the tight coupling.
UPDATE
For completeness (for possible future readers): if the specific class would have been namespaced and you were allowed to change the namespace you could have thought about changing the namespace. However this is not really good practice, because it may screw with for example autoloaders. An example of this would be PSR-0. But considering you cannot do this either way I don't see it is possible what you want. P.S. you should not really use this "solution".
UPDATE2
It looks like there has been some overload extension at some time (way way way back), but the only thing I have found about it is some bug report. And don't count on it still working now either way. ;-) There simply is no real overloading in PHP.
Found something (a dead project which doesn't work anymore that enables class overloading): http://pecl.php.net/package/runkit
Possibly another project (also dead of course): http://pecl.php.net/package/apd
I am not a PHP programmer, but I think that AOP is what you are looking for. You can try some frameworks, for example listed in this answer.
From the Wikipedia article on the decorator pattern:
Subclass the original "Decorator" class into a "Component" class
So I think you're supposed to keep the class to be decorated private and expose only the already-decorated class.
example:
class Vendor_ClassName_Helper {
CONST FIRST_OPTION = 1;
CONST SECOND_OPTION = 2;
public function __construct($option, $otherArgument) {
}
}
client code:
$obj = new Vendor_ClassName_Helper(Vendor_ClassName_Helper::FIRST_OPTION, $var);
Any good ways to avoid the long lines (and this is a rather short example)? Maybe other ways to implement the same?
I think clarity is better than short code. You can try to think of different words of expressing the same or different form. For your example, it doesn't seem very bad as Omega pointed out, and his method of splitting declaration on multiple lines is good as well.
Here's another trick: Depending on what your option constants do, you may want to employ a factory method instead of the new-keyword.
For example,
class Example {
private function __construct() { }
public static method createA(..) {
//configure for mode A
$cls = new self;
return $cls;
}
public static method createB(..) {
//configure for mode B
$cls = new self;
return $cls;
}
}
$x = Example::createA();
I avoid long lines and improve readability in most languages by breaking up the parameters into their own kind of block...
$obj = new Vendor_ClassName_Helper(
Vendor_ClassName_Helper::FIRST_OPTION,
$var
);
But two options doesn't always warrant it in my opinion. Static constants unfortunately can't really be changed and you do of course want them to remain descriptive.
What you have here isn't so bad :)
If you're passing a constant to the constructor, it would suggest that you should create subclasses instead:
class Vendor_ClassName_Helper {
public function __construct($otherArgument) {
}
}
class Vendor_ClassName_Helper_First extends Vendor_ClassName_Helper {
}
class Vendor_ClassName_Helper_Second extends Vendor_ClassName_Helper {
}
without using shorter name for class or constant's names (and making your code impossible to understand, which is something you definitly don't want), no, I don't think there is a way -- at least, not in PHP < 5.3
PHP 5.3 adds namespaces to PHP ; with those, you might be able to come to something shorter / better ; but it means using PHP 5.3, which is not proposed by many hosting companies (5.3.0 was release at the end of june this year, so it might be a while before it's available averywhere...)
For more informations about namespaces in PHP (and to cite only a couple of links) :
the manual
some articles on sitepoint
Migrating OOP Libraries and Frameworks to PHP 5.3 might interest you too
I think there isn't a better way (there isn't a dynamic way):
class LongClassName
{
const B = 3;
}
class LCN
{
const B = LongClassName::B;
}
echo LCN::B;
Starting with version 5.3, PHP supports late binding for static methods. While it's an undoubtedly useful feature, there are only several cases where its use is really necessary (e.g. the Active Record pattern).
Consider these examples:
1. Convenience constructors (::create())
class SimpleObject
{
public function __construct() { /* ... */ }
public static function create()
{
return new static; // or: return new self;
}
}
If this class may be extended (however, it's not extended by any class in the same package), should late static binding be used just to make extending it easier (without having to rewrite the ::create() method, and, more importantly, without having to remember to do that)?
Note: this idiom is used to work around the impossibility to call methods on just constructed objects: new SimpleObject()->doStuff() is invalid in PHP.
2. Class constants
class TagMatcher
{
const TAG_PATTERN = '/\<([a-z\-]+?)\>/i';
private $subject;
public function construct($subject) { $this->subject = $subject; }
public function getAllTags()
{
$pattern = static::TAG_PATTERN;
preg_match_all($pattern, $this->subject);
return $pattern[1];
}
}
The reason to use static:: in this example is similar to the previous one. It's used just because this class can be made to match differently formed tags just by extending it and overriding the constant.
So, to wrap it all up, are these uses (and similar ones) of late static binding are an overkill? Is there any noticeable performance hit? Also, does frequent use of late binding reduce the overall performance boost given by opcode caches?
So, to wrap it all up, are these uses (and similar ones) of late static binding are an overkill? Is there any noticeable performance hit? Also, does frequent use of late binding reduce the overall performance boost given by opcode caches?
The introduction of late static binding fixes a flaw in PHP's object model. It's not about performance, it's about semantics.
For example, I like to use static methods whenever the implementation of the method doesn't use $this. Just because a method is static doesn't mean to say that you don't want to override it sometimes. Prior to PHP 5.3, the behavior was that no error was flagged if you overrode a static method, but PHP would just go ahead and silently use the parent's version. For example, the code below prints 'A' before PHP 5.3. That's highly unexpected behavior.
Late static binding fixes it, and now the same code prints 'B'.
<?php
class A {
public static function who() {
echo __CLASS__;
}
public static function test() {
static::who();
}
}
class B extends A {
public static function who() {
echo __CLASS__;
}
}
B::test();
?>
static methods (early- or late-bound) create tight coupling and (thus) reduce testability. you can create large programs in PHP without using more than a few static calls. for me, late static methods are a non-feature.
edit to answer Marco Demaio's question, how do static method reduce testability?
i'm sorry if this is all obvious to you, static members (both data and methods) are useful and do no harm if used responsibly, i was alluding to their prevalent misuse.
say you have a web application that uses an SQL database. your business objects may retrieve data using a static interface or through polymorphism. either
class MyBusinessObject
extends...
{
public function doThisOrThat(...)
{
$results = db::query('sql string...');
...
}
}
or
class MyBusinessObject
extends...
{
public function __construct(dbconn $db)
{
$this->db = $db;
}
private $db;
public function doThisOrThat(...)
{
$results = $this->db->query('sql string...');
...
}
}
the latter is easier to test (as in: i want to test that the sql string constructed from such-and-such inputs is such-and-such) because it's easier to create another implementation of the dbconn interface than it is to change the meaning of db::. why you'd want either? because you don't need a real database to test the sql-composing behavior, and in fact it's easier to test for without a real database. also, it's easier to stub out the sql consumer if your tests are concerned with another aspect of the CUT (Code Under Test).
testing always implies lying to the tested code about its collaborators, and abstaining from static interfaces (the "doublecolon" or "quadridot") means the lie need not be a massive surgery, which is a plus, since the farther the tested code is from the production code, the less meaningful the test results are.
Where I find a need to use late static binding is to allow mocking of static methods for unit testing with PHPUnit. The problem I have is that I don't like changing code strictly to allow mocking, but I can get over that.
To answer your question, however, I would bet that whatever performance cost this carries, it will pale in comparison to most program runtimes. In other words, it won't make a noticeable difference.