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.
Related
I have a registration class. The problem I'm facing is that the instantiation itself is somehow causing the functions within that class to be called.
I've tested this by adding an error_log() directly before and after the instantiation: $register = new Register(); every time I receive another error_log() which I placed inside the functions of the class that I'm instantiating.
How can I solve this?
EDIT this is what an example may look like:
testclass.php
class Test {
public function test() {
error_log("Function test() was run.'");
}
}
test.php
require_once("testclass.php");
$test = new Test();
It's because your function, test() has the same name of the class Test so PHP is using it as the constructor and calling it when you instantiate it.
class Bar {
public function Bar() {
// treated as constructor in PHP 5.3.0-5.3.2
// treated as regular method as of PHP 5.3.3
}
}
As the docs say:
Warning Old style constructors are DEPRECATED in PHP 7.0, and will be
removed in a future version. You should always use __construct() in
new code.
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?
I recently upgraded to PHP 5.3.8 to 5.3.20. Once the upgrade was in place, I started receiving numerous Cannot redeclare errors:
[20-Dec-2012 11:15:00 America/New_York] PHP Fatal error: Cannot redeclare class user in /var/www/htdocs/includes/mysql.php on line 3
In one of my PHP classes, I instantiate a mysql class in a constructor using code like:
class foo {
function foo() {
global $db;
require '/var/www/htdocs/includes/mySQL.php';
$db = new mydb_driver();
}
function show_text() {
require '/var/www/htdocs/includes/mySQL.php';
$db2 = new mydb_driver();
}
}
In the same class, I create a function that instantiates the same mysql class using a different variable so I can connect to a different database. This second require is what is triggering the errors and I am unsure why. If I remove the require from show_text or change it to require_once it works fine.
Does anyone know what changed between these two versions that would cause this code to fail? I am not even sure how $db2 is being properly intialized without the require. Does requiring the class constructor make it globally visible in the file?
Edit:
This is not a logging issue and the errors were not happening before the upgrade. The apps were working fine until the upgrade. This how I have enabled errors in my error logging:
error_reporting = E_ALL & ~E_NOTICE
Try this
require_once '/var/www/htdocs/includes/mySQL.php';
class foo {
function foo() {
global $db;
$db = new mydb_driver();
}
function show_text() {
$db2 = new mydb_driver();
}
}
Requires belong at the top of the file, and although slower, require_once will prevent the file from being required twice, giving you that error
can't really assume to know what you are trying to do. But you could switch it to something like this too, to get rid of globals and confine more to the PSR standards
require_once '/var/www/htdocs/includes/mySQL.php';
class foo {
private $db;
public function __construct( )
{
$this->db = new mydb_driver();
}
public function bar() {
// Do bar stuff with $this->db
}
public function show_text() {
// Doo show_text stuff with $this->db
}
}
In you class, change require to require_once. This ensures that file is loaded at most once.
You also got those errors on your previous PHP version. It's just that you only now have errors enabled (which is something you should always do in a development environment). You can only "load" the class once. When you are going to deploy it to the product environment you should still have errors enabled, but only log them instead of displaying them.
To enable error reporting you can do:
error_reporting(-1); // or you could do `E_ALL` in PHP 5.4
ini_set("display_errors", 1);
Also you should stop using the global keyword. It is bad practice because you make it impossible to test your code, introduce tight coupling and may introduce unpredictable results.
class foo {
private $db;
public function __construct($db) {
$this->db = $db;
}
public function foo() {
$this->db->doSomething();
}
public function show_text() {
$this->db->doSomething();
}
}
require '/var/www/htdocs/includes/mySQL.php';
$db = new Mydb_driver();
$foo = new Foo($db);
Note: that I have also added the visibility to your methods (public). Although they are public by default it is always better to add it so people would not think it is a mistake.
You may also want to read into autoloading your classes/ This prevent you from doing require_*, include_* and friends so it helps keeping your code clean, prevent errors when forgetting to load classes and would also prevent errors like you got now.
I have a PHP class that needs some pre-defined globals before the file is included:
File: includes/Product.inc.php
if (class_exists('Product')) {
return;
}
// This class requires some predefined globals
if ( !isset($gLogger) || !isset($db) || !isset($glob) ) {
return;
}
class Product
{
...
}
The above is included in other PHP files that need to use Product using require_once. Anyone who wants to use Product must however ensure those globals are available, at least that's the idea.
I recently debugged an issue in a function within the Product class which was caused because $gLogger was null. The code requiring the above Product.inc.php had not bothered to create the $gLogger. So The question is how was this class ever included if $gLogger was null?
I tried to debug the code (xdebug in NetBeans), put a breakpoint at the start of Product.inc.php to find out and every time it came to the if (class_exists('Product')) clause it would simply step in and return thus never getting to the global checks. So how was it ever included the first time?
This is PHP 5.1+ running under MAMP (Apache/MySQL). I don't have any auto loaders defined.
Thanks for the informative answers guys. My belief was that when you
include a file PHP starts executing it line by line from line one, so
it would not allow me to include the file if the globals were not
defined. I will move the checks into the constructor. Based on the
original question, I accept the answer from #deceze
The file is parsed before it is executed. Classes are "loaded" by parsing, but functions are executed after the parsing. By putting the function call in the same file as the class, the class is always parsed and "loaded" before that function executes, thereby it's always true.
If you are always including the file using require_once (which is good), there's no point in that check anyway. A class definition shouldn't conditionally depend on some global variables. Rethink what you're doing here.
I see a main issue here:
// This class requires some predefined globals
That might be surprising to you, but I think what you actually want to do is, that if that is the case, you don't check that when you define the class, but when you instantiate it.
When a class is instantiated, it's constructor function is called. This seems like the perfect place to me to check for that:
class Product
{
public function __construct() {
// This class requires some predefined globals
$this->needGlobal('gLogger', 'db', 'glob');
}
private function needGlobal() {
foreach (func_get_args() as $global) {
if (!isset($GLOBALS[$global])) {
throw new RuntimeException(sprintf('Global %s needed but not set.', $global));
}
}
}
...
}
When you instantiate a Product it then automatically checks if the preconditions are met:
$blueShoes = new Product();
This will not work if the pre-conditions are not met, but it will work if.
But that is only partially solving your problem. The real problem with your code is that the Product needs global variables to work.
Instead make the product just take the things it needs to work with:
class Product
{
private $gLogger;
private $db;
private $glob;
public function __construct(LoggerInterface $gLogger, DbInterface $db, GlobInterface $glob) {
$this->gLogger = $gLogger;
$this->db = $db;
$this->glob = $glob;
}
...
}
Usage:
$redShoes = new Product($gLogger, $db, $glob);
And then you don't need to care about anything global inside Product any longer.
You commented you want to gradually improve the code. You can do so, here is how. As written the second variant above is the way to go, but currently the legacy code is not compatible with it. In any case if the Product class is new code, you should write it with dependency injection. This is important to separate the legacy code from the new code. You don't want to have the legacy stuff swallowed by your new code. That would make new code legacy code, so you would not be able to gradually improve. You would just add new legacy code.
So take the class definition with the dependency injection. For your legacy needs write a second class that is shielding this:
class ProductLegacy extends Product
{
public function __construct() {
// This class requires some predefined globals
list($gLogger, $db, $glob) = $this->needGlobal('gLogger', 'db', 'glob');
parent::__construct($gLogger, $db, $glob);
}
private function needGlobal() {
$variables = array();
foreach (func_get_args() as $global) {
if (!isset($GLOBALS[$global])) {
throw new RuntimeException(sprintf('Global %s needed but not set.', $global));
}
$variables[] = $GLOBALS[$global];
}
return $variables;
}
}
As you can see, this little stub brings together the global way of doing things with the new way. You can use the Product class in your new code, and if you need to interface with old code, you use the ProductLegacy class that works with the global variables for class instantiation.
You could also create a helper function that is doing this, so you can use it for different classes. Depends a bit on your needs. Just find a border where you can draw a clear line between old code and new code.
I am still looking for a way to phrase it properly (I'm not a native speaker...).
So I have this class SQL which implements the singleton pattern (for obvious reasons) and I also have this function, checkUsr(), which queries the database using one of SQL's methods.
Everything works fine as long as I don't call checkUsr() from within the SQL class. When I do so, the scripts just exits and a blank page is displayed - no errors are returned, no exception is thrown... What's happening? And how do I work around this problem?
EDIT:
some code here:
class SQL
{
public static function singleton()
{
static $instance;
if(!isset($instance))
$instance = new SQL;
return $instance;
}
public function someOtherFun()
{
checkUsr();
}
public function tryLoginAuthor( $login, $sha1 )
{
// SQL query
}
}
function checkUsr()
{
if (!isset($_SESSION['login']) || !isset($_SESSION['sha1']))
throw new Exception('Not logged in', 1);
$SQL = SQL::singleton();
$res = $SQL->tryLoginAuthor($_SESSION['login'], $_SESSION['sha1']);
if (!isset($res[0]))
throw new Exception('Not logged in', 1);
}
So the problem occurs, when I call checkUsr from within the SQL class. It does not, however, happen when called from some other class...
You have to turn on error_reporting, to see the error messages by php, otherwise you will get the blank page you describe.
At the top of your index.php file, include these:
ini_set('display_errors', true);
error_reporting(E_ALL | E_STRICT);
Don't forget to turn it off in your production machine, this is only for development.
You have declared the variable $instance, as static inside the function, instead of inside class. These are two completely different things. See the usage of static variables here, and see the usage of a static class property here. You need the latter, so change your code to this:
class SQL {
static $instance;
public static function singleton()
{
if(!isset(self::$instance))
self::$instance = new SQL;
return self::$instance;
}
...
}
Implementing a SQL class, or any kind of database access as a singleton is a very bad idea, it's going to bite you very hard in the long run. If it turns out that you
need to support another database, like you need to pull information from a forum, that is in a different DB than your site, you will have serious problems.