vlio20
vlio20

Reputation: 9305

Include and namespace in php

I have created this factory class:

namespace ValidatorFactory;

foreach (glob("Types/*.php") as $filename)
{
    include $filename;
}

class ValidatorFactory
{
    public static function getValidator($betTypeId)
    {
        switch ($betTypeId)
        {
            case 1:
                return new B();
                break;

            default:
                return null;
        }
    }
}

Here is my A abstract class definition:

abstract class A
{
    abstract function validateSchema($schema);
}

Here is my B class definition:

class B extends A
{
    function validateSchema($schema)
    {
        return true;
    }
}

Now I want to use the factory class in some other file in my project, here is how I am doing it:

$obj = ValidatorFactory::getValidator($someId);

I am using Laravel and via composer (and this tutorial) I tried to load the ValidatorFactory class like built in classes of laravel. Here is what I have added to the composer file:

"autoload": {
    "classmap": [
        ...
    ],
    "psr-0": {
        "ValidatorFactory": "app/"
    }
},

I have run composer update.

My problem is that my ValidatorFactory is not loaded because I am getting this error:

Class 'ValidatorFactory' not found

If I will add the namespace like this:

$obj = ValidatorFactory\ValidatorFactory::getValidator($someId);

I am getting other error, here it is:

Class 'ValidatorFactory\B' not found

So there are 2 problems, first related to larave/composer autoload (of the namespace). The second is related to includes and inheritance (as I suspect) in php.

How can I solve this two issues? Thanks!

Update: As suggested in the answer, I have added the same namespace for all 3 involved classes A, B and the factory. No class including any other class.

In the class which calls the factory's getValidator function I am doing it like this:

$obj = ValidatorFactory\ValidatorFactory::getValidator($someId);

Still getting the error which says class B is unknown :(

Here is how the updated class B looks like:

namespace ValidatorFactory;
class B extends A
{
    function validateSchema($schema)
    {
        return true;
    }
}

The file structure looks like this: factory file located in app/dir1/ , Class A and B located in app/dir1/dir2/

Upvotes: 1

Views: 1326

Answers (2)

Unnawut
Unnawut

Reputation: 7578

From our chat discussion, we couldn't figure out why PSR-0 is not working for you. It turns out that PSR-4 configuration works fine. So I summarise here what we did in the discussion to use PSR-4 autoloading.

File structure:

app
 |- commands
 |- config
 |- ...
 |- MyValidator
    |- MyValidatorFactory.php
    |- A.php
    |- B.php
 |- ...

MyValidatorFactory.php

namespace MyValidator;

class MyValidatorFactory
{
    ...
    return new B();
    ...
}

B.php

namespace MyValidator;

class B extends A
{
    function validateSchema($schema)
    {
        return true;
    }
}

And finally you can setup PSR-4 autoloading in your composer.json:

"autoload": {
    ...
    "psr-4": {
        "MyValidator\\": "app/MyValidator"
    }
}

Bonus point:

With the psr-4 configuration above, you may also structure your namespaces and classes like this:

app
 |- commands
 |- config
 |- ...
 |- MyApp
    |- Validators
        |- MyValidatorFactory.php
        |- A.php
        |- B.php
 |- ...

And set the psr-4 config to "MyApp\\": "app/MyApp", the autoloader will recognise your classes and use it like below:

new \MyApp\Validators\MyValidatorFactory;
new \MyApp\Validators\A;
new \MyApp\Validators\B;

Upvotes: 1

Jeff Lambert
Jeff Lambert

Reputation: 24671

Anything defined in an include is defined in the global namespace, so the abstract class definitions define the \A and \B classes, respectively (and not ValidatorFactory\A or ValidatorFactory\B). In order to use them the way you are, you would need to add a use statement for each of them after they are included.

But why include anything at all? This goes agains the very idea of an autoloader. If you want to declare a class, put it in one of your namespaces and let Laravel and composer do the hard work for you.

Update

return new B();

Try changing that line to:

return new \B();

And that will clear up your second error. As for the first, you declared your Validator factory to live in a namespace:

namespace ValidatorFactory;

Any code that uses that class (which is not in the same namespace) will need to either A) import the class with a use statement, or B) use the Fully-Qualified Namespace whenever referencing it:

use ValidatorFactory\ValidatorFactory;

// ...

$factory = new ValidatorFactory();

Or

$factory = new ValidatorFactory\ValidatorFactory();

Remember, all namespaces are for is to allow you and another developer to name a class exactly the same thing. For instance, perhaps I'm working on some sort of route mapping tool. I may want to define a Route class, because that name fits extremely well with my current problem domain. However, Taylor already beat me to the punch because there already is a Route class. Using a name space I can ensure that I can still name anything I want whatever I want however I want to and there won't be any conflicts with stuff that has already been named or stuff that has yet to be named.

Update 2

Namespaces in projects using composer need to mirror the directory path in which the underlying files are located (this is how the autoloader can find them- it basically looks at the namespace and classname and turns that into a directory path to the file, and appends a .php extension). If you have classes like this:

// File: app/dir1/dir2/A.php
class A{}

// File: app/dir1/dir3/B.php
class B{}

Then A will have to be in the namespace dir1\dir2, and its fully qualified name would be dir1\dir2\A. Similarly, class B would have to be in the namespace dir1\dir3 and its fully qualified name would be dir1\dir3\B.

Upvotes: 2

Related Questions