1

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/

vlio20
  • 8,955
  • 18
  • 95
  • 180
  • Can you tell us more about your class B? What is its filename, where is it in the folder structure, do you have a `namespace ...` (and what namespace is it) above your `class B extends A`, etc. – Unnawut Jul 16 '14 at 19:17
  • And the `B` class file is at `app/B.php`? – Unnawut Jul 16 '14 at 19:27
  • No, it is : factory file located at `app/dir1/` , Class A and B located at `app/dir1/dir2/` – vlio20 Jul 16 '14 at 19:29
  • Ah that's the problem then. PSR-0 expects that it finds all the classes under namespace `ValidatorFactory` in `app/` as you defined in composer.json. Try move class A and B to `app/` to see if it works. Then talk about better structure after it works. – Unnawut Jul 16 '14 at 19:33
  • I have moved all 3 files to `app/` but now i am getting this: `Class 'ValidatorFactory\B' not found`. The error I happening in the factory class, at this line: `return new B();` – vlio20 Jul 16 '14 at 19:39
  • Have you done `composer dump-autoload` or maybe `composer update` after that? Composer will need to walk through your folders again to pick up the new files. – Unnawut Jul 16 '14 at 19:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/57441/discussion-between-vlio20-and-unnawut). – vlio20 Jul 16 '14 at 19:41

2 Answers2

2

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.

Community
  • 1
  • 1
Jeff Lambert
  • 24,395
  • 4
  • 69
  • 96
  • Thanks for the tip, but why the composer not loading the namespace as it supposed to? – vlio20 Jul 15 '14 at 20:48
  • It's not supposed to load the namespace, you still must use the namespace or include a `use` statement, or you can modify your `config.php` file and add an alias for it so you can use it however you want. – user1669496 Jul 15 '14 at 20:50
  • If you will see the tutorial, the only thing that he did in order to include the namespace in the composer autoload is to add it in the composer. no config file editing. – vlio20 Jul 15 '14 at 20:54
  • @vlio20 he's just giving you another option. Laravel will let you make aliases so that referencing classes inside of the IoC container is easier. I don't think you need to worry about that too much. – Jeff Lambert Jul 15 '14 at 20:55
  • @watcher, I have set a namespace to the factory class and have added it to the composer, still I can't access it without the prefix. Why? – vlio20 Jul 15 '14 at 20:58
  • He prefixed his class in that tutorial with the namespace. He had `return new Acme\Foo->bar()`. When he added it to the autoloader, he was then able to take out the `require appPath()...` portion so you could see the class was being autloaded, but he still had to prefix it with the namespace. To clarify, what composer is doing is loading the classes by the namespace, so it's searching all files for the namespace of Foo in the path you gave it and if it finds that namespace, it will then load that file. – user1669496 Jul 15 '14 at 20:58
  • One more thing, I am now getting: `Class 'B' not found`. I have added the name space to A, B and the factory class. Why? – vlio20 Jul 15 '14 at 21:04
  • If you added the namespace to class B, then your invocation should be `return new B();` (because its no longer in the global namespace) – Jeff Lambert Jul 15 '14 at 21:08
  • @watcher, So in the factory I should do this: `return new B()` and not `return new \B()`? in both cases I am getting this error: `Class 'ValidatorFactory\B' not found` or `Class 'B' not found` – vlio20 Jul 15 '14 at 21:18
  • Make sure you have `namespace ValidatorFactory;` in both your class a and class b files and run `composer dump-autoload` so they get loaded. – user1669496 Jul 15 '14 at 21:20
  • @user3158900, I have added the name space to all 3 classes but still getting the same error. Any suggestions? – vlio20 Jul 16 '14 at 18:02
  • All those classes reside in the same folder? – user1669496 Jul 16 '14 at 18:11
  • @vlio20 Can you update your question with what the code you have so far resulting from this answer? – Unnawut Jul 16 '14 at 19:09
  • @user3158900, no the factory is in 'app/dir1/' and A,B are in 'app/dir1/dir2/'. – vlio20 Jul 16 '14 at 19:15
  • That's your problem then. They still aren't being loaded because the namespace for `A` and `B` should be `ValidatorFactory\dir2` and in your `ValidatorFactory` class, be sure you have the statement `use ValidatorFactory\dir2;` then you should be able to use them like `new A()` or `new B()` inside `ValidatorFactory`. This might help http://stackoverflow.com/questions/18146057/composer-autoloader-psr-0-namespaces – user1669496 Jul 16 '14 at 19:28
  • For testing as @Unnawut, has suggested I have moved all 3 files to app/ but now i am getting this: Class 'ValidatorFactory\B' not found. The error I happening in the factory class, at this line: return new B();. My composer has this: `"psr-0": { "BetTypeValidatorFactory": "app/" }` – vlio20 Jul 16 '14 at 19:40
1

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;
Community
  • 1
  • 1
Unnawut
  • 7,500
  • 1
  • 26
  • 33
  • Thank you so much! One thing, why we need to name `"psr-4": { "MyValidator\\": "app/MyValidator" }` with double slashes 'MyValidator\\'? – vlio20 Jul 17 '14 at 05:58
  • Hmm actually the only thing that might be wrong with the psr-0 we used was the double trailing slashes! https://getcomposer.org/doc/04-schema.md#psr-4. They are for "to avoid conflicts between similar prefixes. For example Foo would match classes in the FooBar namespace so the trailing backslashes solve the problem: Foo\\ and FooBar\\ are distinct." – Unnawut Jul 17 '14 at 07:30
  • In the tutorial, he didn't add any slashes. why then we need to add them? – vlio20 Jul 17 '14 at 09:03
  • 1
    From my understanding if you set `"Foo" : "app/Foo"` and `"FooBar" : "app/FooBar"`, and you have classes `Foo\MyClass` and `FooBar\MyClass`. You might end up getting `app/Foo/MyClass.php` when accessing `FooBar\MyClass`. The `Foo\\` makes sure that it does not also match for `FooBar` namespace. – Unnawut Jul 17 '14 at 09:26