0

I have a namespace-based code, with a "Model" folder. I call my models statically everywhere in my code:

\Myapp\Model\PersonModel::doSomething()

Now, I would like to distribute my application in several countries, and be able to override some features of PersonModel for some countries (and add models for some countries, and so on).

I will have:

\MyApp\Model\France\PersonModel::doSomething()

I would like to be able to use the specialized version, with that in mind:

  • Not modifying too much code
  • Keep my IDE code completion

One solution would be to specialize every controller for every country, and use fully qualified names everywhere, but I'm not sure it is realistic (time consuming and maybe even not functional).

Another solution would be to override every model, and add a function somewhere that would give me the name of the fully qualified class (for the current country), but the code would become ugly, and I would lose code completion.

A third solution would be to have a repository for every country, and try to modify everything so that the core code is in a Git submodule and everything else is specialized classes... It seems to be the best solution to me but it seems like a Titan work, and I would prefer to have all countries inside the same repository to keep things under control.

I have really no other idea. I'm sure I'm not the only one with that problem, but I searched and found nothing. Please, tell me there is a magic feature for namespaces that I wasn't aware about, or something? :)


Edit: this is partially solved

I am now using a custom autoloader, which will load the proper country-specific class. But all country-specific classes will share the same namespace (which will work because we are using only 1 country at a given time). However, I lose code completion, but it's a tradeoff I'm ok with. If someone has a solution that would also allow to keep code completion, please feel free to answer!

halfer
  • 19,824
  • 17
  • 99
  • 186
Lideln Kyoku
  • 952
  • 9
  • 20
  • I'm trying to pin this down to a concrete question. Is the issue about how to best make a `\MyApp\Model\France\PersonModel` call? If so, you could use `call_user_func`, but that removes your autocompletion. However it would be better for all cases of `PersonModel` to call things in an instance context, and then the variable `$personModel` can come from a factory that is labelled (with doc blocks) as returning a `PersonModelInterface`. Static calls are the problem here - they are harder to generalise. – halfer Jan 11 '15 at 13:18
  • A controller per country, or a repo per country, are usually (really) wrong solutions. Generalise as much as you can. – halfer Jan 11 '15 at 13:20
  • Actually I found a solution by myself: using a custom autoloader, that will look for the custom class in the current country folder, but the namespace for all countries will be the same, which allows me to use fully qualified classes. Although, I am losing code completion... But it's a tradeoff I'm ok with, comparing to the cost of creating a repo for each country! – Lideln Kyoku Jan 11 '15 at 13:53
  • Alright, would you consider adding an answer, and accepting it by clicking the tick mark? Thanks. – halfer Jan 11 '15 at 13:54
  • Ah, you just overwrote my edits - fixed. Bear in mind we ask posters here not to add `[solved]` tags to their titles, and whilst status updates are OK, answers proper belong in the answer box below. Yours is fine - I've just moved it to after the question, since it makes no sense to read about a partial solution before discovering what the problem is. – halfer Jan 11 '15 at 13:58
  • 1
    Sorry @halfer, I'm not very used to the proper manners, I was still thinking that one should add "[solved]" in the question title! I added a solution and will accept it, unless someone offers a more elegant solution. Thank you @halfer! – Lideln Kyoku Jan 11 '15 at 14:02
  • No apology needed. The title-amend approach is indeed common in forums where threads cannot be explicitly marked as resolved, but since we have that feature here, there is no need to spoil the title. Once you are happy with the solution, you may optionally click the tick mark below - and you may accept your own answer if you wish. – halfer Jan 11 '15 at 14:04
  • 1
    I tried but I do not seem to be allowed to do it until 2 days from now. I will keep the tab open to remind me to do it (unless someone has a more elegant solution of course, which I would really love, because losing code completion is not pretty) – Lideln Kyoku Jan 11 '15 at 14:10

2 Answers2

1

Ok I found a globally satisfying solution:

All country-specific classes will share the same namespace (for example "Myapp\Model\Custom*"), which will not cause conflicts because there is only 1 country at a time.

A custom autoloader will load the proper class/file based on the current country.

Inconvenients of this solution are:

  • Not possible to "use" multiple countries at the same time (which is not the goal, so it's ok)
  • Losing code completion :(

Advantages are:

  • Much less time consuming and hair-pulling than creating a repo for each country
  • Globally clean code and code structure

Please feel free to post a more elegant solution, I will be glad to read it (and accept it)!

Lideln Kyoku
  • 952
  • 9
  • 20
0

Another idea, following on from my thoughts in the comments. Move this:

\Myapp\Model\PersonModel

to here (random location, as I don't know your default territory):

\Myapp\Model\Germany\PersonModel

You'll now have two classes with a country in the namespace, and of course you can add more. You can now add a new generalised class, thus:

namespace \Myapp\Model;

/*
 * Document your methods here using @method, so you get autocompletion
 */
class PersonModel
{
    public function __callStatic($name, $args)
    {
        // Convert the call to an appropriate static call on the correct class.
        // The first parameter is always the country name, which you can examine
        // and then remove from the list. Throw an exception if $args[0] is
        // empty.
    }
}

You can then do this:

\MyApp\Model\PersonModel::doSomething('France', $args);

The use of @method (see here) will ensure you retain auto-completion, and you can learn more about the call magic methods here.

Of course, make sure your language-specific classes refer to a parent class, so you are not duplicating large blobs of code. Some methods may also not care about language, in which case they can go in \Myapp\Model\PersonModel, with the benefit that they do not need manual phpdoc entries.

halfer
  • 19,824
  • 17
  • 99
  • 186
  • Hi Halfer. This might work indeed, however even though I could use multiple countries at once with your solution, I do not feel comfortable because I would still lose completion as long as I don't have the country in the namespace (or I would need to use the @method for each specific method of each country, and in addition it would be "too permissive" and maybe do me wrong). Your solution is good so I will accept it, but it does not have my personal preference as I'm more comfortable with the one above. Thank you anyway Halfer! – Lideln Kyoku Jan 17 '15 at 14:43
  • @Lideln: I suspect if you can use instance-level objects rather than static references, this would be easier to work with. That was basically the thrust of my first comment, I think. Still, if you are happy with your solution, that's good! – halfer Jan 17 '15 at 14:51