Directory structure in MVC: Do strategy objects go in models folder? - php

Which folder should I put strategy objects, or any objects that are not domain models? I'm using Zend Framework, if that matters. Appreciate it!

I would use the recommended folder structure laid out here in the Zend Programmer's Reference Guide and place them into the modules folder.

Two pretty standard options:
Place this code into the library folder. Typically a file library/App/SomePackage/SomeClass.php wouldcontain the class App_SomePackage_SomeClass. Just add the line autoloadernamespaces[] = "App_" into your configs/application.ini file.
Create a new folder inside your application folder and configure a Zend_Loader_Autoloader_Resource (or its extended class Zend_Application_Module_Autoloader) with appropriate appnamespaces, paths, and prefixes.
Using this second approach could go something like this:
protected function _initResourceLoader()
{
$resourceLoader = Zend_Application_Module_Autoloader(array(
'namespace' => 'Application_',
'basePath' => APPLICATION_PATH,
));
$resourceLoader->addResourceType('strategy', 'strategies', 'Strategy');
}
Then a class named Application_Strategy_SomeClass would reside in the file application/strategies/SomeClass.php.
Noe that using Zend_Application_Module_Autoloader - instead of the more generic parent Zend_Loader_Autoloader_Resource - will give you a standard set of autoloader mappings for models, forms, services, view helpers, etc.

Related

Effective ways to implement helper functions?

I am trying to implement a helper function which helps me sort of 'add' routes (which map to controllers/actions) in an MVC application.
My index.php file looks like this.
// use block
use App\Router;
use App\Config;
use App\Core\Database;
use App\Bootstrap;
// dependencies
$router = new Router;
$config = new Config;
$database = new Database($config);
$bootstrap = new Bootstrap($router, $database);
// routes
require '../App/Routes.php';
$bootstrap->router->dispatch($_SERVER['QUERY_STRING']);
And my 'routes' file has one route in it, my routes look like this.
$bootstrap->router->add('/home', ['controller' => 'home', 'action' => 'index']);
I would prefer to simply add roots into my file like this...
route('/home', ['controller' => 'home', 'action' => 'index']);
So I need to make a helper function
question
Thanks to psr-4 autoloading I know how to pull in classes whenever I need them, however this is less clear when it comes to plane old functions. How can I cleanly add my helper functions into my MVC Framework without adding require files everywhere?
Many thanks in advance for your time and consideration!
To have helper functions available globally, you have to include them globally. One way to do this is in your application entry point (usually index.php), you require the file that has your helper functions.
Laravel, for example, does this and has a wide variety of helpers: https://laravel.com/docs/5.1/helpers
Whenever I wrote my own framework for learning purposes, I found that I liked to have my helpers separated into areas of functionality, so what I tended to do is have a folder for helpers, something like /app/helpers and use a bit of code like this in my entry point:
foreach (glob("./app/helpers/*.php") as $helper) {
require_once($helper);
}
Be aware that this pollutes the global namespace, so be very careful and thoughtful about your naming conventions.
An alternative is to create a Helper class and have methods available statically in them so you could call things like Helper::route() for example and it keeps the functions out of the global namespace.
As covered here Autoloader for functions, you have a couple of options, as there are no autoloaders for function.
If you are using composer to manage your dependencies, you can put all of your functions in one file and add a "files" directive to the "autoload" section of your composer.json file. This will cause composer to do the dirty work for you in it's generated autoloader.
Other than that, you can just put the require_once for the file with your functions in it into the index.php for your framework and away you go.
You could even take all of your functions, combine them into a class as static methods and simply call them using Libraryname::functionname(), then when you use this, the autoloader will take care of everything for you just like with any other class.
To recap, no autoloader, but lots of good, solid, clean options for making this work.

Codeigniter 3 - Third_party and controllers / loading ressources

I'd like to use the controllers from the /third_party/myapp/controllers folder without having to move them to the /application/controllers one
It doesn't seem possible (at least not without hacking the core). Though the question concerns loading model, limitations are mentioned here
I don't feel like using HMVC - as stated in the solution proposed there, cause I don't need any other functionality than avoiding to multiply file transfer in CI base folders (i don't really need the 'hierarchical' part of it)
But I don't really get it... if one declares its third_party app, CI will load resources from those folders (as stated by the doc)
config/
helpers/
language/
libraries/
models/
[metaphysical] Why wouldn't it be possible to simply load controllers as well ?
[pragmatic] is it a good idea to try to hack the core system or should I stay on the "HMVC or do copy your files" choice ?
[optimistic] do someone have a solution ?
EDIT
While looking further to trajchevska tip in another installation of CI, I tried to implement DFriend example of spl_register. I read several times the manual and this answer as well :
So far I understood that spl_autoload_register is triggered when a class is called, which allows to skip their manual loading
So I've tried simply adding the spl_autoload_register at the end of the config file in application/config/config.php, expecting it to print Auto load ClassName when a first class is called - a bit naïve, I know.
but from what I understood (thanks to DFriend), this spl_autoload_register would NOT work with controllers, as CI will always check if the file exists (in its own path) before trying to declare the controller.
It seems on the other hand that it could work with other classes (core, model ... see DFriend answer below)
Conclusion
So, after a few hairs pulled out of my head, I decided to try the HMVC extension for CI - MX from WireDesign. It solved my problem of loading any resources from different folder, along with bringing a brand new scope of shared classes and their attached issues (what is available where ?).
As it's the beginning the project I had the opportunity to switch to the "hierarchical" side, otherwise I'd followed Dfriend's advice of
going with the flow and putting controllers for third-party packages
where CI expects them to be
You could use PHP's spl_autoload_register to implement an autoloader. A google search will reveal multiple approaches for using one within the CI framework.
One that works and is often described is to register the autoloader in config.php or maybe a site-specific constants file. Such a solution is to put something like this in one of the mentioned files.
spl_autoload_register(function($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.`/third_party/myapp/controllers/`.$class.'.php'))
{
require_once $file;
}
}
});
You can use hooks if you want to avoid hacking config.php or some other file. Here's one way to do that.
application/config/config.php
$config['enable_hooks'] = TRUE;
application/config/hooks.php
$hook['pre_system'][] = array(
'class' => '',
'function' => 'register_autoloader',
'filename' => 'Auto_load.php',
'filepath' => 'hooks'
);
application/hooks/Auto_load.php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
function register_autoloader()
{
spl_autoload_register('my_autoloader');
}
function my_autoloader($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.`/third_party/myapp/controllers/`.$class.'.php'))
{
require_once $file;
}
}
}
Addendum
On further digging and consideration the above will not work because CI insists that Controllers be in the application/controllers directory or a subdirectory thereof.
PHP will run a registered autoloader when you try to use a class/interface which hasn’t been defined yet. Controllers are php classes so you might expect an autoloader to come to the rescue. But CI is very explicit about where it looks for controller files. If the file is not found then it never attempts any calls to the class. Instead it abandons all hope and immediately issues an 404 error. The autoloader never gets a chance to look for the file.
The answer to the metaphysical question is that CI's routing and URL parsing are tightly coupled to the file storage structure making the desired functionality impossible. (without some serious core hacking)
The answer to the pragmatic question is very subjective. I'm not fond of the HMVC approach and personally would opt for going with the flow and putting controllers for third-party packages where CI expects them to be.
Start: Serious diversion from question at hand
So, what good is registering an autoloader in the CI environment?
Controllers are defined like so.
class Home extends CI_Controller{ ...
The class being extended, CI_Controller, is a core class. If you want to extend this core class for use by multiple other controllers you can use the CI convention of prepending the class name with "MY_". e.g.
class MY_Controller extends CI_Controller
(
//class implementation
}
and then use it to define actual controllers using the extended core class
class Home extends MY_Controller{ ...
The problem is that you can only extend the core CI_Controller once using this convention. What if you have a need for more than one extension to the CI_Controller core class? One solution is registering an autoloader. (A good discussion of this and other ways to overcome the MY_Controller monopoly is HERE.)
An autoloader for these purposes could look this.
spl_autoload_register(function($class)
{
if(strpos($class, 'CI_') !== 0)
{
if(file_exists($file = APPPATH.'libraries/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'models/'.$class.'.php'))
{
require_once $file;
}
elseif(file_exists($file = APPPATH.'core/'.$class.'.php'))
{
require_once $file;
}
}
}
Note that the above also allows you to extend models from other models.
End: Serious diversion from question at hand
I assume it's because of the routing process - the CI Router checks whether a corresponding controller exists in the application folder, or a respective route is defined in the config. If none of them is found, 404 is returned.
You can override the Router file to enable loading controllers from other locations. In the core folder, create a MY_Router file that will extend the CI_Router. Then extend the _validate_request function, to allow loading controller files from different path than the default.
UPDATE: So, I've spent some time on this and didn't manage to extend the Router to get the controller file from the third party folder. It's easy to do when it comes to subfolders of the default controllers directory, but setting up a custom directory for reading the controllers is not that straightforward and unfortunately, I couldn't find any documentation on that.

Autoloading Classes from PHP Project into Zend Framework 2 Project

What is best possible approach to autoload classes from a PHP Project which is residing in
PHP Project
C:
|Projects
|MyProject
|Classes
|CStudents.php
|Student (ClassName)
|getStudentName() (Method)
All the classes have prefixes, such that, if the class is Students then it resides in CStudents.php
Right now I need to access these classes pull out some data and further with the Zend Framework 2 modules.
Zend Framework 2 Project:
C:
|Projects
|Zend
|module
|Application
|Module.php
So what would be the simple and best possible approach for this. I need to autoload not more than 50 Classes.
EDIT: My question is almost similar to this one, but the only difference is that I have the requirement for Zend Framework 2.
As mentioned all Classes are residing in Classes folder, Class files have "C" [CStudent.php] as prefixes and the class names [Class Student]start without the prefixes, so lets say I want to access Student::getStudentName(); How can I accomplish that?
Thanks
Here is an example of how I use the Autoloader.
Module.php
/**
* Autoloader
*
* #return array
*/
public function getAutoloaderConfig()
{
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php'
)
);
}
ZF2 comes with a classmap_generator.php which will build the required PHP Array of all the classes found in your Module. You can run classmap_generator -l modules/NameOfYourModule.
This will output a file called autoload_classmap.php and place it inside of your Module/NameOfYourModule folder for you.
You can also find a lot more information in the documentation http://zf2.readthedocs.org/en/latest/modules/zend.loader.autoloader-factory.html

zend framework models location and autoloading

I've started to document myself regarding Zend Framework as I will soon start using it in production. Everything was working fine, until I started to use and work with models :).
The default location for models, based on Zend recommendations, is application/models/DbTable, where all the models will be thrown in. This location forces me to name a model like Application_Model_DbTable_Actors. For me, this is a very long name for a model and not a easy to use one.
The directory structure that I want to obtain looks something like this:
application/
models/
actors/
ActorsMapper.php
Actor.php
books/
BooksMapper.php
Book.php
etc.
So all my models will reside under the models directory, but grouped in their own directories.
Naming for each class should be ActorsMapper or Actor (They will both extend Zend_Db_Table or Zend_Db_Row).
I am aware of the fact that in my controllers if I instantiate a model using something like $actors = new ActorsMapper() I will get a Fatal error: Class not found and that why I'm asking for your help with this.
To solve this issue I tried to add my models directory to the include_path:
first try
added includePaths.models = APPLICATION_PATH "/models" to application.ini
but this one doesn't even add it to the include path
second try:
explicitely added the path using set_include_path(implode(PATH_SEPARATOR, array(
realpath(APPLICATION_PATH . '/../library'),
realpath(APPLICATION_PATH . '/models'),
get_include_path(),
)));
but even if this adds that path among the included ones, the error still persists.
I've seen this naming of models in the official documentation of the Zend_Db_Table, but I couldn't find anything related to autoloading them.
Thank you all for any solutions.
p.s. zend framework version is 1.11.1
Zend Framework has a build in autoloader. It works by mapping the class name to the directory tree, so Application_Model_Actors_Actor will get mapped to
Application\
Models\
Actors\
Actor.php
Unfortunately, you cannot change this. The feature you are looking for is namespacing but it is only supported (and is in fact one of the major features) of Zend Framework 2 which is still being developed.
Try to extend Zend_Application_Bootstrap_Bootstrap then you can try this one
$loader = $this->getResourceLoader();
$loader->addResourceType('books', 'models/books', 'Model_Book');
$loader->addResourceType('actors','models/actors','Model_Actor');
I am also trying to implement this kind of implementation in a observer pattern.

Adding 3rd party library to ZendFramework

my question is simple:
How do I add an API and/or 3rd party
library to my ZendFramework
application in a way which makes it
possible to access it in a
controller
Here is a blog post detailing how to achieve this: http://blog.keppens.biz/2009/05/add-your-own-library-to-your-new-zend.html
Alternatively, if you don't want to tweak the application.ini file, you can do it through your Bootstrap class. Add this function to Bootstrap:
protected function _initAutoload() {
$loader = Zend_Loader_Autoloader::getInstance();
$loader->registerNamespace('MyCode_');
}
Then in the "library" folder, you would add a folder called "MyCode". This folder should be parallel to the "Zend" folder. Naturally you should change "MyCode" to reflect the name of the library you're adding.
I should note that by using the above method, I'm assuming the code uses the PEAR naming scheme (just like ZF).

Categories