I have a bootstrap class which I want to use to set CSS variables:
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initView()
{
$this->bootstrap('view');
...
...
}
}
But trying to get the view resource fails at the bootstrap('view') stage. I get the error:
... Circular resource dependency detected' in C:\ZendFramework\library\Zend\Application\Bootstrap\BootstrapAbstract.php on line 662
...
Which is strange because this is the procedure that tutorials(and the zend documentation) use. What could be wrong?
Change the method to something like _initViewStuff() and all will be fine.
The reason is that the bootstrap sequence in Zend_Application_Bootstrap_BootstrapAbstract is as follows:
Your initial call to $app->bootstrap() in public/index.php runs through all _initXxx() methods (#see Zend_Application_Bootstrap_BootstrapAbstract::getClassResourceNames()) and calls $this->bootstrap('xxx') for each Xxx it finds. It will then do a similar thing for all the plugin resources defined by resources.* keys in application.ini (though yours never gets that far, as described below).
The call to bootstrap('view') internally stores a flag that he has started the process to bootstrap a resource called view.
He does a similar thing as (1), looking for a matching _initXxx() method. He find it and attempts to execute $this->_initView()
He notices the flag he set, indicating that he's gonna hit an infinite loop, so he bails out with circular dependency exception.
Typically, for each resource xxx, you bootstrap it using one (but not both, as you have discovered) of the following approaches:
Define an _initXxx() method.
Creating a plugin resource class named something like My_Application_Resource_Xxx (you inform that system that My_Application_Resource_ is a namespace prefix for plugin resources using pluginPaths.My_Application_Resource = /path/to/dir/containing/plugin in application/configs/application.ini)
You cant use this method name in your bootstrap class '_initView' because there is corresponding Zend_Application_Resource_View, just rename your bootstrap method name
Related
I created a simple test for my new Laravel 7 application. But when I run php artisan test I get the following error.
Target [Illuminate\Contracts\View\Factory] is not instantiable.
The error doesn't appear when I go to the page in the browser.
$controller = new HomeController();
$request = Request::create('/', 'GET');
$response = $controller->home($request);
$this->assertEquals(200, $response->getStatusCode());
Although "Just write feature tests" may seem like a cop-out ("They're not unit tests!"), it is sound advice if you do not want to get bogged down by framework-specific knowledge.
You see, this is one of those problems that come from using facades, globals, or static methods. All sorts of things happen outside of your code (and thus your test code) in order for things to work.
The problem
To understand what is going on, you first need to know how Laravel utilizes Containers and Factories in order to glue things together.
Next, what happens is:
Your code (in HomeController::home() calls view() somewhere.
view() calls app() to get the factory that creates Views1
app() calls Container::make
Container::make calls Container::resolve1
Container::resolve decides the Factory needs to be built and calls Container::build to do so
Finally Container::build (using PHP's ReflectionClass figures out that \Illuminate\Contracts\View\Factory can not be Instantiated (as it is an interface) and triggers the error you see.
Or, if you're more of a visual thinker:
The reason that the error is triggered is that the framework expects the container to be configured so that a concrete class is known for abstracts (such as interfaces).
The solution
So now we know what is going on, and we want to create a unit-test, what can we do?
One solution might seem to not use view. Just inject the View class yourself! But if you try to do this, you'll quickly find yourself going down a path that will lead to basically recreating loads of framework code in userland. So not such a good idea.
A better solution would be to mock view() (Now it is really a unit!). But that will still require recreating framework code, only, within the test code. Still not that good.[3]
The easiest thing is to simply configure the Container and tell it which class to use. At this point, you could even mock the View class!
Now, purists might complain that this is not "unit" enough, as your tests will still be calling "real" code outside of the code-under-test, but I disagree...
You are using a framework, so use the framework! If your code uses glue provided by the framework, it makes sense for the test to mirror this behavior. As long as you don't call non-glue code, you'll be fine![4]
So, finally, to give you an idea of how this can be done, an example!
The example
Lets say you have a controller that looks a bit like this:
namespace App\Http\Controllers;
class HomeController extends \Illuminate\Routing\Controller
{
public function home()
{
/* ... */
return view('my.view');
}
}
Then your test[5] might look thus:
namespace Tests\Unit\app\Http\Controllers;
use App\Http\Controllers\HomeController;
use Illuminate\Contracts\View\Factory;
class HomeControllerTest extends \PHPUnit\Framework\TestCase
{
public function testHome()
{
/*/ Arange /*/
$mockFactory = $this->createMock(Factory::class);
app()->instance(Factory::class, $mockFactory);
/*/ Assert /*/
$mockFactory->expects(self::once())
->method('make')
->with('my.view')
;
/*/ Act /*/
(new HomeController())->home();
}
}
A more complex example would be to also create a mock View and have that be returned by the mock factory, but I'll leave that as an exercise to the reader.
Footnotes
app() is asked for the interface Illuminate\Contracts\View\Factory, it is not passed a concrete class name
The reason Container::make does nothing other than call another function is that the method name make is defined by PSR-11 and the Laravel container is PSR compliant.
Also, the Feature test logic provided by Laravel already does all of this for you...
Just don't forget to annotate the test with #uses for the glue that is needed, to avoid warnings when PHPUnit is set to strict mode regarding "risky" tests.
Using a variation of the "Arrange, Act, Assert" pattern
This is not how you test endpoints in Laravel. You should let Laravel instantiate the application as it is already setup in the project, the examples you can see here.
What you already wrote can be rewritten to something like this.
$response = $this->call('GET', route('home')); // insert the correct route
$response->assertOk(); // should be 200
For the test to work, you should extend the TestCase.php, that is located in your test folder.
If you're finding this in The Future and you see #Pothcera's wall of text, here's what you need to know:
The ONLY reason he's doing any of that and the ONLY reason you're seeing this in the first place in a Unit test is because he and you haven't changed from PHPUnit\Framework\TestCase to Tests\TestCase in the test file. This exception doesn't exist when you extend the test case that includes app().
My advice would be to simply extend the correct base test case and move on with your life.
I've created new project using PhalconPHP 3.1.2. Everything works fine, but I have problem with IDE. In PhpStorm I've added ide/stubs/Phalcon from phalcon-devtools 3.1.2 as external libraries to dispose of warnings and errors.
But there is still one problem: in app/config/router.php (standard catalog structure created by devtools) I got line $router = $di->getRouter(); (also created by devtools) with warning: Method getRouter not found in Phalcon\Di\FactoryDefault.
There is no method in this class indeed: https://github.com/phalcon/phalcon-devtools/blob/3.1.x/ide/stubs/Phalcon/di/FactoryDefault.php -> https://github.com/phalcon/phalcon-devtools/blob/3.1.x/ide/stubs/Phalcon/Di.php
Now I don't have autocomplete of router's methods and this is problem for me. Did I do something wrong?
First of all -- I'm not familiar with Phalcon. These are my comments based on general PHP/PhpStorm experience.
So .. if you look at that PHPDoc in the last link you gave (stubs for Di.php) you will notice the code example there that has $request = $di->getRequest();.
If you just copy-paste that whole sample you will get the same "method not found" error ... as getRquest() is transformed at run time via magic __get() method.
The possible solution here is to create your own stub for your $di:
Create new class that will extend original Di, e.g.
class MyDi extends \Phalcon\Di
Add all those getRoute() / getRequest() etc methods there (could be real methods with empty bodies as in Phalcon's stub files .. or via #method PHPDoc tag)
Place such file anywhere in the project -- it will be used by IDE only.
When you are using your $di -- typehint it with your class, e.g.
/** #var \MyDi $di */
$di = new Di();
In the above code during runtime $di will be an instance of Di but for PhpStorm during development it will be MyDi so all type hints are in place.
As possible alternative -- instead of using magic via $di->getRoute() .. try $di->getShared('route') or similar.
If you use PhpStorm's Advanced Metadata functionality it will allow to get correct type based on parameter value.
$di->getRouter() is magic method, Phalcon\Di implements __call method and it gets router service this way if you use getRouter().
If you want you can use PHPStorm metadata and use $di->get() https://www.google.pl/search?client=opera&q=phpstorm+metadata&sourceid=opera&ie=UTF-8&oe=UTF-8
I've previously created an action helper Website_Layout_ActionHelper which extends Zend_Controller_Action_Helper_Abstract and has a getName method which returns "layoutSelector".
This helper has been working fine for years along with several other helpers that have been put together, although none of them call on any of the others.
I'm now trying to create a new action helper which the layout helper needs to call on.
The new action helper is Website_UserInfo_ActionHelper - it is set up in practically the same way as the other helpers (file paths, etc), and has a getName method returning userInfo.
However, when I try to access the new helper from my layout helper, I get the following exceptions:
Fatal error: Uncaught exception 'Zend_Loader_PluginLoader_Exception' with message 'Plugin by name 'UserInfo' in /usr/share/php/Zend/Controller/Plugin/Broker.php on line 336
Zend_Loader_PluginLoader_Exception: Plugin by name 'UserInfo' was not found in the registry; used paths: Zend_Controller_Action_Helper_: Zend/Controller/Action/Helper/ in /usr/share/php/Zend/Loader/PluginLoader.php on line 424
Zend_Controller_Action_Exception: Action Helper by name UserInfo not found in /usr/share/php/Zend/Controller/Action/HelperBroker.php on line 369
However, when I dump the array from Zend_Controller_Action_HelperBroker::getExistingHelpers() instead of trying to call Zend_Controller_Action_HelperBroker::getHelper('userInfo') the userInfo helper is there...
The fact that the error messages are capitalising the UserInfo name is probably a clue, but I don't understand why I'm getting this behaviour when all the other helpers are happily working. Is it because I'm trying to access the helper from inside another helper? Because in my reading, this should be possible.
So, the question: why do I get exceptions when trying get a helper from the broker when the helper is actually loaded?
I am trying to register my modules with the front controller using the bootstrap class by using the code below:
$this->bootstrap('FrontController');
$front = $this->getResource('FrontController');
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->setParam('prefixDefaultModule', true);
This works fine and all the modules are registered. But when I do the following my module directory is not registered and I get the "controller not found error".
$front = Zend_Controller_Front::getInstance();
$front->addModuleDirectory(APPLICATION_PATH."/modules");
$front->setParam('prefixDefaultModule', true);
As the front controller implements the singleton design pattern shouldn't both the code blocks refer to the same instance and both of my code blocks should work?
The issue is this line in your application.ini:
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
The file structure you gave doesn't include this directory, so I think you can remove it and it will solve your problem.
Longer explanation of why it works one way and not the other:
There are two ways of configuring resources with Zend Application: class resources and plugin resources. The resources.xxx lines in application.ini are plugin resources, and the _init methods are class resources. Class resources run before plugin resources, but if you run $this->bootstrap('...') it will ensure that resource has been initialized (regardless of type).
So in your first code example, $this->bootstrap('FrontController'); triggers the frontController resource which you defined in your application.ini. This then sets the controller directory using the path you gave it. You then add a module directory which seems to be what your application actually uses.
In your second code example, you get an instance of the front controller and then add your module directory to it (all good). But then the plugin resource will run (as remember these run after). This will grab your existing front controller instance, but setting the controller directory overrides the module directory, so this is what causes your error later on.
I generally try and avoid mixing and matching plugin and class resources as it can cause some weird problems. So either put it all in application.ini or all in the Bootstrap class. Personally I find the latter more readable.
If you do need the controller directory as well, adding $this->bootstrap('FrontController'); to the start of your second code example should work as well, since that will trigger the plugin resource.
Here is what is going on. Tim Fountain is correct. The following line in your application.ini file is the culprit. If you remove it, your application should load correctly.
resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers"
You may also have to remove this line since it is part of the frontcontroller too.
resources.frontController.params.displayExceptions = 1
But you also have 2 other options to be able to use Zend_Controller_Front::getInstance()
Option 1. You change your index.php to bootstrap specific resources:
$application->bootstrap(array('FrontController', 'ModuleConfig'))->run();
This will bootstrap your FrontController first from your application.ini and then run your initModuleConfig method. Essentially this allows you to control what resources are loaded and in what order. This is helpful when you have resources that you only want to bootstrap at specific times.
I think if you do not provide an array to the bootstrap method here, then it will call all methods prefixed with init in the order they are declared.
Option 2. You could do the configuration of your of your module directory within your application.ini
resources.frontController.moduleDirectory = APPLICATION_PATH "/modules"
resources.frontController.params.prefixDefaultModule = 1
resources.frontController.params.displayExceptions = 1
This will do what you were trying to do in code. One thing to note is that you may want to bootstrap your FrontController in the constructor of your Bootstrap class. This is just in case you need to use it during your custom initialization.
Now here is an explanation for why option 1 works.
This is a method from Zend_Application_Bootstrap_BootstrapAbstract
protected function _bootstrap($resource = null)
{
if (null === $resource) {
foreach ($this->getClassResourceNames() as $resource) {
$this->_executeResource($resource);
}
foreach ($this->getPluginResourceNames() as $resource) {
$this->_executeResource($resource);
}
} elseif (is_string($resource)) {
$this->_executeResource($resource);
} elseif (is_array($resource)) {
foreach ($resource as $r) {
$this->_executeResource($r);
}
} else {
throw new Zend_Application_Bootstrap_Exception('Invalid argument passed to ' . __METHOD__);
}
}
This _bootstrap is called when you call the public bootstrap method. Example $this->bootstrap("FrontController") or $this->bootstrap();
Notice the case when you don't pass in a parameter. This will call the null case which is the case you get in your index.php - $application->bootstrap()->run();
First it will load your class resources and then it will also call your plugin resources. Notice that the the plugin resources are not loaded in the other cases.
If you follow the method calls for class resources, it is basically call your init methods in your bootstrap call class.
The plugin resources are called afterward and one of the plugin resources. I'm not entirely sure how all the plugin resources are loaded, but I believe one source is in your application.ini file. These will be the lines that start with resources. Examples includes views, frontcontroller, db.
So in your situation where you call $application->bootstrap()->run();, your init methods are loaded first. But your FrontController is not bootstrapped yet. It eventually gets bootstrapped as a plugin resource which is taken from your application.ini. This apparently overwrites what you did in your bootstrap class.
Another question you may be asking is why is the FrontController instance not overridden when you call $this->bootstrap("FrontController) explicitly. I guess this pretty obvious but personally I had this question myself.
In the Bootstrap class, there is a method called _executeResource and this will check if the resource has already been bootstrapped. It uses an associative array to keep track. The associative array is called $this->_started.
This is why the plugin resource for the front controller is not called in your first case where you explicitly bootstrap the front controller. Hence your front controller instance is not replaced.
You need to bootstrap the front controller first so it is initialized. Once done in the cycle you could always retrieve it with the static getInstance.
OK, this is a tough one... I think, and I have a feeling the answer is simply no, but in that case I would like some answers for alternatives.
I have a very complex __autoload() function in a framework which can dynamically create classes. There a three classes required in order for the dynamic creation of a class called AuthActions -- these three are: fRecordSet, RecordSet, and AuthAction (note there's no S on that one).
My autoloader will look for a static method "init" on any class it loads and try to run that. On my ActiveRecord class it attempts to use AuthActions in order to get a list of supported actions on a particular active record. AuthAction (no S) also calls AuthActions within it's init, so basically the Active Record is loaded, and tries to load AuthActions, triggering the loading of the other three, and then when it finishes loading AuthAction still within the original autoloader AuthAction tries to call AuthActions which triggers another autoload since the original one did not complete yet.
This causes the following which has some echo statements to clarify:
Attempting to load ActiveRecord
Attempting to load fActiveRecord
fActiveRecord loaded via /var/www/dotink.org/inkwelldemo/inc/lib/flourish
ActiveRecord loaded via /var/www/dotink.org/inkwelldemo/inc/lib
Attempting to load AuthActions
Attempting to load RecordSet
Attempting to load fRecordSet
fRecordSet loaded via /var/www/dotink.org/inkwelldemo/inc/lib/flourish
RecordSet loaded via /var/www/dotink.org/inkwelldemo/inc/lib
Attempting to load AuthAction
AuthAction loaded via /var/www/dotink.org/inkwelldemo/models
Fatal error: Class 'AuthActions' not found in /var/www/dotink.org/inkwelldemo/models/AuthAction.php on line 24
The problem here is that the subsequent call to __autoload('AuthActions') would succeed because the three classes it requires are now in place... but it seems to die on the premise alone that it's already trying to autoload 'AuthActions' -- this seems to be hardwrit into PHP.
In testing this I found the following will loop forever with no error:
function __autoload($class) {
__autoload($class);
}
$foo = new Bar();
While this one below will error similarly:
function __autoload($class) {
$test = new Bar();
}
$foo = new Bar();
This behavior seems inconsistent as essentially they should amount to the same thing (kinda). If PHP internally triggered autoloads acted like a user call to __autoload() I don't think I would have a problem (or if I did the then it would be a problem of it looping forever which would be a separate issue of determining why the class wasn't getting loaded to resolve the dependencies).
In short, I need a way to either recursively loop the autoloader like this based on PHP triggered autoloads... is this simply not possible? Is it a bug in my particular version possibly? It seems to be affecting 5.2.6 - 5.3.2 in my tests, so I can't imagine it's a bug overall.
Update:
The code for the init method() on AuthAction is below:
/**
* Initializes the AuthAction model
*
* #param array $config The configuration array
* #return void
*/
static public function init($config) {
// Establish permission definitions and supported actions
$every_permission = 0;
$supported_actions = array();
foreach (AuthActions::build() as $auth_action) {
$action_name = $auth_action->getName();
$action_value = intval($auth_action->getBitValue());
$every_permission = $every_permission | $action_value;
$supported_actions[] = $action_name;
define(self::makeDefinition($action_name), $action_value);
}
define('PERM_ALL', $every_permission);
}
You can see where it is calling AuthActions as a separate class, and mind you it is only because it is being loaded within the original attempt to load that it fails. I can obviously remove this code and it will work -- the original load of AuthActions will complete successfully as all the pre-requisite classes are then loaded.
That said, init() is the most appropriate place for this code, and even if I am unable to use inter-dependent classes which haven't been loaded yet within init() I would still prefer to keep that functionality. Any alternative way of implementing that functionality would be great... Ideally PHP would have events for such things where you could for example register a callback when say an "autoloaded" event is triggered. This would allow the code to run AFTER the original autoload and would resolve this seemingly meaningless restriction.
Now with that said, any suggestions how to automatically call init() on a class every time it is loaded are appreciated and welcome.
__autoload method only gets called when you are are trying to use a class/interface which hasn't been defined yet.
Therefore with your example
function __autoload($class) {
$test = new Bar();
}
$foo = new Bar();
You are attempting to load the class bar which has not been required/requiered_once therefore the class is not defined so it calls the __autoload method as a last resort then within the autoload you are again trying to load the same class which has not been defined.
The correct way to use the __autoload would be as shown in the php site.
<?php
function __autoload($class_name) {
require_once $class_name . '.php';
}
$obj = new MyClass1();
$obj2 = new MyClass2();
?>
And all your init stuff can be put in __constructor can't it?
unless i'm missing something from your question...
I think this
My autoloader will look for a static
method "init" on any class it loads
and try to run that.
is the key to your problem. Your autoloader shouldn't be running anything, it should be including (or otherwise defining) the class and nothing else.
What sort of code are you trying to run when a class is defined (ie. in your static "init" method)?