Effective ways to implement helper functions? - php

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.

Related

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.

CakePHP 3 include custom lib

In CakePHP 2.x I had static classes inside:
\app\Lib\Util\MyStaticClass1.php
\app\Lib\Util\MyStaticClass2.php
Then in app\Config\bootstrap.php file I added these lines.
App::uses('MyStaticClass1', 'Lib/Util');
App::uses('MyStaticClass2', 'Lib/Util');
So I didn't include these classes in every controller, component, helper or ctp file.
How can I do this in CakePHP 3 ? I tried this:
I copied these files to
\src\Util\MyStaticClass1.php
\src\Util\MyStaticClass2.php
Then inside every controller, component, helper and ctp file I added this:
use App\Util\MyStaticClass1;
use App\Util\MyStaticClass2;
This works. But is there an easier way to include these files ?
But is there an easier way to include these files ?
I don't think so that's how the namespaces work. You could wrap the static method class in traits. See the EventManagerTrait of the core for example.
I had static classes (...) inside every controller, component, helper
This is bad practice and introduces tight coupling. I think your application architecture needs a serious overhaul if you used them everywhere extensively.

Where can I put my global code in Kohana 3.3?

Say I want a constant, a function or a class to be available in all my models, views and controllers code (within a particular web site (application)). Where can I put them? I don't mind importing them explicitly in every file I need them in but I don't know how (simple require_once does not seem to work).
You can put them in the vendor folder (in application/vendor or modules/MOD/vendor). Then you can load it like this:
require Kohana::find_file('vendor', 'folder/file','ext');
You can read up more on this in the user guide
Now, it should be stated you should in general not use functions or globals.
I declare Constants in Bootstrap.php and create my own Helpers for general functions under application/classes/Helpers.
If you need to integrate a third party library into Kohana or want to make code available to other Kohana users consider creating a module instead.
You can define all your constants in new php file and place it in the application/classes directory. In your Template controller or in the main controller like Welcome or Website, just place the code in the __constructor()
require_once Kohana::find_file( 'classes', 'filename' );
I suggest you to put your constant variables in the bootstrap.php file because this is loaded by the framework before every request.
You can simply put your classes in the root of the classes directory, the framework will find.
This worked well for me...
In application/config/constants.php:
define('SOME_COOL_CONSTANT', 'foo');
return array();
In index.php:
Kohana::$config->load('constants');
SOME_COOL_CONSTANT should then be available globally.

PHP Global namespace aliases

Here is the scenario.
I am implementing namespaces into my projects.
I have my own custom bridge library that calls other libraries like Zend to do the heavy lifting.
I have no problem using fully qualified namespaces in my custom bridge library but would like to keep the code as terse as possible in my controllers, models and view.
Here is an example of some aliasses i would like to use:
use BridgeLibName\Stdlib\Arrays as arr;
use BridgeLibName\Stdlib\Objects as obj;
use BridgeLibName\Stdlib\Strings as str;
use BridgeLibName\Stdlib\Numbers as num;
use BridgeLibName\Stdlib\File as file;
etc.........
Example usage:
$file = new file('path/to/file.txt');
$file->create();
or
$obj = arr::toObject(['key1'=>'value1']);
is it possible in any way to create an alias or constant that can be globally accessible and not discarded at the end of each file?
Some kind of bootstrap file that can make these aliases stick.
As I was writing the question i thought of a solution.
You can fake it by creating classes that extend the namespaced classes.
example:
class arr extends BridgeLibName\Stdlib\Arrays{
}
One important thing to remember:
If you are going to extend the classes the namespaced class will have to be loaded.
This could have performance implications if used too much since aliases and namespaces are only loaded as needed.
Since I am only using it to bridge to other classes there is very little logic inside my bridge files.
These bridge files in turn uses aliases and namespaces correctly thus loading the real files as needed.
I you are not careful with the implementation you can load a lot of unnecessary stuff and cause your app to become slow and bloated.
A nice thing I noticed is that good IDEs like netbeans also seems to be able to do auto completion with this method.
If there is a better way to do this please let me know.
Just thought of an amendment to this method to fix the problem with unnecessary class instantiation.
The core library can work with the normal psr-0 loader.
To have the aliases autoload I created an aditional dir named includes next to my namespaced class.
in composer you describe it like so:
"autoload": {
"psr-0": {
"BridgeLibName\\": "."
},
"classmap": ["include/"]
}
Now your libraries will load as expected from the correct namespace and your alias classes will autoload as needed.
Classes put into the include dir can now extend the namespaced classes ( as shown above ) and will no longer be loaded prior to being used.
Now you have global aliases without having to sacrifice performance by loading unused classes.

Proper way to include SPL Class Loader

Problem
I am using the SPL Class Loader provided by PSR Group. However, I've come to a chicken-egg paradox.
That is, this will autoload classes preventing explicitly including them. But I first need to include/require this class and the code for instantiating the object
$classLoader = new SplClassLoader('Doctrine\Common', '/path/to/doctrine');
$classLoader->register();
Question
What is common solution for including the autoloader code?
In the case of a front-end controller, I could add the code there. But I'm not always using the front-end controller patter. I suppose I could also use PHP's auto_prepend_file. Interested in the communities input.
You explicitly require the autoloader on the bootstrap stage (the stage where the application is started up).
All subsequent functions/classes are then autoloaded.

Categories