I have a script (a small and simple CMS-like system), which I'm always working on and use it for client sites. Since clients have different requirements, I've implemented a module system which allows me to modify/or add functionality to the CMS, without having to modify the CMS script.
How can I implement a configuration system that allows me to change the default settings of the main CMS from the modules?
For example the CMS has by default two menus: $menu = array('menu-1', 'menu-2');
How could I override this setting from the modules?
One solution I've thought of is to use constants and serialize/unserialize:
defined("BLA") or define("BLA", serialize(array(
'boo' => 'stuff',
'foo' => array('1', '2', '3'),
'moo' => true,
...
)));
So I could easily override this setting in the module initialization function which runs before the constant is defined in the CMS.
Then I'm using these constants everywhere inside my scripts, like:
$bla = unserialize(BLA);
...
foreach(unserialize(BLA) as $key => $value)...
Another alternative would be to use a global variable, but people say it's bad to use global.
So are there any better solutions to what I'm looking for?
I would recommend using a class with static variables. More or less the same result, but no need for unserializations and you can actually use the variable, not a temporary one.
// My Constants
class MCo {
public static $BLA = array(
'boo' => 'stuff',
'foo' => array('1', '2', '3'),
'moo' => true,
// ...
);
}
echo MCo::$BLA['boo'];
foreach (MCo::$BLA as $key => $value) {
// ...
}
EDIT: ircmaxell has a point, consider this then
// My Private Constants
class MPCo {
private static $_BLA = array(
'boo' => 'stuff',
'foo' => array('1', '2', '3'),
'moo' => true,
// ...
);
public static BLA() {
return self::$_BLA;
}
}
foreach (MPCo::BLA() as $key => $value) {
// ...
}
If it's going to be a lot of static data that you plan on storing in the array, why not store it in a config file? Alternatively, you could load it from a DB, but config is better for this.
And the other option is to do what #inti suggested in his answer.
The first question I'd ask is why do you need an array in a constant. There is likely another solution to your problem that's just as good, but doesn't need arrays.
One alternative that would be better is to create a configuration object, and pass that around. That way it's still testable since it's injecting the dependencies and doesn't have a performance impact of working with serialize.
$config = new StdClass();
$config->boo = 'stuff';
doSomething($config);
Constants aren't much better than global variables. In fact, they are worse in some respects since it's very hard to test with them since once you define a constant, you can't change it. So in the pursuit of good testable code, constants are not good. If you're dependent on constants other than for dealing with magic numbers or filesystem paths, you might want to rethink your approach (Even magic number constants might be better handled with a configuration class)...
This is really unnecessary, just use a variable instead of a constant, it will be simpler, clearer and I would guess better performing.
Related
I've a set of data (~30 properties, all having their own array of values) that I want to pass around to various classes in PHP and I want to also enforce the data's array structure. Multiple classes will be expecting this structure to be consistent.
Because of these facts I can't really rely on a standard array and so I decided to pass an object around. I looked into ArrayObject and while it allows me to set/get as if the class were an array I didn't see anything about enforcing the structure.
Is there an existing standard class that can handle enforcement of it's array-like structure while still being treated as an array, e.g., basically ArrayObject + enforcement?
An example of the array structure:
$item_type_props = array(
'phone' => array('speed' => 1000, 'self_label' => false, 'support_poe' => true, 'bluetooth' => false),
'pbx' => array('acd_support' => true, 'max_conn' => 300, max_phones => 600),
'adapter' => array('fxo' => 4, 'fxs' => 0, 't1_e1_pri' => 0),
etc...
);
I know each property in the array could be it's own class and enforce it's own fields through the constructor and set/get but then I suddenly have ~30 classes that are nothing but a bunch of attributes and that seems somewhat excessive for just holding data.
Alternatively, if I'm just approaching this from the wrong mindset and missing something really, really obvious then please do point it out. I get the feeling that I am but my brain might be having a vacation.
While you could roll your own, I encourage you to use an existing validation implementation. For example, Symfony\Validator allows you to define nested structures and the requirements on each level:
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
$validator = Validation::createValidator();
$constraint = new Assert\Collection(array(
// the keys correspond to the keys in the input array
'name' => new Assert\Collection(array(
'first_name' => new Assert\Length(array('min' => 101)),
'last_name' => new Assert\Length(array('min' => 1)),
)),
'email' => new Assert\Email(),
'simple' => new Assert\Length(array('min' => 102)),
'gender' => new Assert\Choice(array(3, 4)),
'file' => new Assert\File(),
'password' => new Assert\Length(array('min' => 60)),
));
$violations = $validator->validate($input, $constraint);
This lets you push the details of how to validate down to another (already tested) level, while letting your code focus on why it needs this data. For the Symfony case, you can use an array as the storage mechanism, and use a design that firewalls unvalidated from validated data.
One way we might do this is notationally. Pretend we have implemented a method, perhaps using Symfony's validator, to return a validated array. We can use Hungarian notation to indicate our structure has passed through validation and is "safe":
<?php
$vInput = validate($_GET); // Hungarian notation: any variable beginning with "v" is "validated" and safe to use
function foobar(array $vInput) { ... }
While this is performant, it's not great for maintenance in the long term. So, you might consider an object wrapper that allows you to leverage the type system:
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Constraints as Assert;
class ValidatedArray extends \ArrayObject {
public function construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator') {
$violations = Validation::createValidator()->validate($array, $this->constraints());
// throw exception if any violations
parent::__construct($input, $flags, $iterator_class);
}
public function __offsetSet($index, $value) {
$constraints = $this->constraints()[$index]; // specific constraints to this index
$violations = Validation::createValidator()->validate($array, $constraints);
// throw exception on violations
parent::__offsetSet($index, $value);
}
public function constraints() {
return new Assert\Collection(...);
}
}
$input = new ValidatedArray($_REQUEST); // first time validation
$input['foo'] = 'bar'; // also subject to validation
You might want to make this implementation an abstract base class, with concrete descendants implementing the constraints method to provide the specific limitations on the array object itself. This provides a flexible way to get dedicated Data Transfer Objects.
Generally speaking I would argue that - unless you are passing data to another context, e.g. javascript - a PHP application should be nicely organized in PHP classes. This is simply the easiest way to enforce the structure. Your are right, this might result in quite straightforward DTO's with a bunch of getters and setters, but it will beat checking array structures for sure. In your case there also appears to be a relationship in the array, otherwise it would not make sense combining them into an array at all.
Using PHP7 you can clearly define the method signature and enforce the types, e.g.
public function setSomething(string $myValue)
{
$this->something = $myValue;
}
Same with return types:
public function myActionMethod(array $options): ActionRespsonse
{
// Do something
}
If you have more complex data types, I would recommend using Value Objects. These are nothing more but simple PHP classes that represent a more complex value. For example, a phone number:
public function setPhoneNumber(PhoneNumber $phoneNumber)
{
$this->phoneNumber = $phoneNumber;
}
Here the PhoneNumber is a Value Object which effectively is a tiny class by itself which enforces its usage:
class PhoneNumber {
private $phoneNumber;
public __construct(string $phoneNumber) {
if (strlen($phoneNumber) != 10) {
throw new \Exception('Not a valid phone number');
}
$this->phoneNumber = $phoneNumber;
}
}
This is also where the validation can tie into the answer from #bishop as you can use an existing Validator to help you out. You can find an example of a Value Object of a Phone Number here (just googled it): Example Phone Number Value Object
I do have the feeling that you might be converting your PHP data to an array for another reason? Like interacting with a database, or passing it on to a different context, such as Javascript?
In that case, once you have your DTO's and VO's neatly defined you can then consider serializing them e.g. to/from JSON. You can use Symfony libraries for that, as described here: Symfony Serializer
If you really want arrays you can also consider hydrating them to/from entities, using the library from Marco Pivetta (ocramius) who is pretty much the authority on hydration an approach extensively used in Doctrine: Ocramius Hydrator
Plenty of options, eh!?
To be honest, though, IMHO typically there would need to be a pretty good argument to pass these complex arrays around as arrays offer very little in supporting functionality. In addition, your code will very likely and very quickly become difficult to read and maintain, as at every point where there would be some kind of modification of the array, or usage of its data elements, you would need to implement all kinds of checks or run a validation as described by #bishop. I would not allow something like that to exist in our applications code bases...
So, concluding: If you have a tightly defined set of PHP Objects with well established constructor, properties, getters, setters, constructor, relationships, action methods and besides that some kind of serializing mechanism, you're in good shape. Also other developers will be 'forced' to work with the objects and provided interface (methods and VO's) thus guaranteeing a degree of maintainability and quality.
By the way, you might consider reasing Martin Fowler's thoughts about these things: Martin Fowler: DTO Martin Fowler: VO Martin Fowler: DTO2
I am making a library for CodeIgniter, and I wish to pass multiple parameters of different types (a PDO object, username and password, configurations, etc).
I know I can pass an array of all of these things, but that doesn't seem to be the best way of doing things (as $params can't ever describe what is needed).
How can I pass multiple parameters to a library?
Thanks in advance.
There are several approaches to this particular problem. I'll list (in preferred order) ways I know to solve it:
Associative Array Arguments:
This approach is pretty flexible, as the order of the parameters doesn't matter, and it resolves a pretty big complaint many have with how PHP defines function parameters. You simply pass in the "non-default" parameters you want. This is probably the most "codeigniterish" way to do it, if that's even a thing.
class MyLibrary {
public function __construct($params = array())
{
// Merge default parameter names and values,
// with given $params array
$params = array_merge(array(
'server' => 'myserver',
'database' => 'mydatabase',
'username' => 'myuser',
'password' => 'mypassword'
), $params);
// Create variables from parameter list
extract($params);
var_dump($server);
var_dump($database);
var_dump($username);
var_dump($password);
}
}
// Initialization:
$this->load->library('mylibrary', array(
'server' => 'server-arg1',
'database' => 'database-arg2'
));
Numbered Arguments:
This approach replicates the typical PHP parameter paradigm (defines names, orders, and default values for all expected parameters).
class MyLibrary {
public function __construct($params = array())
{
// Add relevant defaults to missing parameters
$params = array_merge($params, array_slice(array(
'myserver',
'mydatabase',
'myuser',
'mypassword'
), count($params)));
// Create variables from parameter list
extract(array_combine(array(
'server',
'database',
'username',
'password'
), $params));
var_dump($server);
var_dump($database);
var_dump($username);
var_dump($password);
}
}
// Initialization:
$this->load->library('mylibrary', array('server-arg1', 'database-arg2'));
Override the CI Loader class:
This is AT YOUR OWN RISK. Basically, the CI_Loader::_ci_init_class() method needs to be overridden with a MY_Loader class and corresponding method. These are the lines that you "don't like" (lines 1003-1012 in my install):
// Instantiate the class
$CI =& get_instance();
if ($config !== NULL)
{
$CI->$classvar = new $name($config);
}
else
{
$CI->$classvar = new $name;
}
The "safest" replacement that I could guess would be this:
// Instantiate the class
$CI =& get_instance();
if (isset($config[1])
{
// With numeric keys, it makes sense to assume this is
// is an ordered parameter list
$rc = new ReflectionClass($name);
$CI->$classvar = $rc->newInstanceArgs($config);
}
elseif ($config !== NULL)
{
// Otherwise, the default CI approach is probably good
$CI->$classvar = new $name($config);
}
else
{
// With no parameters, it's moot
$CI->$classvar = new $name;
}
I really don't know how many things this will break, but I can almost certainly say there will be something. It's not really worth the risk. I'd STRONGLY recommend the first approach above.
Cheers!
One can sidestep Codeigniters Loader class and instantiate objects directly. APPPATH is a Codeigniter constant.
require_once( APPPATH.'libraries/some_class.php' );
$this->Some_class = new Some_class( param_1, param_2, param_n );
$this->Some_class->do_something();
Usage is the same as if you'd loaded the Library with Codeigniters Loader class.
$this->load->library( 'some_class' )
$this->Some_class->do_something();
I personally don't like passing arrays to my classes becasue then you have to validate the contents. PHP can take care of this for you when the parameters are passed to the __construct() individually.
From my experience with CodeIgniter, unless you modify the loader class to act differently (as you might know, in application/core/ folder is where you have to implement you custom class) there is no way (no one of that I know)
I use many external libraries (mostly api sdks or sparks) and I like to build my own config files where to set values that will be loaded into libraries when called upon them, so when I need to load libraries I just build a simple or multidimensional $params = array() according to my needs and then work with it inside library.
So in short answer, $this->load->library('lib_name', $params) is the only way I am aware of.
By default in CodeIgniter you can't. You can only pass a single param to your library classes. As stated before that param could be an array that contains all the other parameters and you could just use it as $param[0], $param[1]....
But I agree with you, it's kinda strange and ugly to see.
If you have PHP5+ there's the ReflectionClass that could help you with this but you should edit the source code of CodeIgniter and implement this function. The function you are looking for is ReflectionClass::newInstance() that you can find here.
use $this->upload->initialize($configUpload); after $this->load->library('upload', $configUpload);
this is best way:create a big array
$data1=array('item1','item2','item3');
$arr=array('data1'=>$data1,'data2'=>'item4','data3'=>'item5');//create big array of datas
extract($arr);//convert that to variables again after send to library
//use that again
print_r($data);
echo $data2;
echo $data3;
I have been doing PHP stuff for almost one year and I have never used the function eval() though I know the usage of it.
But I found many questions about it in SO.So can someone show me a simple example in which it's necessary to use eval()?And is it a good or bad practice?
eval() is necessary to implement a "compiling" template engine, like Smarty, that uses its own language and compiles it down to php on the fly. The main function of such engines is usually something like
function render_template($path) {
$code = file_get_contents($path);
$php = $this->compile_to_php($code);
eval($php);
}
Besides that, everytime you use "include" or "require", you're actually using "eval" under the hood - so, actually, eval is one of the mostly used php constructs.
Using eval() is a bad practice, and if it turns out to be necessary to achieve something, that is usually the sign of a underlying design error.
I can't think of any situation where it is necessary to use eval(). (i.e. something can't be achieved using other language constructs, or by fixing a broken design.) Interested to see whether any genuine cases come up here where eval actually is necessary or the alternative would be horribly complex.
The only instance of where it could be necessary is for executing code coming from an external source (e.g. database records.) But this is a design error in itself IMO.
Bad application design is always such an example.
Well I have used eval once. This was for a system, where the users could enter formulas using constants fished from the underlying system.
A string like:
(N * (G - 2,7)) / E
was taken and the constants replaced with values from the system eval is then used to get a value. eval seemed like the easiest way to go.
The statement was filtered to only allow operators and uppercase letters(no two next to each other) so perhaps this is not a "real" use case of eval, but it works and is pretty readable.
That said the system in questing is huge (200k+ lines) and this is the only place that eval is used.
A command line php shell is a great example. I guess you could fork the actual php code and write your shell extensions in C instead, but it seems much more sensible to do it in php. Since the person providing the code should already have full access to the system, there's no security issue at all. Once you get php compiled with readline, this sort of thing is actually really useful.
Drupal (optionally) uses eval to allow for ready extensibility. To accomplish this it takes user (generally administrator-only) input of code to be evaluated and stores it in the database. Drupal also has lots of people making sure that there are no security holes.
Using eval is quite dangerous, if see from security side. Anyway, a lot of template engines use eval, because they should parse page and get some variables or make calculations.
Eval useful for example in such case, as register widgets in cycle in wordpress while
creating custom theme:
class PluginusNetWPTF_Widget extends PluginusNetWPTF_Core {
public static $widgets = array(
'PLUGINUSNET_RECENT_POSTS_WIDGET' => array(
'description' => 'Recent posts of selected category',
'creation' => 'PluginusNet Recent Posts',
'fields' => array('title' => 'Recent Posts', 'category' => '', 'post_number' => 3, 'show_thumbnail' => 1, 'show_exerpt' => 0),
'view' => 'recent_posts',
'form' => 'recent_posts_form'
),
//'PLUGINUSNET_RECENT_POSTS_WIDGET2' => array(),
);
public static function register_widgets() {
foreach (self::$widgets as $widget_class_name => $widget_data) {
$code = '
class '.$widget_class_name.' extends WP_Widget {
//Widget Setup
function __construct() {
//Basic settings
$settings = array("classname" => __CLASS__, "description" => __(PluginusNetWPTF_Widget::$widgets[__CLASS__]["description"], PLUGINUSNET_THEME_NAME));
//Creation
$this->WP_Widget(__CLASS__, __(PluginusNetWPTF_Widget::$widgets[__CLASS__]["creation"], PLUGINUSNET_THEME_NAME), $settings);
}
//Widget view
function widget($args, $instance) {
$args["instance"] = $instance;
echo PluginusNetWPTF_Widget::draw_html("widget/" . PluginusNetWPTF_Widget::$widgets[__CLASS__]["view"], $args);
}
//Update widget
function update($new_instance, $old_instance) {
$instance = $old_instance;
if (!empty(PluginusNetWPTF_Widget::$widgets[__CLASS__]["fields"])) {
foreach (PluginusNetWPTF_Widget::$widgets[__CLASS__]["fields"] as $key => $value) {
$instance[$key] = $new_instance[$key];
}
}
return $instance;
}
//Widget form
function form($instance) {
//Defaults
$defaults = PluginusNetWPTF_Widget::$widgets[__CLASS__]["fields"];
$instance = wp_parse_args((array) $instance, $defaults);
$args = array();
$args["instance"] = $instance;
$args["widget"] = $this;
echo PluginusNetWPTF_Widget::draw_html("widget/" . PluginusNetWPTF_Widget::$widgets[__CLASS__]["form"], $args);
}
}
';
eval($code);
register_widget($widget_class_name);
}
}
}
So, I am looking at a number of ways to store my configuration data. I believe I've narrowed it down to 3 ways:
Just a simple variable
$config = array(
"database" => array(
"host" => "localhost",
"user" => "root",
"pass" => "",
"database" => "test"
)
);
echo $config['database']['host'];
I think that this is just too mutable, where as the configuration options shouldn't be able to be changed.
A Modified Standard Class
class stdDataClass {
// Holds the Data in a Private Array, so it cannot be changed afterwards.
private $data = array();
public function __construct($data)
{
// ......
$this->data = $data;
// .....
}
// Returns the Requested Key
public function __get($key)
{
return $this->data[$key];
}
// Throws an Error as you cannot change the data.
public function __set($key, $value)
{
throw new Exception("Tried to Set Static Variable");
}
}
$config = new stdStaticClass($config_options);
echo $config->database['host'];
Basically, all it does is encapsulates the above array into an object, and makes sure that the object can not be changed.
Or a Static Class
class AppConfig{
public static function getDatabaseInfo()
{
return array(
"host" => "localhost",
"user" => "root",
"pass" => "",
"database" => "test"
);
}
// .. etc ...
}
$config = AppConfig::getDatabaseInfo();
echo $config['host'];
This provides the ultimate immutability, but it also means that I would have to go in and manually edit the class whenever I wanted to change the data.
Which of the above do you think would be best to store configuration options in? Or is there a better way?
Of those 3 options, a static method is probably the best.
Really, though, "the best" is ultimately about what's easiest and most consistent for you to use. If the rest of your app isn't using any OO code then you might as well go with option #1. If you are ultimately wanting to write a whole db abstraction layer, option #2.
Without knowing something more about what your goals are and what the rest of your app looks like, it's kind of like asking someone what the best motor vehicle is -- it's a different answer depending on whether you're looking for a sports car, a cargo truck, or a motorcycle.
I'd go with whats behind door #3.
It looks easier to read and understand than #2, and seems to meet your needs better than #1.
Take a look at this question for ideas on storing the config data in a separate file:
Fastest way to store easily editable config data in PHP?
I'd use method #2 pulling the config data as an array from an external file.
The best way is that which fits your application best.
For a small app, it might be totally sufficient to use an array, even it is mutable. If no one is there to modify it except you, it doesn't have to be immutable.
The second approach is very flexible. It encapsulates data, but does not know anything about it. You can pass it around freely and consuming classes can take from it what they need. It is generic enough to be reused and it does not couple the config class to the concrete application. You could also use an interface with this or similar classes to allow for type hints in your method signatures to indicate a Config is required. Just don't name it stdDataClass, but name it by it's role: Config.
Your third solution is very concrete. It hardcodes a lot of assumptions about what your application requires into the class and it also makes it the responsibility of the class to know and provide this data through getters and setters. Depending on the amount of components requiring configuration, you might end up with a lot of specific getters. Chances are pretty good you will have to rewrite the entire thing for your next app, just because your next app has different components.
I'd go with the second approach. Also, have a look at Zend_Config, as it meets all your requirements already and let's you init the Config object from XML, Ini and plain arrays.
I am doing a PHP web site, without using any framework. I need that the site is available in several languages, and I was reading about it and it seems to be a little bit confusing. There are several solutions but all seem to depend on a specific framework.
What you think of using a simple translation function like the one shown below?
I mean, I would like to know what can be a disadvantage of using such code.
Here it is (this is just a simple and incomplete sample):
class Translator{
private $translations;
public function __construct(){
$this->translations = array(
'Inbox' => array(
'en' => 'Inbox',
'fr' => 'the french word for this'
),
'Messages' => array(
'en' => 'Messages',
'fr' => 'the french word for this'
)
//And so on...
);
}
public function translate($word,$lang){
echo $this->translations[$word][$lang];
}
}
It does not look bad. I've seen this used many times.
I would however separate the different strings in one file per language. At least, or if the files get large, one file per module per language.
Then your translation class can load and cache the language files (if you don't rely on any other caching system) every time a new language is to be used.
A little example of what i mean
class Translator {
private $lang = array();
private function findString($str,$lang) {
if (array_key_exists($str, $this->lang[$lang])) {
return $this->lang[$lang][$str];
}
return $str;
}
private function splitStrings($str) {
return explode('=',trim($str));
}
public function __($str,$lang) {
if (!array_key_exists($lang, $this->lang)) {
if (file_exists($lang.'.txt')) {
$strings = array_map(array($this,'splitStrings'),file($lang.'.txt'));
foreach ($strings as $k => $v) {
$this->lang[$lang][$v[0]] = $v[1];
}
return $this->findString($str, $lang);
}
else {
return $str;
}
}
else {
return $this->findString($str, $lang);
}
}
}
This will look for .txt files named after the language having entries such as this
Foo=FOO
Bar=BAR
It always falls back to the original string in case it does not find any translation.
It's a very simple example. But there is nothing wrong in my opinion with doing this by yourself if you have no need for a bigger framework.
To use it in a much simpler way you can always do this and create a file called 'EN_Example.txt'
class Example extends Translator {
private $lang = 'EN';
private $package = 'Example';
public function __($str) {
return parent::__($str, $this->lang . '_' . $this->package);
}
}
Sometimes you wish to translate strings that contain variables. One such approach is this which i find simple enough to use from time to time.
// Translate string "Fox=FOX %s %s"
$e = new Example();
// Translated string with substituted arguments
$s = printf($e->__('Fox'),'arg 1','arg 2');
To further integrate variable substitution the printf functionality can be put inside the __() function like this
public function __() {
if (func_num_args() < 1) {
return false;
}
$args = func_get_args();
$str = array_shift($args);
if (count($args)) {
return vsprintf(parent::__($str, $this->lang . '_' . $this->package),$args);
}
else {
return parent::__($str, $this->lang . '_' . $this->package);
}
}
There are a few things it appears you haven't considered:
Are you simply translating single words? What about sentence structure and syntax that differs between languages?
What do you do when a word or sentence hasn't been translated into a language yet?
Does your translations support variables? The order of words in a sentence can differ in different languages, and if you have a variable it usually won't be good enough simply to split the word around the sentence.
There are a two solutions that I've used and would recommend for PHP:
gettext - well supported in multiple languages
intsmarty - based on Smarty templates
The advantage with using a class or functions for this is that you can change the storage of the languages as the project grows. If you only have a few strings, there is absolutely no problems with your solution.
If you have a lot of strings it could take time, memory and harddrive resources to load the language arrays on all page loads. Then you probably want to split it up to different files, or maybe even use a database backend. If using i database, consider using caching (for example memcached) so you don't need to query the database hundreds of times with every page load.
You can also check out gettext which uses precompiled language files which are really fast.
I'd have thought it might be easier to simply use an include for each language, the contents of which could simply be a list of defines.
By doing this, you'd avoid both the overhead of including all the language data and the overhead of calling your 'translate' function on a regular basis.
Then again, this approach will limit things in terms of future flexability. (This may not be a factor though.)
It's fine to not use a framework. The only problem I see with your function is that it's loading a lot of data into memory. I would recommend having arrays for each language, that way you would only need to load the language that is being used.
Is using constants (defines) a bad practice?
That's how I have it setup. It was just to have multi langua support.
I have one portuguese file and an english files filled with:
define('CONST','Meaning');
Maybe this is a bit a memory hog, but I can access from every where I want :)
I may change to a oop approach, but for now I have this.
When I had a problem like this (but for a very small site, just a few pages) a long time ago, I created a file named langpack.php and any string of text on my site had to be run through that. Now, I would use a similar approach, but split over multiple files.
Example OOP Approach
langpack.php
abstract class langpack {
public static $language = array();
public static function get($n) {
return isset(self::$language[$n]) ? self::$language[$n] : null;
}
}
english.php
final class English extends langpack {
public static $language = array(
'inbox' => 'Inbox',
'messages' => 'Messages',
'downloadError' => 'There was an error downloading your files',
);
}
french.php
final class French extends langpack {
public static $language = array(
'inbox' => 'Inbioux',
'messages' => 'Omelette du Fromage',
'downloadError' => 'C\'est la vie',
);
}
You should get the idea from there. Implement an autoloader in a config file and then loading the language should be something you could easily do from the session, URL, or whatever, by using PHP's variable nature in conjunction with class instantiation, something like this:
$langpack = new $_SESSION['language'];
echo $langpack::get('inbox');
Of course, all this could be done with simple arrays, and accessed in an imperative style (with absolute references handled via $GLOBALS) to reduce some overhead and perhaps even make the mechanisms by which this is all handled a bit more transparent, but hey, that wouldn't be very OO, would it?
One could also be using the Symfony translation component, no framework is required and composer helps dealing with dependencies:
composer install --prefer-dist "symfony/translation":"#stable"
I think that's ok if you're not using any framework for other reasons. We've been in the same scenario as yours, when you cannot/don't want to use a more structured translation framework:
We were working at a small PHP project and where looking for some simple translation mechanism. We used an array approach similar to yours, but with separate files for each language texts. We put up a small component to keep thins as clean as possible.
If you want to give a look, we shared that on https://github.com/BrainCrumbz/simple-php-translate. Please feel free to improve it!
i would simply use a function with controller and language inputs, for instance:
function getLanguageFile($controller, $lang){
// security: only allow letters a-z in both strings
$controller = preg_replace('/([^a-z]*)/', '', $controller);
$lang = preg_replace('/([^a-z]*)/', '', $lang);
// return language details if present on disk
if (is_file('lang/'.$controller.'/'.$lang.'.json')){
return json_decode(file_get_contents('lang/'.$controller.'/'.$lang.'.json'));
}
return false;
}
you simply have to place your json formatted strings in lang/index/en.json if controller is index and language is en.
you could add a function for dependencies (for instance you want to load index controller values on access to another controller) all you have to do is to merge the results.
you could simply include php files with arrays aswell and just return the array then, but i suggest you split these translations in larger projects. if your project isn't that big, your function is absolutely ok.