TIP: Use Markdown or, <pre> for multi line code blocks / <code> for inline code.
These forums are read-only and for archival purposes only!
Please join our new forums at discourse.kohanaframework.org
PhpSpec and Kohana autoloader
  • I figured out how to install PhpSpec via composer, but have problem with namespace/autoloading classes. Say I want to test some helper, let it be Helper_View. I have my classes in application/classes, so this helper will be in application/classes/Helper/View.php. My composer.json file looks like this:

    { "require-dev": { "phpspec/phpspec": "2.0.*@dev" }, "config": { "bin-dir": "bin" }, "autoload": { "psr-0": { "": "application/classes/" } } }

    I write spec for it:

    namespace spec\Helper;

    use PhpSpec\ObjectBehavior; use Prophecy\Argument;

    class ViewSpec extends ObjectBehavior { function it_is_initializable() { $this->shouldHaveType('Helper\View'); } }

    and all is working fine when this helper class looks like this:

    namespace Helper; class View {}

    but my class looks like this:

    class Helper_View {}

    so the phpspec cannot find it.

    My question is how to get phpspec to work without changing all application?

  • Sorry, one more time, with code formatting.
    I figured out how to install PhpSpec via composer, but have problem with namespace/autoloading classes. Say I want to test some helper, let it be Helper_View. I have my classes in application/classes, so this helper will be in application/classes/Helper/View.php. My composer.json file looks like this: { "require-dev": { "phpspec/phpspec": "2.0.*@dev" }, "config": { "bin-dir": "bin" }, "autoload": { "psr-0": { "": "application/classes/" } } }

    I write spec for it:

    namespace spec\Helper;
    
    use PhpSpec\ObjectBehavior;
    use Prophecy\Argument;
    
    class ViewSpec extends ObjectBehavior
    {
        function it_is_initializable()
        {
            $this->shouldHaveType('Helper\View');
        }
    }
    

    and all is working fine when this helper class looks like this:

    namespace Helper; class View {}

    but my class looks like this:

    class Helper_View {}

    so the phpspec cannot find it.

    My question is how to get phpspec to work without changing all application?

  • Try $this->shouldHaveType('\Helper_View');?

  • Thanks, but the problem is not content of the test, but all this namespace/autoloading of classes stuff. I don't use namespace in my app (maybe it's time to start) and I don't know how to get phpspec to work with Kohana classes and (ideally) to have the same folder structure of spec and tested classes.

  • You don't have to namespace your app. I'm only working from your example. In it, you had:

    $this->shouldHaveType('Helper\View');

    As you say, it only works if you namespace your view. If not:

    $this->shouldHaveType('\Helper_View');

    If you need to bootstrap Kohana separately for the CFS, then do so. If this doesn't answer your question, then I'm not sure what the question is.

  • I'll try to elaborate more ;)

    I have a (Kohana's standard) folder layout:

    project
        - application
            - classes
                - helper
                    - view
        - system
        - modules
        index.php
        
    

    And a standard loader, when I call ie. Helper_View::some_method().

    Then I installed PHPSpec, and my layout became this:

    project
        - application
            - classes
                - helper
                    - view
        - spec
        - src
        - system
        - modules
        - vendor (phpspec files)
        index.php
        
    

    In red are new folders.

    In this setup all tested classes should reside in src folder, not in application/classes. So I made (after long searching) config file for PHPSpec in root dir - phpspec.yml:

    suites:
      UniqueIndicator:
        namespace: 
        src_path: application/classes
    

    So my tested files will be located in application/classes instead of src folder. Then I made changes to composer.json to change autoloader behavior:

    {
        "require-dev": {
            "phpunit/phpunit": "3.7.*",
            "phpspec/phpspec": "2.0.*@dev",
            "behat/behat": "2.4.*@stable",
            "behat/mink": "1.4.*@stable",
            "behat/mink-extension": "*",
            "behat/mink-goutte-driver": "*",
            "behat/mink-selenium2-driver": "*"
        },
        "minimum-stability": "dev",
        "config": {
            "bin-dir": "bin"
        },
        "autoload": {
            "classmap": ["application/classes/"]
        }
    }
    

    And now I have my classess autoloaded from my classes folder. This setup is already good for testing, but still one problem remains. I cannot have the same layout in "spec" folder than in my classes folder.

    I don't know what to change to have this:

    project
        - application
            - classes
                - helper
                    - view
        - spec
            - Helper
                - ViewSpec.php
            - Model
                - UserSpec.php
            ...
        
        - system
        - modules
        - vendor (phpspec files)
        index.php
        
    

    Instead, I can only have this:

    project
        - application
            - classes
                - helper
                    - view
        - spec
            - Helper_ViewSpec.php
            - Helper_UtilSpec.php
            - Model_UserSpec.php
            - Controller_Admin_UsersSpec.php
        - system
        - modules
        - vendor (phpspec files)
        index.php
        
    

    I can live with that, but it will be better to the same folder layout in spec.

  • Wow, phpspec has become a lot more convoluted since I last used it (when it still had Contexts). It seems you would have to write a new Locator for the specs, but currently the PSR-0 one seems hard-coded in the ServiceContainer, regardless of your suite options. And it doesn't implement PSR-0 fully, it appears. It doesn't help that the documentation is terrible. Where is there even mention of the yaml config in the docs?

    So it doesn't look like you can do anything about it. I have a headache from reading all that code now, my only suggestion is to post an issue, but good luck with that, I know from experience that he is very slow to reply (expect months).

    On a side note, I'm not sure that using Composer to autoload Kohana classes like that is going to be much use in the long run. You're going to have to bootstrap the CFS somewhere if your classes extend anything in /system, for example.

    Edit: I see someone has a similar issue to yours, posted 4 months ago still without reply.

  • I've had the same issue, and actually looked at implementing a custom locator but it involves quite a lot of duplication because of the way the existing PSR-0 one is defined. Of course part of the problem is the poor definition of PSR-0 which makes it much harder to map from file name back to class name than it should be. Given that's being fixed (by dropping support for underscores) in PSR-4, I doubt that we'd be successful in getting support for underscores in phpspec core. There is currently no way to avoid putting all specs in a single directory if you want to use underscores, the only option is to represent directories with namespaces for the specs.

    Medium term, I was thinking about building a Kohana extension to help with some of these issues (which could work around the hard-coded locator). But it looks like quite a time-sink, especially because of the weak documentation of the internals (there is no documentation of the yaml config, for a start) which coupled with the heavy use of interfaces and abstractions makes quite a bit of it tricky to follow without just stepping through in xdebug.

    There's lots about phpspec2 I really like, but the weak documentation is frustrating (people in glass houses? :) ) and it does become tricky if you don't want to exactly follow its conventions. It also seems to be a fairly small developer community - especially using phpspec2 which is still officially in beta. The mailing list isn't bad for replying to questions, but there's basically nobody on IRC so I've definitely found it needs a self sufficient mindset. I've a few times found myself wondering whether I shouldn't just move back to using phpunit for now but using "should" in the test names like I used to...

    In terms of using it with Kohana, I've found two options for class names:

    Use namespaces instead of underscores for classes

    For simplicity, consistency and compatibility with PSR-4 (and therefore reduced coupling to Kohana's autoloader) that felt like the best option for new code. The only issue I've found with that so far is that the core Kohana router will only map to underscored class names.

    You could extend the Route class to handle that, for now I'm working round it by adding a class alias to the controller class file - eg:

    namespace Controller\Admin;
    
    class Users extends \Controller {
    }
    
    class_alias('\Controller\Admin\Users', 'Controller_Admin_Users');
    

    That class alias will then always be defined when the autoloader attempts to load a Controller_Admin_Users. I would expect to find and work around similar problems with various other Kohana class loading code, for example in ORM or when loading DB drivers based on the config file.

    To be honest, because I'm trying to follow the clean code principles this is less of an issue - most of my specs cover business classes which can easily use namespaces, rather than eg Controllers which are just wiring stuff together.

    Tell the spec to instance a different class

    According to the phpspec mailing list, this should also work:

    namespace spec\Controller\Users;
    
    class AdminSpec extends \PHPSpec\ObjectBehaviour {
    
        public function let()
        {
            $this->beAnInstanceOf('Controller_Users_Admin');
        }
    }
    

    That feels a bit less neat, but does allow the specs to follow phpspec convention/expectation but your application code to follow Kohana norms. I'd expect problems with the PHPSpec generator with this approach, and there may be other issues too, but they'd at least be specific to your test runtime and leave your production class naming/loading/etc unchanged.

    Autoloading

    I'd really recommend against using composer autoloading for Kohana classes. You may in future want to use the composer autoloader to integrate a third party library in your live code, and that will then cause conflicts with the CFS.

    You'll also find your composer autoloader falls short if your class needs to access anything provided by Kohana - for example unless you bootstrap the Kohana autoloader, you won't even be able to inject a mock Kohana_Config into your class under spec because as far as PHPSpec that class isn't defined.

    There's currently no support for bootstrapping in phpspec itself, so the way I'm doing it is:

    namespace spec\Controller\Users;
    
    require_once(__DIR__.'/../../application/bootstrap.php');  # Obviously has to be the correct relative path from each spec file
    
    class AdminSpec....
    

    Then, split your index.php and bootstrap.php so that all the bootstrapping happens in bootstrap and index just runs the request:

    index.php

    <?php
    // Remove everything before require APPPATH.'bootstrap'.EXT;
    // Replace with:
    require (__DIR__.'/application/bootstrap.php'); # or /../application if you've moved index.php down out of the root directory
    
    if (PHP_SAPI == 'cli') ....
    

    bootstrap.php

    <?php
    /**
     * Configure the directory constants required by Kohana for the application, modules and system. This replaces the
     * code traditionally included in Kohana's index.php to allow the same bootstrap to be used for any case that requires
     * access to the Kohana environment.
     *
     * @link http://kohanaframework.org/guide/about.install
     */
    define('APPPATH', realpath(__DIR__).DIRECTORY_SEPARATOR);
    define('MODPATH', realpath(__DIR__.'/../modules').DIRECTORY_SEPARATOR);
    define('SYSPATH', realpath(__DIR__.'/../system').DIRECTORY_SEPARATOR);
    define('DOCROOT', realpath(__DIR__.'/..').DIRECTORY_SEPARATOR); // or whatever relative path to directory with index.php
    
    define('EXT', '.php');
    
    error_reporting(E_ALL | E_STRICT);
    if ( ! defined('KOHANA_START_TIME'))
    {
      define('KOHANA_START_TIME', microtime(TRUE));
    }
    
    if ( ! defined('KOHANA_START_MEMORY'))
    {
      define('KOHANA_START_MEMORY', memory_get_usage());
    }
    
    // continue the normal bootstrap from here
    

    Obviously just because you now have access to Kohana in your specs, you should still be careful to keep each class and spec isolated and minimise any dependencies on global Kohana state.

  • Andrew, thanks very much for explanations. I'll try these ideas on some test app soon. I agree the documentation of phpspec is very poor, and community small, but I hope this will change.

  • I could have saved myself the headache :P Lesson learned!

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion