Jump to content

Simple OOP question: best practice for site-wide constants


johnsmith153

Recommended Posts

What is the best practice for using site-wide constants in a class?

 

E.g. COMPANY_NAME is set in a config file and used on many pages. How would it be best to use this in a class. I'm guessing just calling COMPANY_NAME anywhere in the class would not be considered best practice.

 

Thanks.

Link to comment
Share on other sites

Good OO design never references globals from object instances.

 

Your class should take $companyName as a constructor param.

 

Without knowing exactly what your class is or what it does, it's hard to provide further info.

Link to comment
Share on other sites

I'm talking for all and any class I ever write.

 

For me, this seems the best way:

 

class Whatever {

const CO_NAME = COMPANY NAME; // name of the company

 

Then (and I know there's a word for it) if the class was to be used in another system, they simply do this, or whatever that matches their system, and the rest of the class should (if designed correctly) work just be changing the class properties/constants etc.

 

class Whatever {

const CO_NAME = NAME_OF_OUR_COMPANY_FROM_THE_CONFIG_FOR_THIS_DOMAIN;

 

...of course this is just an example, but I am talking for all site-wide variables in any class that needs them.

Link to comment
Share on other sites

A class constant must be a literal value, e.g. const X = "value"; not a reference to an address in memory, e.g. const X = MY_GLOBAL_CONST;  The latter will result in a compile-time error.

 

Further, the definition of a class should never contain references to external values.  That's against OO principles.

 

Think of the word "class".  It's a class of object.  A pattern, an archetype.  Not an instance.  A class should always be expandable, portable, reusable, and environment-agnostic.

 

Thus, it should not be dependent upon application settings.  If anything, it should accept parameters in its construcctor.

 

I really suggest you read up on OO design methodology if you plan on getting heavily into software development. 

Link to comment
Share on other sites

Thanks for the reply. I'm not an OOP expert, so this isn't an I know more than you thing. I'm honestly just challenging the apparent right way to do things.

 

A class constant must be a literal value, e.g. const X = "value"; not a reference to an address in memory, e.g. const X = MY_GLOBAL_CONST;  The latter will result in a compile-time error.

...Using my method does not result in any errors. It works just fine.

 

Further, the definition of a class should never contain references to external values.  That's against OO principles.

...Whilst I did say I wanted best practice, I don't think I'm too bothered about OO principles if I have found what could be a better method.

 

A class should always be expandable, portable, reusable, and environment-agnostic.

...my method does all these things and does it better.

 

Thus, it should not be dependent upon application settings.  If anything, it should accept parameters in its constructor.

...but you still have to pass the application settings to it (unless you want to have to change settings in many classes as well as a config file).

 

My method means you can have one config file for everything rather than worrying about other settings (I noticed you recommended hard-coding settings in a class here which again would be better to use my method: http://www.phpfreaks.com/forums/index.php?topic=359554.0).

 

I've heard people go on about DRY (don't repeat yourself). Well surely my method saves repetition by manually having to specify values in the class.

 

I understand you've probably been programming for years and know OOP inside out, and you're exactly the person I hoped would answer my post, but I can't accept a reason just because that's how it has always been done.

 

I understand the value in being able to re-use the class, either for multiple domains on a server, or as a usable-for-all type thing, and my method allows that.

 

Please don't take offence to this and respond with 'well you need to read some books on OOP' or that kind of thing. I would love it if you could show me a better reason for why I would follow your method.

 

This isn't something I've been regularly using, it's just something I thought about as would be a better method.

 

Thanks for taking the time to share your expertise.

Link to comment
Share on other sites

I stand corrected.... I guess a const can reference a global constant.  It's still bad practice, though.  It will definitely fail if you try to reference a variable.

 

As for how you choose to do things, go ahead and do them however you want.  In which case, I can't see why you bothered starting this thread.

 

Bottom line is you're wrong, though. Your way is not superior.  You'll eventually run into a wall, think back on this thread, and realize you were warned. 

 

I'm not going to turn this into a  debate, though, when you are obviously resistant to advice, and won't even take the initiative to read the plethora of resources available to you.

 

 

If you really want to learn how good OO works, I suggest you learn Java.  It will teach you why OO principles are the way they are, and will reinforce good habits, like making code portable (without modification).

Link to comment
Share on other sites

No, I'm not going to just go ahead and use my method. I'm going to get the best advice and then make a decision.

 

I'm listening to you or anybody who wants to advise. But please, only reasons for answers, rather than just "that's not how we do it, see Google or php.net", as that is obvious.

 

You say about taking initiative to read resources, and I have. But, the resources aren't enough on their own to answer this, hence my question on this forum.

 

I would be really interested if someone could advise on what possible problems I could have in the future, maybe then it will make me think again.

 

Thanks again for you help.

 

---

 

Very strange. Whilst I was typing my response, you removed a few things from your post, such as "you'll go and do things your way anyway" and "I'm not going to bother explaining" - hopefully my post still makes sense.

 

I am very interested in the PHP experts on here to advise on this, and give an actual real-world reason for whichever method to use, rather than 'because the book says'.

 

Link to comment
Share on other sites

I've already explained it... by creating a dependency on a global constant defined in your application, your classes will only work in your application.  Thus, they cannot be built upon, extended, etc., for other uses.

 

Of course, this whole conversation is without context, because you've decided not to give an example of what you're talking about... so we're just arguing OO principles.  I can't provide you an example if I have no idea what you're doing.

 

And yes, I edited my previous post because I thought it was a little harsh sounding.

Link to comment
Share on other sites

I don't have an example. I have just been thinking the best way to write future code.

 

Do you have an example where this was a problem for you? Maybe you've seen something in the past which you had to sort out? I just can't see it being a major problem (but want to listen to how it can).

 

I've been looking at the link from creata.physics which is interesting. Thanks for that. I don't have a problem with referencing other classes, I actually pass those in on construct, but this may help with my config values. Thanks.

 

Thanks again to both for replying. I'm still reading and looking into this.

Link to comment
Share on other sites

Just wrap you site-wide configuration settings in a configuration object (or an array) and pass that to your company object using Dependency Injection. Something like:

 

class Company {
  private $_name;
  
  public function __construct($cfg) {
    $this->_name = $cfg['company_name'];
  }
}

 

An example DI container can be found here:

https://github.com/fabpot/Pimple

 

A use-case can be found here:

https://github.com/fabpot/Silex/blob/master/src/Silex/Application.php

Link to comment
Share on other sites

Let's say, for example, you create your global constants:

 

define('USERNAME', 'Joe Schmo');
define('COMPANY_NAME', 'Sprockets, Inc.');
define('EMAIL', 'joe.schmo@sprocketsinc.com');

 

Then you create your objects based on that:

class Mailer {
    public function sendMail() {
        $sendTo = USERNAME . "<" . EMAIL . ">";
        $company = COMPANY_NAME;

       $text = "Lorem ipsum dolor sit amet"
    }
}

 

Well now your Mailer class is necessarily tied to the purpose of your application, and can't be reused for other applications, much less for the purpose of sending emails to multiple people at once.

 

As ignace pointed out in his example, your class should be environment-agnostic...  That means it should not care what's happening outside its definition.  This will allow you to extend on a library of code that you can reuse for many applications going forward.

Link to comment
Share on other sites

smoseley,

 

Your example is pretty bad. I wouldn't tie something like one email to an email class. The idea would be to tie something that never changes. To tie one email address to an email class would be only the work of an idiot. Ignace shows a Company class and a company name. I know his method is different to mine, but tying a company name to a company class would be something I would possibly do with my method. I wouldn't tie one email address to an email class. Yes, if another company, or site, used my Company class they would need to change the settings at the top of the class, but there's always going to be some changes needed. I understand that would make my Company class not environmentally-agnostic though.

 

I completely understand that the class should be usable on any site, but my method would allow that, it just needs someone to change variables at the top of the class when they get hold of it. Just like this class which you hard-coded constants in: http://www.phpfreaks.com/forums/index.php?topic=359554.0 - your example isn't environmentally-agnostic in that case.

 

I do like ignace's idea though, that seems a decent way of ease and best practice combined. I will look into this.

 

Thanks again to everyone for the advice.

Link to comment
Share on other sites

Yes, if another company, or site, used my Company class they would need to change the settings at the top of the class, but there's always going to be some changes needed. I understand that would make my Company class not environmentally-agnostic though.

Explain what you mean by "Company" class.  Is that a DAL class?  A DAO should never contain data in global context.  Its data should be contained within a single instance.  Simple scenario explaining one reason why (of many): what happens when you need to reference TWO companies? 

 

Anyhow, one should never be creating a DAL, it's a waste of time, considering the advent of the dynamic DAL / ORM - Active Record, et. al.  Ignance's post is a simple abstract representation of what many ORMs do automatically - model your database as objects for you.

 

I really think you need to study OO in depth and understand the basic principles before attempting to redesign it.

 

You're attempting to improve well-established paradigms without even knowing how they work.

Link to comment
Share on other sites

I was simply using that as an example, as that was the name Ignace used in his example. Very strange.

 

The point being, my code was to be used to grab values that will always stay the same for the site (name of the company who owns the site), not one email address for an entire email class.

 

Every single one of my posts on this topic involves me asking for help and shows I am listening to advice. I am reading documentation. Nearly every one of your posts keeps telling me to read docs, which is exactly what I am doing. You also haven't yet given an example that relates to my method.

 

Look at the last two sentences in my last post, and look at your response. You sound angry for some reason. I've just said I'm looking at Ignace's links. Why would you be angry? I'm listening. You seem to have taken this topic personal. I'm looking at Ignace's links and considering what to do. Why get angry? Why go on about creating a Company class. Take that up with Ignace, he sounds like he knows OOP inside out.

 

Your example of the email class was silly. To pick up on the 'Company' class thing was silly. I stated from the start that my question was general OOP.

 

What about you hard-coding API key information here: http://www.phpfreaks.com/forums/index.php?topic=359554.0 - what do you think of that? That goes against OO principles, doesn't it.

 

I'm looking into Ignace's links and thank everyone on here for helping me improve on my OO skills. Thanks again.

Link to comment
Share on other sites

I completely understand that the class should be usable on any site, but my method would allow that, it just needs someone to change variables at the top of the class when they get hold of it.

 

Why do you insist on using constants? Why not a configuration file, like the one below:

 

<?php

return array(
  'company_name' => 'My Company Name',
  'company_slogan' => 'My Company Slogan',
  ..
);

 

Through an admin (or installer) interface you can now change these values and write them back to the file:

 

// example, don't copy and use in your production scripts
file_put_contents('config.php', '<?php' . PHP_EOL . PHP_EOL . 'return array(' . PHP_EOL . toArrayKeyValNotation($config) . PHP_EOL . ');');

 

This has the advantage of everything being in one place and it's easier to modify and write back because there is no function for example that gives you only your own declared constants, if you wanted to write these to a file, for example.

Link to comment
Share on other sites

@smoseley - All, that I've read, of your OO explanations seem to be very narrow minded. You claim that OOP is only "valid" if each object is entirely independent of other objects. I don't agree with that.

 

Whilst I agree with many of the arguments you've put forward such as passing variables to the constructor I don't believe every single class needs to be independent of other classes. If that is the case how do classes ever interact? You must at some point extend requests to other classes from within classes thus making one class dependant on another, this is software engineering and a software architecture. A bespoke application which has a specific job can easily implement this using multiple classes. In the case of MVC the V & C can be one and defined as the Controller making the Controller dependant on the Model. That said, MVC can be interpreted differently from one developer to the next.

 

Moreover, as already described by creata.physics you can use a registry to maintain a single instance of objects (Singleton pattern) which contradicts some of what you say.

Link to comment
Share on other sites

Every single one of my posts on this topic involves me asking for help and shows I am listening to advice. I am reading documentation. Nearly every one of your posts keeps telling me to read docs, which is exactly what I am doing. You also haven't yet given an example that relates to my method.

Well, your question was ver abstract.  Impossible to give an example of "Good OOP" in a forum context - it's 100s of pages of content.

 

Given your "company name" concept.  If it's the company that owns the site, put it in a Singleton "Config" class that's available globally, or even available statically.  That way, your values aren't necessarily hard-coded into your application, and can be set by various means, such as DB, XML file,etc.  You would retrieve values like this:

 

$x = Config::read('company_name');

$x = Config::read('db_host');

$x = Config::read('

 

 

Look at the last two sentences in my last post, and look at your response. You sound angry for some reason. I've just said I'm looking at Ignace's links. Why would you be angry? I'm listening. You seem to have taken this topic personal. I'm looking at Ignace's links and considering what to do. Why get angry? Why go on about creating a Company class. Take that up with Ignace, he sounds like he knows OOP inside out.

I'm not angry, just frustrated, because you are persisting a discussion that isn't going anywhere.  You're not going to learn anything here, except that you should read a good book on OOP.  A forum can only teach you so much.

 

What about you hard-coding API key information here: http://www.phpfreaks.com/forums/index.php?topic=359554.0 - what do you think of that? That goes against OO principles, doesn't it.

[/quote[

I didn't write that code, I only offered to help make it work.  As for the API_URL being hardcoded as a class constant, that is proper OO.  Every API I have written has had the url defined as a constant.  The key and cached-file-dir, on the other hand, should be constructor variables, as they're specific to one person's instance of the API.  If you note further down in that thread, I make that suggestion to the OP.

 

Link to comment
Share on other sites

@smoseley - All, that I've read, of your OO explanations seem to be very narrow minded. You claim that OOP is only "valid" if each object is entirely independent of other objects. I don't agree with that.

 

Whilst I agree with many of the arguments you've put forward such as passing variables to the constructor I don't believe every single class needs to be independent of other classes. If that is the case how do classes ever interact? You must at some point extend requests to other classes from within classes thus making one class dependant on another, this is software engineering and a software architecture. A bespoke application which has a specific job can easily implement this using multiple classes. In the case of MVC the V & C can be one and defined as the Controller making the Controller dependant on the Model. That said, MVC can be interpreted differently from one developer to the next.

 

Moreover, as already described by creata.physics you can use a registry to maintain a single instance of objects (Singleton pattern) which contradicts some of what you say.

I don't recall saying good OO Classes have to be completely independent.  I said portable, reusable, and environment-agnostic.  Obvious exceptions to the rule are DAL and BLL classes.

 

Reusable - When you design your classes, you break them down as much as possible into elements that can be reused without modification.  Anything that becomes a reused pattern in your business logic should be rethought as a pluggable component for your system.

 

Portable - When you design classes, you design them with as few interface dependencies as possible.  Further, you ensure that any dependencies are on other reusable classes, so that your code works as a library, or framework.

 

Environment Agnostic - As much as possible, your reusable classes should not be dependent upon global values to function. 

 

 

A class should be interacted with via its interface.  That doesn't mean other classes shouldn't reference it.  It means they should do so via its established pattern.  So if I have class A, and class B contains an A, that's fine.  B now has a dependency on A.  But neither should ever have to be modified to work.  Look at how Java works - many libs have deep dependencies upon other libs.  Many, many levels deep.  But when you grab a .jar, it works.  You don't have to do anything to it, or change any settings.  It works as is.  That's good OO. 

Link to comment
Share on other sites

Just thought of a great example to explain what I'm saying, using the static Config class I showed above...

 

Let's say you have this Config class with DB connection information:

 

Config::write('db_host', 'myhost');
Config::write('db_user', 'myuser');
Config::write('db_pass', 'mypass');

 

And you want to create a DatabaseConnection class.

 

There are two ways you can do this:

 

1) Make the DC class use the Config params to establish its connection, or

2) Make the DC class take host, user, and pass, as parameters.

 

I say that the DC class should be environment agnostic.  It should take host, user, and pass, as params, and your business logic should get a connection using the config params, like so:

 

$host = Config::read('db_host');
$user = Config::read('db_user');
$pass = Config::read('db_pass');
$name = Config::read('db_name');
$dc = DatabaseConnection::getInstance($host, $user, $pass, $name);

 

DatabaseConnection would in this case be a Singleton, and would support multiple connection instances (for a multi-database application).

 

Now, since you're going to be using this snippet a lot, you should build some BL around it:

function getConnection($connection_name) {
    $host = Config::read("{$connection_name}.db_host");
    $user = Config::read("{$connection_name}.db_user");
    $pass = Config::read("{$connection_name}.db_pass");
    $name = Config::read("{$connection_name}.db_name");
    return DatabaseConnection::getInstance($host, $user, $pass, $name);
}

 

Of course, that function could be part of a class somewhere, but that's up to you given your BL.

 

Now, you've got a way of establishing connections to multiple databases for your application, and at the same time, your DC component can be reused independently elsewhere, because it's not dependent upon your environment variables.

 

Maybe you can establish another class that has Config and DC dependencies:

 

class ConnectionManager {

private static $increment = 1;

public static function addConnection($host, $user, $pass, $name, $connection_name=null) {
    if (is_null($connection_name) {
        $connection_name = self::$increment;
        self::$increment++;
    } 
    Config::write("{$connection_name}.db_host", $host);
    Config::write("{$connection_name}.db_user", $user);
    Config::write("{$connection_name}.db_pass", $pass);
    Config::write("{$connection_name}.db_name", $name);
}

public static function getConnection($connection_name) {
    $host = Config::read("{$connection_name}.db_host");
    $user = Config::read("{$connection_name}.db_user");
    $pass = Config::read("{$connection_name}.db_pass");
    $name = Config::read("{$connection_name}.db_name");
    return DatabaseConnection::getInstance($host, $user, $pass, $name);
}

}

 

Of course, beter to isolate your DB Config to that class than to use a global dependency.... so something like:

 

class ConnectionManager {

private static $increment = 1;
private static $config = array();

public static function addConnection($host, $user, $pass, $name, $connection_name=null) {
    if (is_null($connection_name) {
        $connection_name = self::$increment;
        self::$increment++;
    } 
    self::$config[$connection_name] = array(
        "db_host" => $host,
        "db_user" => $user,
        "db_pass" => $pass,
        "db_name" => $name);
}

public static function getConnection($connection_name) {
    $info = self::$config[$connection_name];
    return DatabaseConnection::getInstance($info['host'], $info['user'], $info['pass'], $info['name']);
}

}

 

Now you have a connection class connection manager class that are independent of system variables.

 

In your business logic, you can do this:

 

// Do this once:
$host = Config::read("db_host");
$user = Config::read("db_user");
$pass = Config::read("db_pass");
$name = Config::read("db_name");
return ConnectionManager::addConnection($host, $user, $pass, $name, "main");


// Then any time you need a connection:
$conn = CommectionManager::getConnection("main");

Link to comment
Share on other sites

Now I fully understand where your coming from. I initially thought you were obsessed with ensuring EACH and EVERY class was COMPLETELY and TRULY independent of everything else and never made a single call to anything else; kinda scared me a little haha.

Link to comment
Share on other sites

And here's a version of your connection manager that would persist connections for future reference:

 

class ConnectionManager {

private static $increment = 1;
private static $connections = array();

public static function addConnection($host, $user, $pass, $name, $connection_name=null) {
    if (is_null($connection_name) {
        $connection_name = self::$increment;
        self::$increment++;
    } 
    self::$connections[$connection_name] = array(
        "db_host" => $host,
        "db_user" => $user,
        "db_pass" => $pass,
        "db_name" => $name,
        "handler" => null);
}

public static function getConnection($connection_name) {
    $conn =& self::$connections[$connection_name];
    if (is_null($conn['handler']))
        $conn['handler'] = DatabaseConnection::getInstance($conn['host'], $conn['user'], $conn['pass'], $conn['name']);
    return $conn['handler'];
}

}

 

And note: no dependencies on global config data.  Completely autonomous class that can be reused elsewhere with the same iterface.

Link to comment
Share on other sites

Now I fully understand where your coming from. I initially thought you were obsessed with ensuring EACH and EVERY class was COMPLETELY and TRULY independent of everything else and never made a single call to anything else; kinda scared me a little haha.

LOL. Well, glad we're on the same page now! :D

Link to comment
Share on other sites

DatabaseConnection would in this case be a Singleton, and would support multiple connection instances (for a multi-database application).

 

The correct term would then be Multiton not Singleton. IMO you should avoid static methods when not necessarily needed, they create a not so easy to replace dependency on your code.

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.