Navigating Relative Links in PHP (using NetBeans)

Ask about general coding issues or problems here.

Moderators: egami, macek, gesf

Post Reply
phpnewbie2019
New php-forum User
New php-forum User
Posts: 7
Joined: Sat May 18, 2019 4:42 am

Sat May 18, 2019 5:15 am

Hi PHP Community,

I am learning PHP as a hobby. I am running this project on Apache Netbeans IDE 11 in xubuntu.
I am quite confused on how PHP interpreter is reading relative links. I have a project file structure below.
Image

When I navigate to the http://localhost:8000/Chapter7/advancedOOPFeatures.php i can see my include_once entries that imports classes from the classes folder and the include statements make sense.

Code: Select all

<?php
            include_once './Interfaces/iPillage.php';
        
            include_once './Classes/Employee2.php';
            include_once './Classes/Manager2.php';
            include_once './Classes/Employee3.php';
            include_once './Classes/Executive3.php';
            include_once './Classes/Executive4.php';
            include_once './Classes/Assistant.php';
            
        
            $emp1 = new Employee2();
            $emp1->setName("Alden");
            
            $emp2 = $emp1; // Assigning objects are treated as reference.
            $emp2->setName("Vivor");
            
            printf("<br />Employee 1 = {$emp1->getName()}");
            printf("<br />Employee 2 = {$emp2->getName()}");
            
            $emp3 = clone $emp2;
            $emp3->setName("Stupida");
            printf("<br />Employee 3 = {$emp3->getName()}");
            
            $manager = new Manager2;
            $manager->setName("Ericson Marcial");
            
            $manager2 = clone $manager;
            printf("<br /><br />Manager 1: {$manager->getName()} ");
            printf("<br />Manager 2: {$manager2->getName()} ");
            
            printf("<br /><br />Manager 3 (.self, .static):");
            printf("<br />%s", Executive3::watchTV());
            
            $executive4 = new Executive4();
            $assistant = new Assistant();
            printf("<br /><br />Executive 4 (interfaces, implementation):");
            printf("<br />Executive EmptyBank Account: {$executive4->emptyBankAccount()}");
            printf("<br />Executive Burn Documents: {$executive4->burnDocuments()}");
        ?>
The Executive4 Class Code:

Code: Select all

include_once 'Employee3.php';
include_once './Interfaces/iPillage.php';

class Executive4 extends Employee3 implements iPillage {
    private $totalStockOptions;
    
    function emptyBankAccount() {
        printf("<br />Call CDO and ask to transfer fund to Swiss bank account.");
    }
    
    function burnDocuments() {
        printf("<br />Torch the office suite.");
    }
}
Assistant Class Code:

Code: Select all

include_once 'Employee3.php';
include_once './Interfaces/iPillage.php';

class Assistant extends Employee3 implements iPillage {
    function takeMemo() {
        printf("<br />Taking Memo");
    }
    
    function emptyBankAccount() {
        printf("<br />Call CDO and ask to transfer fund to Swiss bank account.");
    }
    
    function burnDocuments() {
        printf("<br />Torch the office suite.");
    }
}
My question is, based on the folder structure on the first screenshot, why do I have to import Employee3.php and iPillage.php the way they are declared?
As in

Code: Select all

include_once 'Employee3.php';
include_once './Interfaces/iPillage.php';
because when I replace the include_once statements with

Code: Select all

include_once './Employee3.php';
include_once '../Interfaces/iPillage.php';
That part of the code returns an error saying
[Sat May 18 20:36:46 2019] PHP Warning: include_once(/Interfaces/iPillage.php): failed to open stream: No such file or directory in /home/sheenlim08/NetBeansProjects/php-basics/Chapter7/Classes/Executive4.php on line 15
[Sat May 18 20:36:46 2019] PHP Warning: include_once(): Failed opening '/Interfaces/iPillage.php' for inclusion (include_path='.:/usr/share/php') in /home/sheenlim08/NetBeansProjects/php-basics/Chapter7/Classes/Executive4.php on line 15

My understanding was when a Class needs to import another resource declared from another file, it will start looking from its own directory? so why iPillage declared as

include_once './Interfaces/iPillage.php';

instead of

include_once '../Interfaces/iPillage.php';
User avatar
hyper
php-forum GURU
php-forum GURU
Posts: 854
Joined: Mon Feb 22, 2016 5:52 pm

Sat May 18, 2019 10:22 am

Yeah, it is confusing - it's to do with current working directory (or the first file executed).

Along time ago I stopped trying to work out relative paths for includes and started to use absolute paths. I do this by defining the ROOT directory as a constant:

Code: Select all

define('ROOT',$_SERVER['DOCUMENT_ROOT']);
Then you just use

Code: Select all

include_once ROOT . '/Classes/Employee2.php';
etc..

The problem that you have with including one file from another is that if you make any changes, such as no longer needing a particular file, it's a nightmare to work through all of the files to update them (the same as defining static html pages and then changing the logo, you have to manually make the change in each file) and as your code shows, you are including the files twice, although it won't be loaded, IMO it's bad practice.

Also, you can make use of an autoloader for classes:

Code: Select all

spl_autoload_register(function ($class_name){
  include ROOT."/classes/$class_name.php"; # assumes you have declared ROOT as a constant
});
Once this line has executed, you only need:

Code: Select all

$emp1 = new Employee2();
and PHP will load the appropriate class file (Employees2) for you.

It's a good question to ask rather than just blindly going through a tutorial - keep questioning and asking :D
phpnewbie2019
New php-forum User
New php-forum User
Posts: 7
Joined: Sat May 18, 2019 4:42 am

Sat May 18, 2019 2:47 pm

hyper wrote:
Sat May 18, 2019 10:22 am
Yeah, it is confusing - it's to do with current working directory (or the first file executed).

Along time ago I stopped trying to work out relative paths for includes and started to use absolute paths. I do this by defining the ROOT directory as a constant:

Code: Select all

define('ROOT',$_SERVER['DOCUMENT_ROOT']);
Then you just use

Code: Select all

include_once ROOT . '/Classes/Employee2.php';
etc..

The problem that you have with including one file from another is that if you make any changes, such as no longer needing a particular file, it's a nightmare to work through all of the files to update them (the same as defining static html pages and then changing the logo, you have to manually make the change in each file) and as your code shows, you are including the files twice, although it won't be loaded, IMO it's bad practice.

Also, you can make use of an autoloader for classes:

Code: Select all

spl_autoload_register(function ($class_name){
  include ROOT."/classes/$class_name.php"; # assumes you have declared ROOT as a constant
});
Once this line has executed, you only need:

Code: Select all

$emp1 = new Employee2();
and PHP will load the appropriate class file (Employees2) for you.

It's a good question to ask rather than just blindly going through a tutorial - keep questioning and asking :D
Thanks hyper.
You were absolutely right, I removed my include_once statements on the classes and just added my include_once for the classes in the advancedOOPFeatures.php that worked. further more, I added the autoloader

Code: Select all

	spl_autoload_register(function ($class_name)
	{
		include_once './Classes/'.$class_name.'.php';
	});
to load my classes in advancedOOPFeatures.php.

Is there any way to create autoloader for interfaces or other object types like traits?

I created this autoloader

Code: Select all

spl_autoload_register(function ($name)
            {
                if ($name[0] == 'i') {
                    include_once './Interfaces/'.$name.'.php';
                }
                
                else {
                    include_once './Classes/'.$name.'.php';
                }
            });
Is this a good practice? it looks like i just need to enforce the naming conventions for the classes, interfaces, traits.
phpnewbie2019
New php-forum User
New php-forum User
Posts: 7
Joined: Sat May 18, 2019 4:42 am

Sat May 18, 2019 6:54 pm

Is it also possible to create a PHP file and then declare the autoloader for the entire project?
I tried creating autoloader.php

Code: Select all

spl_autoload_register(function ($objectName) {
    switch ($objectName[0]) {
        case 'c':
            include_once './cEntities/' . $objectName . '.php';
            break;

        case 'i':
            include_once './iEntities/' . $objectName . '.php';
            break;

        case 't':
            include_once './tEntities/' . $objectName . '.php';
            break;

        default:
            break;
    }
});
then include a include_once statement in of the php webpages.

Code: Select all

include_once '/home/sheenlim08/NetBeansProjects/php-basics/autoLoader.php';
This is my entire advancedOOPFeatures.php

Code: Select all

<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        <?php
        printf("%s", getcwd());
        include_once '/home/sheenlim08/NetBeansProjects/php-basics/autoLoader.php';
        
        use php_basics\cEntities;
        
        $emp1 = new cEntities\cEmployees("Sheen Ismhael", "Lim", 29);
        
        printf("Employee 1: {$emp1->getFullName()}, is age: {$emp1->getAge()}");
        ?>
    </body>
</html>
But when loading the advancedOOPFeatures.php, i get this error.
[Sun May 19 10:43:45 2019] PHP Fatal error: Uncaught Error: Class 'php_basics\cEntities\cEmployees' not found in /home/sheenlim08/NetBeansProjects/php-basics/Chapter7/advancedOOPFeatures.php:19
Stack trace:
#0 {main}
thrown in /home/sheenlim08/NetBeansProjects/php-basics/Chapter7/advancedOOPFeatures.php on line 19
[Sun May 19 10:43:45 2019] 127.0.0.1:54914 [500]: /Chapter7/advancedOOPFeatures.php - Uncaught Error: Class 'php_basics\cEntities\cEmployees' not found in /home/sheenlim08/NetBeansProjects/php-basics/Chapter7/advancedOOPFeatures.php:19
Stack trace:
#0 {main}
thrown in /home/sheenlim08/NetBeansProjects/php-basics/Chapter7/advancedOOPFeatures.php on line 19

Any ideas? it seems a hassle for the need to declare spl_autoload_register for every php webpage.
phpnewbie2019
New php-forum User
New php-forum User
Posts: 7
Joined: Sat May 18, 2019 4:42 am

Sat May 18, 2019 10:21 pm

I finally gave up and just put my trails, classes, and interfaces under one folder.

Code: Select all

NetBeansProjects/php-basics/
├── autoLoader.php
├── Chapter7
│   └── advancedOOPFeatures.php
├── Entities
│   ├── cEmployees.php
│   └── iEmployees.php
├── index.php
├── meta
│   └── nbproject
│       ├── private
│       │   └── private.properties
│       ├── project.properties
│       └── project.xml
└── tEntities

6 directories, 8 files
And changed my autoloader.php

Code: Select all

<?php
spl_autoload_register(function ($objectName) {
    if (!defined("ROOT")) {
        define ('ROOT', filter_input(INPUT_SERVER, 'DOCUMENT_ROOT'));
    }
    
    $entityName = str_replace("\\", DIRECTORY_SEPARATOR, $objectName);
    include_once ROOT.'/Entities/' . getClassNameFromNameSpace($entityName) . '.php';;
});

function getClassNameFromNameSpace($namespace) {
    $parts = explode("/", $namespace);
    
    return end($parts);
}
User avatar
hyper
php-forum GURU
php-forum GURU
Posts: 854
Joined: Mon Feb 22, 2016 5:52 pm

Sun May 19, 2019 7:39 am

Read this link I offered earlier

Use the

Code: Select all

define('ROOT', $_SERVER['DOCUMENT_ROOT']);
rather than

Code: Select all

'./cEntities/' . $objectName . '.php';
also note, when using autoloaders, you don't need to use include_once since it will only load the file if it can't find the class definition the first time.
phpnewbie2019
New php-forum User
New php-forum User
Posts: 7
Joined: Sat May 18, 2019 4:42 am

Sun May 19, 2019 5:32 pm

Thank you, can you also help on how to figure out how to determine what object resource is being loaded. I'd like to separate my classes, interfaces for Entities, and classes that will be used as infrastructure objects (like EmployeeFactory, DatabaseController), so if a PHP file would like to load a class or interface, i need my spl_autoload_register what directory to look for.
User avatar
hyper
php-forum GURU
php-forum GURU
Posts: 854
Joined: Mon Feb 22, 2016 5:52 pm

Tue May 21, 2019 11:30 am

You can do anything you want to do in your autoload function, so it's up to you how you determine what's what, where and how it's stored.

Just keep it simple for your own sanity.
Post Reply