Textpattern CMS support forum
You are not logged in. Register | Login | Help
- Topics: Active | Unanswered
Braindead OOP with global classes
This is probably not the correct forum for a non-txpish programming query, but if I post on StackOverflow, the code nazis there will call me names, go off on a tangent about evil globals / how my project is fundamentally flawed, and I’ll never get a straight answer.
I’m creating a web application and want to use good object practices. Since it’s an international audience one of the things I’m keen to do is l10n strings, a bit like Txp does. So I’ve wrapped all the language-type functionality up in a class — gTxt()
, setLang()
, getLang()
, loadLang()
from the en-gb.txt, fr-fr.txt, etc files, blah blah. It ultimately populates a class variable $textarray
which the public method gTxt()
accesses to return the localised string. All nice and objecty so far.
Now I want all the other classes to be able to use it to translate their internal strings. And therein lies the rub: I can’t decide the best (cleanest, least hacky, most accepted) way to do it.
As far as I can figure, I can either instantiate one from index.php
:
global $i18n;
$i18n = new LangHandler();
and then in all the other class files I can either:
- import
global $i18n
; in any methods that need to display localised strings - pass
$i18n
as a parameter into any method that needs it - pass
$i18n
in as a parameter to the constructor of any class that needs it, setting a class level variable to refer to the passed-in object
Option 1 uses that word everyone shivers at.
Option 2 seems like a lot of work and I get into the sticky situation of what happens if the method’s signature is extended in future: the i18n variable becomes embedded as, like, arg 4 of 7 which is silly. Plus, why does anybody who uses the (public) method in future have to be saddled with remembering to pass in an (internal) i18n object?
Option 3 seems the cleanest on the surface, but is saddled with the same backwards compatibility/usability caveats of what happens to the constructor signature if I add parameters in future, and why should anyone who wants to reuse the class have to remember to pass in an i18n object.
Alternatively I could simply create a new LangHandler()
inside the constructor of each class that needs one, set a class level variable and then use it inside the function. No globals in sight, and it feels less coupled, but since the LangHandler class reads its strings from files, I’m creating a lot of duplicate objects with an unnecessary amount of file access which wastes resource and slows things down. I could partially get round the file access portion by using a static inside the LangHandler()
(is that frowned upon in OO circles?) and returning that for subsequent instances, but I’m still instantiating lots of the same class which is going to waste memory, right?
Thirdly I could implement the LangHandler as a Singleton thus guaranteeing that the same object is returned each time. The syntax seems like an extension of the second approach inside any class constructor that needs localised strings:
Class SomeClass {
private $i18n;
public function __construct() {
$this->i18n = LangHandler::getInstance();
// rest of constructor
}
public some_method() {
echo $this->i18n->gTxt('my_string');
}
}
But I’ve read that singletons are now being considered bad practice because, at the end of the day, they’re still introducing global state to an app. So are they any better than just using a standard global?
There may be other approaches I’ve not thought of (ditch the class altogether and do it as procedural code?!) in which case, please feel free to educate me. Right now I’m stuck trying to find the best approach that gives me:
- most efficient, least wasteful implementation
- cleanest / shortest syntax to get at the
gTxt()
method, as I’ll be using it a lot throughout the code - maximum flexibility to extend the app in future without battling through obscure method signatures
- no condescending looks or tutting from other, more competent, developers when they see the code
Please help set me on the path to righteous objectness if you can. Many thanks in advance.
Last edited by Bloke (2012-11-14 12:12:39)
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline
Re: Braindead OOP with global classes
If only thing you want to do is avoid global namespace, then you could just define a namespace and call it a day. There are also interfaces for offering templates, extending, statics and use for namespaces. E.g.
namespace YourAppNamespace\Members;
use YourAppNamespace\L10n as L10n;
class SomeClassAndStuff
{
public function no_methods_here()
{
L10n::get('hello_world');
L10n::set('set_string', 'Set string');
}
}
Namespaces support aliasing and importing. Plus you can build a autoloader around them which offers very painless experience, and due to aliasing, potentially shorter syntax.
Bloke wrote:
static … is that frowned upon in OO circles?
For using them as a shortcut from me the answer would normally be yes, which the common use — people wanting ‘shorter’ syntax. In which point you start to question, why classes.
There is nothing ultimately wrong with statics if you need storage that carries over instances. For that purpose they are fine, and there is some unreasonable hate against that. But, keep in mind that statics also defeats the purpose of having instances — instances.
Last edited by Gocom (2012-11-14 13:51:30)
Offline
Re: Braindead OOP with global classes
Gocom wrote:
you could just define a namespace and call it a day.
Ahaaa. I just checked and the minimum version of PHP we’re supporting for the app is 5.3.0 so this might well be a winner. I’ll have a play with this approach, thanks for opening my eyes.
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline
Re: Braindead OOP with global classes
Gocom wrote:
L10n::set(‘set_string’, ‘Set string’);
Haddock.
The only slight issue with this approach is the methods are called statically using ::
so I can’t seem to use $this to refer to the class variables: I receive Using $this when not in object context
errors. Maybe I screwed up somewhere else. Namespaces do feel like the right way to go so I’m sure this is just a hiccup in my logic. Uhh, somewhere.
Last edited by Bloke (2012-11-16 09:42:15)
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline
Re: Braindead OOP with global classes
You might want to look into the pattern I used for the TextfilerSet class.
It sports:
- a private singleton
- thus a handy private constructor to initialise instance variables
- a “class-named-spaced” static caller interface which hides much of the internal complexity from the outside
You can of course extend this pattern with PHP namespaces to add another layer of encapsulation.
Offline
Re: Braindead OOP with global classes
wet wrote:
You might want to look into the pattern I used for the TextfilerSet class.
Thanks. That was (sort of) the route I was going down before the namespace thing popped up. Given the slightly annoying static limitations (which can be worked around with heavy doses of self::
or static::
) I’m warming to the singleton approach — despite what the naysayers and code perfectionists claim — combined with namespaces.
Last edited by Bloke (2012-11-16 13:33:43)
The smd plugin menagerie — for when you need one more gribble of power from Textpattern. Bleeding-edge code available on GitHub.
Txp Builders – finely-crafted code, design and Txp
Offline