0

I did brows through that question page but the answers given, I felt, didn't sufficiently answer the question I was struggling with.

I have a little php project. In this project I define two classes both in their own files. The second class extends the first class. In the app.php file I instantiate the second class and call a method.

├── Models/
│   ├── Class1.php
│   └── Class2.php
└── app.php
<?php
// Class1.php

namespace Models\Class1;

/**
 * Class1 does things
 */
class Class1 {
  public function someMethod() {
    // code
  }
}
<?php
// Class2.php

namespace Models\Class2;

use \Models\Class1\Class1;

include('./Class1.php');


/**
 * Class2 does other things
 */
class Class2 extends Class1 {
  public function someMethod() {
    // code
  }
}
<?php
// app.php

use Models\Class1\Class1;
use Models\Class2\Class2;

require('Models/Class1.php');
require('Models/Class2.php');

$c1 = new Class1();
$c2 = new Class2();

Now I'm not a really experienced php programmer but I thought I had a solid grasp of this class/namespace business and including/requiring files, but apparently I don't. If I copy the methods I need from Class1 to Class2 and not extend/include/use Class2 everything works fine, but want to extend Class1, so I don't have to repeat myself! Damn it!

This is what I get when running the app.php file.

Warning: include(../Models/Class1.php): failed to open stream: No such file or directory in C:\wamp64\www\project\Models\Class2.php on line 8

Warning: include(): Failed opening '../Models/Class1.php' for inclusion (include_path='.;C:\php\pear') in C:\wamp64\www\project\Models\Class2.php on line 7

I've been reading all I can find online about this stuff, have written and rewritten my code in a dozen different ways but have yet to find a solution. Some help would be greatly appreciated!

Community
  • 1
  • 1
AndrewRMillar
  • 608
  • 1
  • 7
  • 17
  • 4
    Relative paths are relative to the current working dir. (Use `__DIR__` if you must establish relations). In this case, you don't. The `extends` declaration by itself does not require Class1 to be defined - that is, until you actually instantiate it. – mario Sep 10 '19 at 07:14
  • 2
    i.e. `include(__DIR__ . '/Class1.php');` – Patrick Fay Sep 10 '19 at 07:16
  • Possible duplicate of [require\_once :failed to open stream: no such file or directory](https://stackoverflow.com/questions/5116421/require-once-failed-to-open-stream-no-such-file-or-directory) – yivi Sep 10 '19 at 07:22
  • 1
    @mario --- Does this mean that it is not essential to include 'Class1.php' in 'Class2.php' because it is sufficient to reference `Class1` in 'app.php' only where it is actually instantiated? – RWRkeSBZ Sep 10 '19 at 23:46
  • @mario From a different question, sort of related, I understood that is it best practice to always use absolute file paths so using `include(__DIR__ . './DB.Class.php');` in Class2.php, this did solve a number of problems. But apparently I should be only including in either the app.php or the Class2.php file, not both. – AndrewRMillar Sep 11 '19 at 09:11
  • 1
    If you have a central configuration file (as in: autoloader), perhaps even located at the document root, relative paths are often sufficient. (As would be using it in your main app.php script). The `__DIR__` workaround is mostly advisable for include-based direct references. (As aside: If you're free to not use the en.vogue mixed-case filenames, then don't. That's both a 90s remnant and not quite language compliant if used verbatim for autoloading. PHP identifiers are case-insensitive.) – mario Sep 11 '19 at 12:51
  • @mario On your aside: I thought that files containing only a class should get a file name referring to the class name, also getting the capitalization that the class name gets. – AndrewRMillar Sep 11 '19 at 13:43
  • That's a demerit of PSR-1/4 et al. Poor standards are better than none. But it's not very Unix-compliant, nor in line of how core PHP would look up class files (`spl_autoload`). You can do it, of course, but bear in mind that this introduces an imparity between filesystem and language case handling. – mario Sep 11 '19 at 13:59

2 Answers2

2

Quick Answer

Unless you've specified the full path to the include file, PHP will always try to resolve files according to the location of the entry script. In your case, your entry script seems to be app.php at the root of the application.

So in your Class2.php, instead of include('./Class1.php');, you should write include('Models/Class1.php'); and things should work. But while it fix a short term problem, your code would be really non-portable. Besides, including the same file twice would result in another error (e.g. re-declaring class).

Slightly Smarter Approach

A slightly smarter would be to do this in Class2.php instead.

<?php
// Class2.php

namespace Models\Class2;

use \Models\Class1\Class1;

include_once __DIR__ . '/Class2.php';

/**
 * Class2 does other things
 */
class Class2 extends Class1 {
  public function someMethod() {
    // code
  }
}

The variable __DIR__ will always be resolved to the directory of the script file, not the entry script.

But again, it is clumsy and error prone to do file includes by hand.

An Even Smarter Approach: Autoload

PHP supports file autoloading when a class is declare. That is to have a piece of code to do the file include when a class is called. If you want to have fun, you're welcome to write your own autoloader function and register to spl_autoload_register. With a combination of the __DIR__ technique we talked about, you can easily resolve the autoloading path from namespace.

A quick and ugly autoloading app.php would probably look like this:

<?php
// app.php

use Models\Class1\Class1;
use Models\Class2\Class2;

spl_autoload_register(function ($class_name) {
        $realClassName = basename(str_replace('\\', DIRECTORY_SEPARATOR, $class_name));
        include_once __DIR__ . DIRECTORY_SEPARATOR . 'Models' . DIRECTORY_SEPARATOR . $realClassName . '.php';
});

$c1 = new Class1();
$c2 = new Class2();

The Smart Approach: Composer Autoload

If you're learning OOP PHP today, I'd highly recommend you to learn Composer and PSR-4. PSR-4 defines how you should structure a PHP application for class autoloading. Composer implements a PSR-4 autoloader by default.

First you should comply with the namespace standard. The least change to do is to loose the extra "Class1" and "Class2" in your namespace:

<?php
// Class1.php

namespace Models;

/**
 * Class1 does things
 */
class Class1 {
  public function someMethod() {
    // code
  }
}

<?php
// Class2.php

namespace Models;

use \Models\Class1;


/**
 * Class2 does other things
 */
class Class2 extends Class1 {
  public function someMethod() {
    // code
  }
}

With a nicely structured application folder, namespace structure and a correctly written autoload.psr-4 section in composer.json, composer will help you to generate a class autoloader. Your composer.json would probably look like this:

{
    "autoload": {
        "psr-4": {
            "Model\\": "Model/"
        }
    }
}

You may now run composer dump-autoload to create the autoloader. Then you can simply add this to your entry script app.php. When things are ready, you may simply add use and new statement anywhere in the application.:

<?php
// app.php

use Models\Class1;
use Models\Class2;

require_once './vendor/autoload.php';

$c1 = new Class1();
$c2 = new Class2();

All includes are handled by the autoloader. Hands free.

Koala Yeung
  • 7,475
  • 3
  • 30
  • 50
  • That 'spl_autoload_register' thing seems like dark magic. It isn't called anywhere but soes it's thing in the background, unseen. I think I will try to go the composer rout, I love standards so I will study the spr-4 standard and the php-fig site. Thanks for your exhaustive answer! – AndrewRMillar Sep 12 '19 at 09:37
  • Composer also use `spl_autoload_register` in the background :-). If you want to learn, you may use `var_dump` in your registered function to see when and where it is triggered. Of course, using composer autoload is a lot simpler than hand write you r own autoloader. PSR-4 has become a defacto standard in PHP world so it is only smart to follow it than bake up your own practice. – Koala Yeung Sep 12 '19 at 09:41
-2

Remove the line

include('./Class1.php');
illia permiakov
  • 423
  • 3
  • 10
  • Apparently adding `include(__DIR__ . './DB.Class.php');` solved a lot of problems. – AndrewRMillar Sep 11 '19 at 09:02
  • Illia (the '@' isn't working for your handle...) at first I gave you a down vote but after some trying found out your answer actually solved an error then upvoted your answer. – AndrewRMillar Sep 11 '19 at 13:52
  • 1
    If I come right down to it this is the easiest solution to get rid of the Warnings. The only reason that I accept the other answer is because there is a load of info to get a better solution to the problem I am trying to solve with my little script. – AndrewRMillar Sep 12 '19 at 09:30