Strict standards error when using __autoload - php

I am faced with an error when using __autoload().
Here is an example of two simple classes in the same file:
class A
{
public function check($a, $b)
{
$subClass = new B();
return $subClass->check($a);
}
}
class B extends A
{
public function check($a)
{
return $a;
}
}
$a = new A();
$a->check(77, 44);
Everything is OK; and it works as expected. But when I create a file for each class and use __autoload(), there is an error:
Strict standards: Declaration of B::check() should be compatible with that of A::check()
To reproduce:
file A.php
class A
{
public function check($a, $b)
{
$subClass = new B();
return $subClass->check($a);
}
}
file B.php:
class B extends A
{
public function check($a)
{
return $a;
}
}
file test.php
function __autoload($className)
{
require_once $className . '.php';
}
$a = new A();
$a->check(77, 44);
So, why does this error appear only when using __autoload?

Actually it's the non-autoload code that is being weird. The strict standards error is supposed to happen.
The reason why the error doesn't appear in the single-file case is due to a quirk of how PHP loads the classes, and the timing of when the error reporting mode is set. There is some explanation here: https://bugs.php.net/bug.php?id=46851.
In short:
If you define the classes in a separate file or files (such as with __autoload or include), they are loaded after you have turned on E_STRICT reporting in your main file, so you see the error.
If you define the classes directly in your main file, then the classes get loaded before the script starts running, before you turn on E_STRICT error reporting, so you don't see the error.
Oddly, if you define the classes in your main file but reverse the order (class B, then class A) you do see the error. According to the page linked above, this is because PHP doesn't load them until a bit later.
If you turn on E_STRICT error reporting in php.ini, so it is already on before the script starts, you see the error, regardless of how the classes are loaded.
To understand the purpose of the error, it is telling you that it is wrong for a subclass to override a method with a different number of arguments, which it is.
See Liskov substitution principle on Wikipedia:
The Liskov substitution principle states that, in a computer program, if S is a subtype of T, then objects of type T may be replaced with objects of type S (i.e., objects of type S may substitute objects of type T) without altering any of the desirable properties of that program (correctness, task performed, etc.).
Currently we have this code:
$a = new A();
$a->check(77, 44);
Now since B extends A, this indicates that B is a subtype of A, and according to the above principle it should be logically possible for objects of type A to be replaced with objects of type B. But look what happens:
$a = new B();
$a->check(77, 44);
We call the check method as usual, but it is not expecting two arguments, so the call doesn't make sense.
The improper override works in PHP, because PHP is generally not very strict, but turning on strict standards errors lets it warn about this.

This message means that there are certain possible method calls which may fail at run-time. return $subClass->check($a); calls to class B method which has signature in parent method in class class A.
Different signature (2 parameters in A and 1 parameter in B) produces strict error, abstract or not.
It works if signatures are same:
class A
{
public function check($a, $b)
{
$subClass = new B();
return $subClass->check($a, NULL);
}
}
class B extends A
{
public function check($a, $b)
{
return $a;
}
}
function __autoload($className)
{
require_once $className . '.php';
}
$a = new A();
$a->check(77, 44);
Can I ask you what are you trying to achieve by instantiating new class in class which is extended by it?

Related

Different number of parameters in function overriding

I want to ask how to enable full error reporting, E_ALL and startup errors in php.ini don't have effect in my case.
My code:
class A
{
function funcA(arg1=null, arg2=null, arg3=false, arg4=null) {}
}
class B extends A
{
function funcB() {}
}
class C extends B
{
function funcA(arg1=null, arg2=null, arg3=false) {}
}
With php 7.0 it was allowed and it was working, after upgrading to php 7.2.15 there is some kind of crash of php, script execution stops, no errors in error logs. With php 7.2 there must be the same number of method parameters like in parent class, that's not a problem for me, but problem is that I don't have any feedback from php about this error.
Do you have any ideas why there is no error or exception? I'm using development php.ini with all errors display enabled.
This code always produces an incompatible signature warning from version 7.0.33 to 7.3.
It can be confirmed here: https://3v4l.org/Ifmbk
Actually, you are unintentionally breaking the L rule of the SOLID, which stands for Liskov's Substitution Principle:
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
Instances of C and A in your example are not literally interchangeable, even having optional arguments in the signature.
Still, you have at least two options and both of them requires a design change and the warning's itself confirming the existence of a smell.
Remove the inheritance if there is a really few common functionality and go with the composition:
class B
{
/**
* A
*/
private $a;
public function __construct(A $a) {
$this->a = $a;
}
}
Or split the functionality to different methods and have a more relaxed interface to accept AbcInterface in other places and validate the instance type you got in actual implementation:
interface AbcInterface
{
public function B();
}
class A implements AbcInterface
{
public function funcA($arg1=null, $arg2=null, $arg3=false, $arg4=null)
{
}
public function funcAWithLessArgs($arg1=null, $arg2=null, $arg3=false)
{
}
}
In reality, what you need here is function overloading which is does not exists in PHP ecosystem since the beginning.

Using the :: operator on an object

Recently I've found interesting usage of code and I didn't know that it's possible. Can someone explain or give me manual page with explanation why the code below works? I understand :: can be used to reflect methods from parent, static etc. or to access static fields/methods but with reference $this it seems weird mostly because method a() is not static
class Test
{
private function a()
{
echo 'a works';
}
public static function c()
{
echo 'c works';
}
public function b()
{
$this::a(); // this is weird
$this::c(); // also this
$this->a(); // normal usage
self::a(); // as expected
static::a(); // same as above
Test::c(); // as expected
}
}
(new Test)->b();
I've tried to find some information on my own but with no luck.
Edit:
I'm aware what :: is also I know it will throw warning if E_STRICT is enabled.
As of PHP 5.3, you can use a variable to reference the class with the :: operator. The manual only shows usages in which the variable is a string, however it is in fact possible to also use an object in its place; the class referenced is then the class the object is an instance of. There's an example buried in the manual for static: http://php.net/manual/en/language.oop5.static.php#language.oop5.static.properties.
So, all these resolve to the same thing:
$foo = new Foo;
$foo::bar();
$foo = 'Foo';
$foo::bar();
Foo::bar();
The methods will always be called statically; for methods which are already static this works as expected, while for non-static methods an E_STRICT notice will be raised.
This is mostly for convenience as far as I gather; you already have an object of a specific class, now you want to reference some static item of that class – just use the object you already have. This also allows for some more dynamic behaviour with subclassing. E.g.:
$foo = new SomeClassWithAVeryLongName;
$foo->bar($foo::BAZ); // much more concise than repeating SomeClassWithAVeryLongName::

PHP Override way

What is the PHP way to do a correct override ?
php -a
Interactive mode enabled
php > error_reporting(E_ALL);
php > class A { public function z($a, $b){} }
php > class B extends A { public function z($a){parent::z($a, '1');} }
PHP Strict standards: Declaration of B::z() should be compatible with A::z($a, $b) in php shell code on line 1
Note: Override is the correct word in english ?
In your example, in class A function z have 2 parameters, in class B you are trying to declare it with 1 parameter. In php it wont work.
Btw. I guess you probably want to OVERLOAD funciotn z not override it, and it is not really supported in php. If I'm right here is little explanation fro manual:
Note:
PHP's interpretation of "overloading" is different than most object oriented languages. Overloading traditionally provides the ability to have multiple methods with the same name but different quantities and types of arguments.
The reason for the E_STRICT notice (which is just a programming hint, not an error as such) is that a child class should theoretically be usable interchangeably with its parent (the posh term is the "Liskov Substitution Principle").
In your case, you need to know that you have an instance of B in order to leave off the second parameter. PHP is being relaxed and letting you do it, but it's not really how inheritance should work - B extends A should mean that everything that "is a B" also "is an A".
If you used an interface to define the contract for function z($a, $b), class B would violate that contract.
A better approach for this problem would be to use delegation rather than inheritance: class B could act as an "adaptor" which uses A's behaviour, but simplifies it, e.g.
class B
{
private $delegated_a;
public function __construct() {
$this->delegated_a = new A;
}
public function z($a) {
$this->delegated_a->z($a, '1');
}
}
Here, we're making no promise to calling code about the relationship between A and B, so can re-define the methods however we like.
You mean method overloading :)
Well actually this works a bit different then you would expect if you programmed in Java before. Please also checkout the comment in the PHP manual.
The difference is that in eg. Java you can have multiple methods with the same name but another footprint. Eg:
public void max(int a, int b) {
// calculate max value
}
public void max(double a, double b, double c) {
// do some other calculation
}
Now if you would call the method max, Java will automatically decide which method to use.
Now in PHP this does not work. You can overwrite a method in a child class, but only if the footprint is the same, and you can only define the method in the child class. Example:
class A {
function z($a, $b) {
echo 'called in A';
}
}
class B extends A {
function z($a, $b) {
echo 'called in B';
}
}
$a = new A;
$b = new B;
$a->z(1, 2); // prints out "called in A"
$b->z(1, 2); // prints out "called in B"

Cannot call class's function in PHP

I have 4 classes. When someone goes to printHi.php, it prints "hi" twice--from different classes. However:
printHi.php
include('main.php');
$main = new Main;
main.php:
class Main {
function __construct() {
include('class2.php');
include('class3.php');
$this->class2 = new class2;
$this->class3 = new class3;
$this->class2->sanity();
}
}
class2.php
class class2 {
public function sanity() {
echo "Hi.";
}
}
class3.php
class class3 {
function __construct() {
$this->class2 = new class2;
$this->class2->sanity();
}
}
No ouput shows (or errors)? What am I doing wrong?
Also, if I wanted to use sanity() in all of my classes, how would I do that without doing
$this->class2 = new class2;
$this->class2->sanity();
in every class?
http://pastebin.com/HHyQfvhW
Errors are being thrown. You might have error_reporting turned off and be seeing a blank screen, but they are being raised. Here's a list of errors from what I can see:
Class3's constructor is missing the function declaration. This should be a fatal parse error
function __construct() {
Class1's constructor tries to call the method sanity() on the non-object $this->class. This should be a fatal error.
So, obviously this isn't your actual code. Assuming that you're just talking about making Class2 available to all your classes, I'd suggest Dependency Injection. So:
class Main {
public function __construct(class2 $class2, Class3 $class3) {
$this->class2 = $class2;
$this->class3 = $class3;
$this->class2->sanity();
}
}
class Class2 {
public function sanity() {...}
}
class Class3 {
public function __construct(Class2 $class2) {
$this->class2 = $class2;
}
}
That way, everything is passed in. It's far more flexible, easier to understand and debug, and far easier to test.
Edit: Based upon the linked code:
There are a few issues.
Inject your dependencies. Don't just create new instances of classes everywhere (hardcoding relationships)
Indent your code properly. Readability is king. always indent.
require() or die() is pointless. require will end execution for you if it fails. the or die bit is redundent.
The sanity() method on Config is declared as static, yet you're trying to call it on an instance. Figure out if it's tied to an instance (needs to use $this) or not, and make it appropriately. Then only call it appropriately. Don't call Foo::bar() if bar is an instance method and vise versa.
Your todo is wrong, since require 'foo' or die() is working how it should. OR has the higher precidence, so that's why you get require 1 since it's interpreted as require ('foo' or die())...
Finally, don't use require blindly like this. Instead, either autoload you classes, or use require_once in case a file was already required (to prevent errors).
You are not seeing errors likely because
class class3 {
__construct() {
$this->class2 = new class2;
$this->class2->sanity();
}
}
contains a parse error. Namely, you need to write function __construct(). Because of this, methods to turn on errors such as error_reporting and ini_set will not work because the script never runs due to the parse error. Therefore, look to your php.ini file and set the error_reporting and display_errors directives there. After having done that, you should see your error messages.

php static methods question

What is the difference between these two pieces of code?
class something {
static function doit() {
echo 'hello world';
}
}
something::doit();
and the same but without the static keyword
class something {
function doit() {
echo 'hello world';
}
}
something::doit();
They both work the same is it better to use the static keywords? Am i right in understanding that it doesn't instantiate the class if you use the static method?
The second example is technically incorrect - if you turn on E_STRICT error reporting you'll see that PHP is actually throwing an error.
PHP Strict Standards: Non-static
method something::doit() should not be
called statically in...
In other words, it's being nice and letting you call the function anyway.
In addition to the other valid answers, the reason for the 2nd example working is also due to a quirk in how PHP handles objects and calls (Besides PHP 4 compatibility). Calling a non-static declared method statically from within another instance will let you access class methods on other classes as if they were local. To understand, let's take an example:
class A {
public function foo() {
echo get_class($this) . "\n";
}
}
class B {
public function bar() {
A::foo();
}
}
$a = new a();
$a->foo(); // "A"
$b = new B();
$b->bar(); // "B"
Did you see what happened there? Because you called the A::foo() method from within another class's instance, PHP treated the call as if it was on the same instance. Note that there is no relationship between A and B other than the fact that B calls A. Within A->foo(), if we did $this instanceof A (or $this instanceof self), it would fail and return false! Quite unusual...
Now, I first thought it was a bug, but after reporting it, it's apparently as designed. It's even in the docs.
Note that this will not work with E_STRICT mode enabled. It also will not work if you declare a method as static.
The difference is that static functions can be used without having to create an instance of the class.
Have a look at this great PHP OOP beginner tutorial here. It explains in more detail with an example under the Static Properties and Methods section.
Second bit shouldn't work as you should call it by
$something = new something();
$something->doit();
Static functions allows you to call a function within a class without consturcting it.
So basically if you have a class to handle users, so a function that logs the user in should be a static function, as in the constructor of that class you will probably gather the user information and you cannot do so without logging him in.
Your second example is wrong. Using a static method does not create an instance of the class. Your second example should look like this:
$x = new something();
$x->doit();
Static methods should be declared static for minimum two reasons:
a) when using E_STRICT error_reporting, calling non static method as static will generate error:
Strict standards: Non-static method something::doit() should not be called statically
b) based on keyword static some IDE's filter method possible to run at auto-complete.

Categories