Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to create Interceptor class? #5

Closed
kenjis opened this issue Jan 29, 2015 · 30 comments
Closed

How to create Interceptor class? #5

kenjis opened this issue Jan 29, 2015 · 30 comments
Labels

Comments

@kenjis
Copy link

kenjis commented Jan 29, 2015

if you want to make it work with your own autoloader, you will need to create your own Interceptor class for it

Is there any how-to documentation or samples?

@m1ome
Copy link
Collaborator

m1ome commented Jan 29, 2015

👍 Trying to get it, but it's kinda difficult 😓

@jails
Copy link
Contributor

jails commented Jan 29, 2015

It's difficult to provide a generic answer here since each custom autoloader will require a custom answer.

For example if you are not using composer, you'll need to rool you own https://github.com/crysalead/kahlan/blob/master/bin/kahlan binary script since this one has been configured for Composer. A simple copy/paste and passing your custom autoloader here https://github.com/crysalead/kahlan/blob/master/bin/kahlan#L40 should do the job.

Then you will need to roll your own kahlan-config.php with something like this:

<?php
use filter\Filter;

// The original code for composer is there
// https://github.com/crysalead/kahlan/blob/master/src/cli/Kahlan.php#L312-320
Filter::register('my.custom.interceptor', function($chain) {
    Interceptor::patch([
        'loader' => [$this->_autoloader, 'loadClass'],
        'loadClass' => 'loadClass', // To change according your custom loader
        'findFile' => 'findFile', // To change according your custom loader
        'add' => 'add', // To change according your custom loader
        'addPsr4' => 'addPsr4', // To change according your custom loader
        'getPrefixes' => 'getPrefixes', // To change according your custom loader
        'getPrefixesPsr4' => 'getPrefixesPsr4', // To change according your custom loader
        'include' => $this->args()->get('include'),
        'exclude' => array_merge($this->args()->get('exclude'), ['kahlan\\']),
        'persistent' => $this->args()->get('persistent'),
        'cachePath' => rtrim(sys_get_temp_dir(), DS) . DS . 'kahlan'
    ]);
});

// Apply the logic to the `'autoloader'` entry point.
Filter::apply($this, 'autoloader', 'my.custom.interceptor');

This way, this filter will replace the original code.

If I remember right, only 'loadClass' && 'findFile' are required to run specs but if your custom autoloader is not able to provide a full mapping over the Composer API, you will get into trouble with the code coverage reporter.

@m1ome
Copy link
Collaborator

m1ome commented Jan 29, 2015

@jails it seems like a bad idea to "hack" a code inside of a binary to provide a stubbing inside specs. Don't you think so? On my case - if i using a Phalcon framework i think i need rewrite a composer compatible autoloader to work with your stubs?

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Since the Phalcon framework autoloader is a fully PSR-0 compliant why not using your Phalcon autoloader for your application and simply using the Composer autoloader for running the specs ?
And to make the classes of your project "autoloadable" in Kahlan just configure your kahlan-config.php the following way:

/**
 * Adds some "extra" namespaces to the autoloader (i.e. for the ones whih are not in the composer.json)
 */
Filter::register('yourproject.namespaces', function($chain) {
    // Register your PSR-0 namespaces
    $this->_autoloader->add('YourNamespace\\', 'you/project/path');
    // And you will probably need a constant like the following to bail out the Phalcon autoloader initialisation when you are in test mode.
    define('KAHLAN_ENVIRONMENT', 'true');
});

Filter::apply($this, 'namespaces', 'yourproject.namespaces');

I used this strategy for a li3 project. Indeed since the lithium autoloader is also PSR-0 compatible both kind of autoloaders can do the job the same way.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
I am trying to do so, but it's not working

<?php
use filter\Filter;

Filter::register('app.namespaces', function($chain) {
  $this->_autoloader->add('Api\\Models\\', __DIR__ . '/app/models/');
});

Filter::apply($this, 'namespaces', 'app.namespaces');

In some of my tests i am creating

$a = new \Api\Models\Payments();

And i got following error:

PHP Fatal error:  Class 'Api\Models\Payments' not found in ...

But it has a file /app/models/Payments.php and kahlan-config.php lying on same level with app

@jails
Copy link
Contributor

jails commented Jan 30, 2015

It's probably related to some miss typing somewhere, you should try this:

Filter::register('app.namespaces', function($chain) {
    $this->_autoloader->add('Api\\Models\\', __DIR__ . '/app/models/');
    echo $this->_autoloader->findFile('Api\Models\Payments') . "\n";
    echo class_exists('Api\Models\Payments') . "\n";
    exit();
});

It should echo the path Composer is looking for and exit() the script.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

I tried this, and all show me a false result.
In my case i have such folders:

app/
app/models/Payments.php
spec/
kahlan-config.php

In Payment model i have:

namespace Api\Models;

class Payments {}

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
I also tried to dump some PSR-0 file inclusion in composer sources. And got this one:

/vagrant/crm/wtm.dev/app/models//Api/Models/Payments.php

It's adding a namespace Api/Models to path according to PSR-0 as i think.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

Fixed it, by adding not PSR-4 compatible namespaces.

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Cool, indeed the tree structure you provided didn't follow a PSR-0 convention, you had to either change it to a PSR-0 compatible structure or use the composer addPsr4() function.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
I change it to addPsr4, but when in my spec i trying to do something like that:

    Stub::on('Api\Models\Payments')->method('save')->andReturn(true);

    $a = new \Api\Models\Payments();
    var_dump($a->save());

It will dump false not true. Why?

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Here a minimal example https://github.com/jails/temp
Just clone & run composer install & ./bin/kahlan.
It's working fine for me. Do you have something in '/tmp/kahlan/vagrant/crm/wtm.dev/app/models//Api/Models/Payments.php' ? You should have the patched file which allows dynamic stubbing.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
Yeah, i just trying to do dynamic stubbing in Phalcon models, and as i see it's kinda complicated.
If i try to stub method that exists on \Phalcon\Mvc\Model - which is a C-extension, it wouldn't work.

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Ah indeed, but there's different way to play with Kahlan. For example you can also dynamically add a save() method at a stub level. That way it'll "override" the Phalcon save() method anyway. I updated the repo https://github.com/jails/temp (the diff https://github.com/jails/temp/commit/45e9ab72e3c4680f1dbcacfe6f9ff137714b553b)

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
I found it, maybe i don't get something. But in Payments model i add a method:

  public function test($a) {
    return false;
  }

And then i trying to stub it:

    Stub::on('Api\Models\Payment')->method('test', function($a) {
      return true;
    });
    $b = new \Api\Models\Payments();
    var_dump($b->test('lol'));

And i'll got a false.
When i remove parameter from function it will work normal.
By default create and save in Phalcon have params:

public function save($data=null, $whiteList=null){ }
public function create($data=null, $whiteList=null){ }

How must i stub them correctly?

@jails
Copy link
Contributor

jails commented Jan 30, 2015

you miss typed Api\Models\Payment should be Api\Models\Payments. Btw you shouldn't use the class name here since you have an access to the instance. So:

    $b = new \Api\Models\Payments();
    Stub::on($b)->method('test', function($a) {
        return true;
    });
    expect($b->test('lol'))->toBe(true);

would be more appropriate.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
When i got this in my spec

    Stub::on('Api\Models\Payments')->method('save', function($data=null, $whitelist=null) {
      return true;
    });
    $b = new \Api\Models\Payments();
    var_dump($b->save());

And in my model i have this override:

  public function save($data=null, $whitelist=null) {
    return parent::save($data, $whitelist);
  }

It works, when i comment this override it will not work. Do you have any ideas why?

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Because Kahlan is only able to stub PHP files. So if you comment the save() method from your php file Kahlan won't be able catch anything in the Phalcon C extension. So this diff https://github.com/jails/temp/commit/45e9ab72e3c4680f1dbcacfe6f9ff137714b553b shows how to create a stub which doesn't force you to add an additionnal save() method in your model. With this syntax a save() method will be added automagically at runtime so you won't need to change your codebase.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails

Ok, i get it. I just thinking how to solve this problem. Your way - normal, but if i got to test something like:

<?php
class PaymentController extends \Phalcon\Mvc\Controller{
   public function indexAction() {
      $payment = new Payment();
      if($payment->create()) {
          throw \Exception('Some exception');
      }
   }
}

Is there any way in my case to cover a bad creation? This line - throw \Exception('Some exception'). Or it's untestable?

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Indeed in this scenario it's not possible since $payment = new Payment(); is not injected. So you can't use the stub method injection trick i detailled above. And since redefining a class at run time is not possible in PHP, the only workaround is to have all your model to extends a Base class. e.g. Payment extends BaseModel extends Phalcon\Model. And by overriding the create()/save()/update()/delete() method in BaseModel. This way you should be able to test all your CRUD operations easily. But yeah for Phalcon I'm not sure Kahlan will be of a great help compared to classic test framework since it's a C extension.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
Maybe you say that i am mad, but in my helper i just copy models to spec/models and change extend for each file with str_replace. It is silly, but works!

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Maybe it can be adapted as a patcher. For example this one replace all quit statements by a Kahlan's plugin call https://github.com/crysalead/kahlan/blob/master/src/jit/patcher/Quit.php. And applying a filter on the 'patchers' entry point (like this https://github.com/crysalead/kahlan/blob/master/src/cli/Kahlan.php#L328-338) to add your custom & configured patcher should work. And maybe it can be integrated in standard as a Layer patcher which automatically apply a Layer between a class and its extends.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
One more thing is there is any way to inject some "globals" in all scopes on load?
I will see a way for patchers, it's kinda complicated now. But 👍

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
I am currently working with code-coverage and seeing something like that

    94:            $this->JSONResponse['profiler'] = [
    95:              'time'  => $time,
    96:              'query' => $profiles,
    97:0           ]; // This string is "red" as uncovered

How can i cover it? It's seems like a stupid question, but i dunno.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

@jails
I get it it's when array have last comma

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Q1: just use $this->myvar in the top most describe closure, it'll be available in any sub scopes.
Q2: just have this line executed inside an it, and then it'll be marked as green. And you will probalby need to change https://github.com/crysalead/kahlan/blob/master/src/cli/Kahlan.php#L405 to 'app' instead of 'src' if you are not following conventions (or using the '--src' option instead).

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

Q1: no, i am not asking about top describe, can i share some var in every *Spec.php scope?
Q2: i already fix it, changed to app already in kahlan-config using this repo kahlan-config-travis as an example. All is ok now.

@jails
Copy link
Contributor

jails commented Jan 30, 2015

Imo with just plain PHP. At the bottom of your kahlan-config file you should be able to add:

function di() {
  static $di = new \Phalcon\DI();
  return $di;
}

$di = di();
$di->set('mydatabase', ... );

Then use di() to get your DI instance in your specs.

@jails jails closed this as completed Jan 30, 2015
@jails
Copy link
Contributor

jails commented Jan 30, 2015

I'm closing this one, better to have one topic per issue only.

@m1ome
Copy link
Collaborator

m1ome commented Jan 30, 2015

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants