I've been reading on many sites even here that in order to improve performance of Zend Framework applications is not to use the Zend_Application in bootstrap, but haven't been able to find a site that has this demonstrated.
Do you guys know of a place that has this method described and might provide me with some code samples?
Thanks
I just threw this together:
https://gist.github.com/2822456
Reproduced below for completion. Not tested, just some ideas for how I think it generally (!) might work. Now that i have walked through it a bit, I have a greater appreciation for Zend_Application, its bootstrap classes, and its configurable/reusable application resources. ;-)
// Do your PHP settings like timezone, error reporting
// ..
// Define path to application directory
defined('APPLICATION_PATH')
|| define('APPLICATION_PATH', realpath(dirname(__FILE__) . '/_zf/application'));
// Define application environment
defined('APPLICATION_ENV')
|| define('APPLICATION_ENV', (getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production'));
// Ensure library/ is on include_path
set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
get_include_path(),
)));
// Get autoloading in place
require_once 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();
// Any additional configs to autoloader, like custom autoloaders
// Read config
$config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/application.ini', APPLICATION_ENV);
// bootstrap resources manually:
// * create db adapter
// * create resource autoloaders with the mappings you need
// * etc
// Get the singleton front controller
$front = Zend_Controller_Front::getInstance();
// Set controller directory
$front->setControllerDirectory(APPLICATION_PATH . '/controllers');
// Or set module directory
$front->setModuleDirectory(APPLICATION_PATH . '/modules');
// Other front config, like throw exceptions, etc.
// ...
//
// Create a router
$router = new Zend_Controller_Router_Rewrite();
// Add routes to the router
$router->addRoute('myRoute', new Zend_Controller_Router_Route(array(
// your routing params
)));
// More routes...
// Alternatively, the routes can all be in an xml or ini file and you can add
// them all at once.
// Tell front to use our configured router
$front->setRouter($router);
// Add an plugins to your $front
$front->registerPlugin(new My_Plugin());
// other plugins...
// Dispatch the request
$front->dispatch();
There might be some View/ViewRenderer stuff to do, as well. But as noted in other places, the ViewRenderer incurs a non-trivial performance hit. If performance is the issue, then you'll want to disable the ViewRenderer and make your action controllers call their own rendering using $this->view->render('my/view-script.phtml')
When you call $front->dispatch(), the $request and $response objects will be created automatically. If you want to do something specific to them at bootstrap - like setting the charset in Content-Type header of the response - then you can create your request/response object yourself, do what you want to it, and then attach it to the front with $front->setResponse($response); Same for the request object.
Although I see that my example uses Zend_Loader_Autoloader and Zend_config_Ini which Padraic notes incur performance hits. The next step would be to address those by using arrays for config, stripping require_once calls from the framework, registering a different autoloader, etc, an exercise left for the reader... ;-)
Hi I disagree somewhat with the not using Zend_Application in bootstrap and no I have yet to see a concrete example of this technique.
Personally I don't see the benefit in not using Zend_app for bootstrapping your application, assuming a) Your doing things 'the Zend way' and b) your project is either big enough or just simply warrants using the Zend framework (or any for that matter).
While Zend_App is great for creating consistent complex bootstraps
within a standardised structure, it doesn’t come without a significant
performance hit to baseline performance. A more direct bootstrap
(typical of ZF until Zend_App arrived) is far faster and can also be
done without configuration files.
Taken from Pádraic Brady link.
Well to me the above does not make sense, he basically just said Zend_App is great for complex bootstraps, but adds a performance hit. But isn't that the premise for ANY framework / framework component? I know Padraic is a very clever guy and I'm sure he has his reasoning, but I too would love to see examples / evidence of this suggestion.
Perhaps in answer to your question, you could benchmark a basic app using the latest Zend framework and then use Zend framework from < 1.10 using the old non Zend_App way, but I would say although clearly not perfect Zend_App is clearly faster to get most app's up and running, so whether this is worth 'the performance hit' is I guess up to the developer(s).
Here is a link which somewhat goes into what you may be after but makes reference to a modular approach (still interesting, none the less):
http://www.osebboy.com/blog/zend-framework-modules/
Related
I am writing my own php mvc framework (just for training). The question is how to handle exception when the requested controller doesn't exist? Should I call 404 class or create and show new View from Router? I'll be glad if you have any advices for me!
Here are my autoload.php:
function __autoload($class)
{
$filename = __DIR__ . '/' . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
if (file_exists($filename))
{
require $filename;
}
else
{
throw new \Exception('The file doesn\'t exists!');
}
}
and Route.php:
namespace App;
class Route
{
public static function start ()
{
$controller_name = 'News';
$controller_action = 'Index';
if (isset($_GET['furl']))
{
// Getting rid of spaces
$url = str_replace(' ', '', $_GET['furl']);
if (substr($url, -1) == '/')
{
$url = substr($url, 0, count($url) - 2);
}
$arr = explode('/', $url);
foreach($arr as &$value)
{
$value = strtolower($value);
$value = ucfirst($value);
}
$controller_action = $arr[count($arr) - 1];
unset($arr[count($arr) - 1]);
$controller_name = implode('\\', $arr);
}
$controller_name = '\App\Controllers\\' . $controller_name;
try
{
$controller = new $controller_name();
}
catch (\Exception $e)
{
//HELP ME PLS!
}
$controller->action($controller_action);
}
}
No matter how many web frameworks, routers, autoloaders, etc are there already: keep doing what you think it's right for you and suitable to your momentarily understanding level, in order to LEARN. Actually, by confronting yourself with problems arised along the process of implementing yourself different parts of your application, you will not only gain the opportunity to learn and discover new things, but also to learn how and what to study in the already existing frameworks' design.
Study the PHP Standard Recommendations (the ones marked as "accepted"). Especially PSR-1,2,4,7. They are used by many frameworks and PHP projects. Read FAQs to find out more about the project itself.
Autoloader:
The PSR-4 provides a link with examples at the document end.
#mike suggested, that you should use the Composer autoloader. I agree with him and I strongly recommend it to you too. BUT I suggest you to do this only after you correctly implement and make use of your own autoloader (PSR-4 conform). Why? You definitely need to learn how the autoloading process works. And in some future situations you will still need your own autoloader implementation, even after Composer is installed and running.
Also be aware that you must not raise any exceptions from autoloader itself!
Router:
Btw, your class should be called "Router".
The router should not be responsible for validating the controller class/file and the action, nor for calling the action. These tasks are part of the "front controller" responsibilities. Your router should just return the components resulted after parsing, e.g. "exploding" the request URI ($_GET['furl']), in some form (as a Route object (with them as properties), as array, etc). These components are the controller name, the action name, the action parameters list (NB: the action parameters are not the query string parameters). The front controller uses them further to validate/access the controller class/file and its action and to call the action.
But please note that a router works actually in other way. In short: it matches (e.g. compares) the request method (GET, POST, etc) and the request URI against an existing (e.g. predefined by you) list of route definitions. A route definition contains the infos related to a specific controller, action, etc. If the HTTP method and the request URI "correspond" to one of the route definitions, then the router returns the matched definition's components to the front controller (in some form: as object, as array, etc).
For more details describing this principle see:
How to load classes based on pretty URLs in MVC-like page?
FastRoute
Aura.Router
Front controller:
It can be a class, but it can also be just vanilla code in the entry point of your app (index.php, bootstrap.php, etc). In the latter case, the front controller code should reside in a file outside of the document root of the app. For example in a bootstrap.php file, which is to be just included in index.php - whereas index.php resides inside the document root.
"controller/action not found" specific handling:
If a controller, or an action is not found/valid, then call a predefined action (for example displayError) of a predefined Error controller, which informs the user that, for a specific part of his request (actually of his provided request URI), no resource was found.
For example, consider that the user provided the request URI www.example.com/books/show/12. Conform to your app workflow the controller is Book, the action (e.g. the controller's method) is show and the action parameter is 12 (the value is passed as argument to the show method and defined as $bookId parameter in it). But, if the controller class is not defined, or no controller file exists, then the front controller should call the action displayError of Error controller, which should display a message like No resource found for your 'book' request. A similar info should be displayed when the show method is not yet defined in the Book controller.
Note that, if the Error controller or its action is not found/valid, then the PHP engine raises a corresponding error/exception. If you follow the next links I provided, you'll end up implementing three custom error/exception handling functions (referenced by set_error_handler, set_exception_handler and register_shutdown_function, respectively). They will catch and handle the described situation properly.
To read: Manage the errors of a framework
General error/exception handling in MVC:
Here are some good resources:
Again: Manage the errors of a framework
Error logging, in a smooth way
Error reporting basics
The (im)proper use of try..catch
Other MVC related resources:
Build a PHP MVC Application (Just for the start...)
Dependency Injection and Dependency Inversion in PHP
MVC for advanced PHP developers (A further list of resources)
Tom Butler's Programming Blog. MVC, PHP, Best practices
Clean, high quality code
P.S: Avoid the use of statics, globals, singletons. Why? Read here and here, for example.
Good luck.
I have asked this question yesterday as well, but this one includes code.
Issue
My application have multiple modules and 2 types of user accounts, Some modules are loaded always which are present in application.config.php some of them are conditional i.e. some are loaded for user type A and some for user type B
After going through documentations and questions on Stack Overflow, I understand some of ModuleManager functionalities and started implementing the logic that I though might work.
Some how I figured out a way to load the modules that are not present in application.config.php [SUCCESS] but their configuration is not working [THE ISSUE] i.e. if in onBootstrap method I get the ModuleManager service and do getLoadedModules() I get the list of all the modules correctly loaded. Afterwards if I try to get some service from that dynamically loaded module, it throws exception.
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for jobs_mapper
Please note that, the factories and all other stuff are perfectly fine because if I load the module from application.config.php it works fine
Similarly when I try to access any route from the dynamically loaded module it throws 404 Not Found which made it clear that the configuration from module.config.php of these modules are not loading even though the module is loaded by ModuleManager.
Code
In Module.php of my Application module I implemented InitProviderInterface and added a method init(ModuleManager $moduleManager) where I catch the moduleManager loadModules.post event trigger and load modules
public function init(\Zend\ModuleManager\ModuleManagerInterface $moduleManager)
{
$eventManager = $moduleManager->getEventManager();
$eventManager->attach(\Zend\ModuleManager\ModuleEvent::EVENT_LOAD_MODULES_POST, [$this, 'onLoadModulesPost']);
}
Then in the same class I delcare the method onLoadModulesPost and start loading my dynamic modules
public function onLoadModulesPost(\Zend\ModuleManager\ModuleEvent $event)
{
/* #var $serviceManager \Zend\ServiceManager\ServiceManager */
$serviceManager = $event->getParam('ServiceManager');
$configListener = $event->getConfigListener();
$authentication = $serviceManager->get('zfcuser_auth_service');
if ($authentication->getIdentity())
{
$moduleManager = $event->getTarget();
...
...
$loadedModules = $moduleManager->getModules();
$configListener = $event->getConfigListener();
$configuration = $configListener->getMergedConfig(false);
$modules = $modulesMapper->findAll(['is_agency' => 1, 'is_active' => 1]);
foreach ($modules as $module)
{
if (!array_key_exists($module['module_name'], $loadedModules))
{
$loadedModule = $moduleManager->loadModule($module['module_name']);
//Add modules to the modules array from ModuleManager.php
$loadedModules[] = $module['module_name'];
//Get the loaded module
$module = $moduleManager->getModule($module['module_name']);
//If module is loaded succesfully, merge the configs
if (($loadedModule instanceof ConfigProviderInterface) || (is_callable([$loadedModule, 'getConfig'])))
{
$moduleConfig = $module->getConfig();
$configuration = ArrayUtils::merge($configuration, $moduleConfig);
}
}
}
$moduleManager->setModules($loadedModules);
$configListener->setMergedConfig($configuration);
$event->setConfigListener($configListener);
}
}
Questions
Is it possible to achieve what I am trying ?
If so, what is the best way ?
What am I missing in my code ?
I think there is some fundamental mistake in what you are trying to do here: you are trying to load modules based on merged configuration, and therefore creating a cyclic dependency between modules and merged configuration.
I would advise against this.
Instead, if you have logic that defines which part of an application is to be loaded, put it in config/application.config.php, which is responsible for retrieving the list of modules.
At this stage though, it is too early to depend on any service, as service definition depends on the merged configuration too.
Another thing to clarify is that you are trying to take these decisions depending on whether the authenticated user (request information, rather than environment information) matches a certain criteria, and then modifying the entire application based on that.
Don't do that: instead, move the decision into the component that is to be enabled/disabled conditionally, by putting a guard in front of it.
What you're asking can be done, but that doesn't mean you should.
Suggesting an appropriate solution without knowing the complexity of the application you're building is difficult.
Using guards will certainly help decouple your code, however using it alone doesn't address scalability and maintainability, if that's a concern?
I'd suggest using stateless token-based authentication. Instead of maintaining the validation logic in every application, write the validation logic at one common place so that every request can make use of that logic irrespective of application. Choosing a reverse proxy server (Nginx) to maintain the validation logic (with the help of Lua) gives you the flexibility to develop your application in any language.
More to the point, validating the credentials at the load balancer level essentially eliminates the need for the session state, you can have many separate servers, running on multiple platforms and domains, reusing the same token for authenticating the user.
Identifying the user, account type and loading different modules then becomes a trivial task. By simply passing the token information via an environment variable, it can be read within your config/application.config.php file, without needing to access the database, cache or other services beforehand.
I am new to this zend2, previously I worked with CAKEPHP & codeigniter. I want to write some constant values in a particular file & be able to access them any where in the project.
In cakephp it is like Configure::write('environment','dev'); we write this in a file in Config folder which will be at app/Config/file name
and we can access this like $env = Configure::read('environment'); any where..
Can we do in the same way in zend framework 2, like defining the constants in a file & can access them anywhere..?
Please give an example how to define & read it with the path of the file
No is the short answer. Cake, ZF1, CodeIgniter all made use of a design pattern, now widely discouraged, called the the Registry Pattern (which is really just a Singleton).
The very fact that this class is globally accessible, is one of the reasons why its use is not advised.
ZF2 has a completely different architecture and offers a flexible approach by merging configuration based on environment variables. An when it comes to 'using' the configuration, you should be injecting it into your services using the service manager and a service factory.
First define variable in config
Like:-
accountUser.admin = "admin"
Then initialize object like(in controller or model):-
$config = new Zend_Config_Ini(APPLICATION_PATH . '/configs/config.ini', APPLICATION_ENV);
// APPLICATION_PATH is path to project
// APPLICATION_ENV will be your environment
Then use it like:-
$admin = $config->accountUser->admin;
I'd like to be able to manage WebDAV directories (and even reimplement the way files are read and written) in Symfony. To do so I found SabreDAV, which is itself a framework with all the basic classes required.
My problem is, while it's quite easy to get a WebDAV server running using SabreDAV alone, it doesn't work that well when I use Symfony.
Without Symfony, it boils down to:
$server = new DAV\Server($rootDirectory);
$server->exec();
And I can use cadaver to access my directory.
More here: http://code.google.com/p/sabredav/wiki/GettingStarted
I tried to do the same in my controller with Symfony, using:
return new Response($server->exec());
but for some reason cadaver doesn't have access to the folder.
I guess I'm missing something about the way responses work in Symfony, but what? SabreDAV uses its own system of http requests and responses, but if (as I presume) Symfony doesn't mess with superglobal variables such as $_SERVER, this shouldn't be an issue.
About requests and responses in Symfony: http://symfony.com/doc/current/book/http_fundamentals.html#requests-and-responses-in-symfony
Here's what I did; it's a bit slow and there must be a better way, but I'll make do with that for the moment:
Controller.php :
$path=(__DIR__.'/../../../../web/public/');
$path=realpath($path);
$publicDir= new \MyClasses\FS\MyDirectory($path);
$server = new \Sabre\DAV\Server($publicDir);
$server->setBaseUri('/Symfony/web/app_dev.php/');
{
$SyRequest = Request::createFromGlobals();
$_server=$SyRequest->server->all();
$_post=$SyRequest->request->all();
}
{
$SaRequest=new \MyClasses\HTTP\Request($_server,$_post);
$resourceStream=false;
$SaRequest->setBody($SyRequest->getContent($resourceStream),$resourceStream);
}
{
$server->httpRequest=$SaRequest;
$SaResponse=new \MyClasses\HTTP\Response();
$server->httpResponse=$SaResponse;
$server->exec();
}
{
$content=ob_get_clean();
}
{
$SyResponse=new Response($content,http_response_code(),headers_list());
}
return $SyResponse;
$server->exec();
Doesn't really return anything. It attempts to set headers itself, and stream the output to php://output (indeed, with the built-in request/response system).
If you want to embed SabreDAV into symfony, the most proper way to solve this is to subclass both Sabre\HTTP\Request and Sabre\HTTP\Response, and set these in the server (setting the ->httpRequest and ->httpResponse properties) before calling ->exec.
Your overridden request/response objects should then map to symfony's equivalents.
I don't know enough about symfony to tell you if they map cleanly and easily though, and I imagine it will in practice be simpler to try to work around symfony's system (although from an architectural standpoint, it will not be the most proper).
We have used Zend_Log, which is configured in application.ini differently for different circumstances. We initialize it/get it in the bootstrap and store it in the registry:
$r = $this->getPluginResource('log');
$logger = $r->getLog();
But we've subclassed Zend_Log (say, Our_Log) to add customized features, and want to get it the same way. So then we have to make a new Resource Plugin. That seems quite easy - just copy Application/Resource/Log.php, rename the file to Ourlog.php, rename the class to class Zend_Application_Resource_Ourlog. For now, let's not worry about "Our_Log", the class -- just use the new Resource Plugin to get a Zend_Log, to reduce the variables.
So then, our new code in the bootstrap is:
$r = $this->getPluginResource('ourlog');
$logger = $r->getLog();
but of course this doesn't work, error applying method to non-object "r". According to the documentation,
"As long as you register the prefix path for this resource plugin, you
can then use it in your application."
but how do you register a prefix path? That would have been helpful. But that shouldn't matter, I used the same prefix path as the default, and I know the file is being read because I "require" it.
Anyway, any guidance on what simple step I'm missing would be greatly appreciated.
Thanks for the pointers -- so close, so close (I think). I thought I was getting it...
Okay, so I renamed the class Xyz_Resource_Xyzlog, I put it in library/Xyz/Resource/Xyzlog.php
Then, because I don't love ini files, in the bootstrap I put:
$loader=$this->getPluginLoader();
$loader->addPrefixPath('Xyz_Resource','Xyz/Resource/');
$r = $this->getPluginResource('xyzlog');
if (!is_object($r)) die ('Not an object!!');
Sudden Death. So, okay, do the ini:
pluginPaths.Xyz_Resource='Xyz/Resource/'
The same. No help. I believed that the basepaths of the plugin paths would include the PHP "include" paths. Am I mistaken in that? Any other ideas? I'll happily write up what finally works for me to help some other poor soul in this circumstance. Something to do with Name Spaces, maybe?
Plugin classes are resolved using the plugin loader, which works slightly differently to the autoloader; so just requiring the class in doesn't help you here. Instead, add this to your application.ini:
pluginPaths.Application_Resource = "Application/Resource"
you should then be able to use the class as normal. Since your path above will be checked before the default Zend one, you can also name your class 'Log' and still extend the Logger resource to override the standard functionality.