I've been creating WordPress plug-ins for some months now and I have developed a set of "base classes" (most are abstract classes but not all of them) that I use with the plug-ins (some are used with all plug-ins, others are used when a plug-in needs the specific thing the class provides - maybe an admin screen or a button on the visual editor or whatever).
This, of course, works great BUT if I update one or more base classes to add some new functionality then if someone has more than one of my plug-ins installed it is very possible for one or more of those plug-ins to crash.
This is because if a plug-in using an older version of the updated class(es) loads earlier then it "reserves" that name and the others that require the updated class don't have the functionality/fix/etc.
I've tried "nice" solutions but the only thing I can get to work reliably is to change the actual base class names for each plug-in, which is far from ideal.
Namespaces obviously work but can't be used because so many popular hosting companies are using 5.2x versions of PHP (probably why WordPress doesn't require 5.3 either)
I tried to dynamically create the class names and to use class factories but they didn't work or didn't overcome the need to rename all the base classes. I even tried (although I didn't expect it to work but you never know with PHP):
class childClassName extends $parentClassName
This situation can't be that unusual (do people really rewrite everything from scratch everytime and/or use distinct class names) but I can't find a solution and I can't think of an elegant one. It isn't even a WordPress issue, multiple applications using common base classes within a company would have the same problem.
I also can't just force everyone to upgrade all their plug-ins every time because it may crash before they can and I'm not sure it's reasonable for me require that of them or even that I can personally update and re-test, every time, every one of the what's now almost 10 plug-ins with more in the future.
My currect best solution is to use factories to create all base classes and to put tokens in my base classes that I can do a global search and replace on so that the class names are unique for each plug-in; note the replaceable tokens alone would work.
Is there some PHP code that will allow me to achieve a simple, easy solution to this common class name conflict issue?
========================================
It doesn't look like anyone knows how to do this in PHP, maybe it can't be done, so I went with the way I mentioned above.
I thought I'd put it here because this is what I ended up with that works well, if not what I had hoped, and maybe it can help others:
I added tokens to the base class names, for example:
class someBaseClass[TokenNameGoesHere] (I used [ReplaceWithVersionNumber])
also
class someChildClass extends someBaseClass[TokenNameGoesHere]
I created a method called createNewClass and passed the class name (as a string) and any parameters to pass to the new class (in an array).
I also passed information like the token value being used in the application (version number in my case) and the namespace (to be used instead of the token value if I can use PHP 5.3 and above).
I then used reflection to create the class. While reflection is obviously slower I found that I didn't have that many times when I needed to use createNewClass so the trade off of ease of remembering and increased clarity won out.
$reflectionOfClass = new \ReflectionClass($desiredClassName);
$newObject = $reflectionOfClass->newInstanceArgs($ParametersToPass);
Where $desiredClassName is built from the class name and the other parameters: version and namespace in my case.
Then, everywhere I wanted to create a base class object I'd use:
$myNewObject = $this->createNewObject("someBaseClass", other parameters);
When I copied the class library over for a new application I just did a massive search and replace of the token with what I wanted to replace it with in all the files. It could be version number or even an empty string if I don't have to worry about clashing with other versions of my class library like I do with WordPress plug-ins.
It was pretty easy to do and works great although going back through and inserting the tokens in was tedious.
It all could, of course, be done with just tokens but I didn't want to have to remember to do put suffixes on the names and which token name I was using and whatnot.
I hope that helps someone and if anyone has a better way (which to me means less code modification and more code clarity) I'd love to hear about it.
Not sure if you've found a proper solution to your question yet. I'm dealing with the same problem and my approach (not perfect, but does what I need) would be this one:
Create a class alias of your reusable class from a variable, like this:
$aliasclass = $really_unique_prefix."_Prefix_MyPlugin_Class";
class_alias("Prefix_MyPlugin_Class", $aliasclass);
new $aliasclass();
Hope it helps!
Yes namespace is perfect solution.
If php could have provided way to unload / load classes problem could have be easily addressed. But again you need to relink / instantiate your other depended active plugins so as to used object of new classes, this involves deactivation and activation of existing plugins.
With same thought I can think of this solution, you very well know you have 1,2,3 .. n plugins using X class. 1 & 2 plugins are using older version of X and you are about to install / update plugin 3 with newer version of X. Deactivate all your plugins & first activate plugin (3) which has your latest features/version of class X, later activate all others.
$all_plugins = array(
ABSPATH.'wp-content/plugins/plugin_1/index.php',
ABSPATH.'wp-content/plugins/plugin_2/index.php'
......
ABSPATH.'wp-content/plugins/plugin_n/index.php'
);
if (class_exists('X') && X::version < currentXVersion) {
deactivate_plugins($all_plugins);
$plugin_3_lastest_class_X = ABSPATH.'wp-content/plugins/plugin_3/index.php';
array_unshift($all_plugins, $plugin_3_lastest_class_X);
activate_plugins($all_plugins);
}
*provided if its ok to deactivate / activate your plugins & you don't modify signature of existing functions of class X, you can add new functions though.
Related
In Silverstripe 3, eveything was autoloaded on demand. That meant that one could know the class and method that they wanted to use. Example:
Debug::dump('dump message');
If I'm understanding the concepts of SS4 correctly, one needs to import the class to the file that you would like to use the method (or property). In the above example, one would need to do something as follows at the top of the file
use /name/space/to/Debug
Debug::dump('dump message');
Is that understanding correct? If so, my real question is how does a developer effectively know the precise location of everything in core? Are they expected to know exactly where in system these core files reside on top of their names and the methods you wish to use? Are there tools or methods to assist in this?
First, just a clarification -- what you're talking about is not autoloading, it's more like aliasing -- but the two are related.
Because the Debug class is no longer in the global namespace, its name is much more verbose (SilverStripe\Dev\Debug). This is known as a fully qualified class name, or "FQCN" for short. There's nothing short, however, about that new name. It's tedious to type, and if the class ever changes its name, you have a lot of updates to make.
To that end, you can use the use statement to "import" that class into the local scope of your file under an alias. By default, that alias is just the trailing part of the FQCN (Debug), but you're also allowed to use any custom alias you like. (use SilverStripe\Dev\Debug as MyDebugger).
SS4 now uses PSR-4 autoloading, for which namespacing is a critical piece. In very short terms, it dictates that the directory structure must match up with the FQCN, e.g. /framework/src/Dev/Debug.php. This makes the autoloading deterministic and predictable.
As for tooling, using a fully powered IDE like PHPStorm is invaluable for doing dev work in a framework with namespaced classes (which is far more the rule than the exception these days). A good IDE, like PHPStorm, will not only autocomplete as you inject a classname, but will also add the use statement for you, among many, many other wonderful time-saving features.
For a more manual approach, api.silverstripe.org is a good place to look up class mappings.
I'm building a website for a client, and he wishes to have a calculator for clients to calculate their monthly premium when they want to lease a car. This calculator appears in three different places in the website, so I decided to build it once, then include() the file into the main file. For instance:
case 'calculator':
case 'bereken-leasebedrag':
include('calculator.php');
break;
However, I have several classes defined in the main file:
use site\database\Vote;
use site\database\Vehicle;
use site\database\VehicleType;
use site\utils\ApiConnect;
use site\utils\Calculator;
And some more.
Some of these classes are used by the calculator file, such as Calculator and Vehicle and a few others. These classes are defined in the main file, but are not transferred to the calculator file, whereas variables like $_SESSION, $_POST and even my own defined variables like $car and $calc are available in the included file.
If I don't re-declare my classes at the beginning of calculator.php, it throws a fatal error in the page, saying it can't find, for instance, the class ApiConnect.
Am I doing something wrong here? Logically speaking, included files should inherit pretty much everything, and it seems counter-productive to have to specify every class needed for the included files.
I'm terrible at explaining things, so if I need to elaborate on something, I'd be more than happy to.
Thanks to deceze, I learned that use is used for aliasing, something I didn't derive from my Google searches.
Apparently the autoloader automatically includes all the classes we have, we just manually apply aliasing for every class. Which is kind of weird, but there you go.
Long story short, I had to either apply aliasing in the included file so that I could just say new Calculator(), or remove the aliasing and change every new {CLASS}() to new \{PATH}\{TO}\{CLASS}. I personally went with the first option, but if you have an autoloader and are running into thesame problem, at least you have a choice.
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!
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)!
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.
As far as I can tell, the only reason we have namespacing in PHP is to fix the problem of classes (+ functions & constants) clashing with others classes of the same name.
The problem is that most frameworks setup their autoload and filesystem hierarchy to reflect the names of the classes. And no-one actually require()s or include()s files anymore.
So how does namespacing help this any? Either the class is loaded based off of it's name:
new Zend_Db_Table_Rowset_Abstract;
or off it's namespace
new Zend\Db\Table\Rowset\Abstract;
Either way I am stuck with only being able to create one class with this name.
/var/www/public/Zend/Db/Table/Rowset/Abstract.php
UPDATE
I'm not sure I'm getting the point across.
Even if I created two files with the same class Zend\Db\Table\Rowset\Abstract in them I still couldn't use them together since they both claim the same namespace. I would have to change their namespace names which is what we already do!
This leaves me to belive that the only use for namespaces is function names. Now we can finally have three functions all named the same thing!
Or wait, I forgot you can't do that either since each requires the namespace prefix!
a\myfunction();
b\myfunction();
c\myfunction();
Taking ircmaxell's example:
$model = new \Application\Model\User;
$controller = new \Application\Controller\User;
How is that any different than without?
$model = new Application_Model_User;
$controller = new Application_Controller_User;
This is also a neat sounding feature - but what does it truly do for us?
use \Application\Model\User as UserModel;
use \Application\Controller\User as UserController;
$foo = new UserModel;
$bar = new UserController;
Now you cannot have a class named 'UserModel' since you have a namespace setting for that term. You also still cannot have two classes named under the same alias.
I guess the good thing is that you can rename the long Zend_Db_Table_Rowset_Abstract
use Zend_Db_Table_Rowset_Abstract as RowAbstract;
leading to developer confusion about where the non-existent class "RowAbstract" is defined and coming from in the system.
My impression is that namespaces are used in a cargo cult programming fashion. Because it's new, it gets used like crazy. Deeply nested namespaces, like in Doctrine2, add no further protection against name conflicts. And it's very obvious that \nested\name\spaces are just used to achieve a 1:1 mapping to directory/file names. Clearly a code smell.
But I suppose this phenomenon is also caused by trying to mimick Java module names in PHP. And furthermore the backslash syntax doesn't convey a sensible semantic as in other languages.
Anyway, the ability to rename classes while importing namespaces is a big bon. That's useful when it actually comes to mixing conflicting definitions. And the namespace syntax can simply be added, once an actual name conflict arises. (I see no need to implement namespaces right away, when name conflicts are that rare and for most projects a purely fictional problem.)
If you do it only when required, the awkward namespace syntax never even has to rear its ugly head. If there is only one namespace level to import, you can just use namespace1\Class as LocalName and don't convolute the application with any namespace1\Class syntax. (Still it's a better idea not to use overly generic class names in namespaces.)
Actually, you can create more than one class with the same name
Suppose you have 2 classes:
\Application\Controller\User
and
\Application\Model\User
You couldn't import both into the same file without aliasing one, but you still can define both the same way:
$model = new \Application\Model\User;
$controller = new \Application\Controller\User;
Plus you can import and alias:
use \Application\Model\User as UserModel;
use \Application\Controller\User as UserController;
$foo = new UserModel;
$bar = new UserController;
So it really is quite powerful, as it does let you name your classes however you want (and reference them by arbitrary names inside your code). The only rules are reserved keywords... See: http://www.php.net/manual/en/language.namespaces.importing.php
The point you seem to be missing is that namespace names are composable. I.e. you can do:
use Zend\Db\Table as Table;
$a = new Table\Rowset();
$b = new Table\Fields();
etc. I.e. it allows you to define your context (or set of contexts) and then refer to it. Of course, you can reduce it to just one name, but you don't have to. That btw also helps with generic class names - Fields may be too generic, but Table\Fields less to.
Another thing is if you get sick of Zend tables and want to write your own Db table classes, you change the above to:
use My\Own\Table as Table;
and all the code now uses your set of classes.
Also, you don't actually define the class by the long name. What you do is:
namespace Zend\Db\Table;
class Rowset exteds AbstractRowset {
function doStuff() {
$this->fields = new Fields();
if($this->fields->areBroken()) {
throw Exception("Alas, fields are broken!");
}
}
}
Note that here we have used 4 classes from Zend\Db\Table space, but never once had to refer to them by a long name. Of course, in real life it probably won't be as easy :), but the idea is that code - especially library code - tends to be localized - i.e. if you are in Db part of the library, chances are you are using DB-related classes much more than LDAP or PDF classes. Namespacing allows you to exploit this locality by using shorter names.
You are right that namespacing doesn't help with loading - since class should be still loaded using the full name. But once you've got your loader working, you can use nice aliases instead of ugly full names - just as in the filesystem you can use nice relative paths and symlinks instead of ugly full path.
In your example Zend framework, you can throw namespace Zend; at the top of each class file, remove the Zend_ prefix from all of your classes and functions, and you (mostly) don't need to worry about name collisions ever again. Your Zend_Date class can be renamed as just Date without interfering with the built-in date classes. Meanwhile, users of your framework can write Zend\Date instead of Zend_Date which isn't any longer to type, but they now have several other options for accessing the class more easily.
It's probably not worth it to use namespaces unless you have a big project with multiple developers. I would even argue namespaces are overrated.
For example, in Java (since few people use namespaces yet in PHP I couldn't find any similar examples). These are the choices that come up for List in my IDE (Eclipse):
java.util.List
com.ibm.toad.utils.Strings.List
com.ibm.ws.objectManager.List
com.ibm.ws.webcontainer.util.List
java.awt.List
In this case, I don't really see why they couldn't have just kept java.util.List as the only List, and for example renamed java.awt.List to ScrollingList, which actually describes what it is, makes it more obvious that it is a GUI element, and avoids the collision. I would rather type out a longer and more descriptive class name than have to deal with that.
As for one of the above posters, if everyone in your team is making a class called Database perhaps you need to do some design discussion and use only one Database class instead of shoving each person's personal duplicate into a namespace.
Are you really trying to create a new class called Zend_Db_Table_Rowset_Abstract?
The more likely scenario is that you have a local class called something common like Date, Project, User, or something similar or you have a framework that already has those classes. By having namespaces, there shouldn't be any collisions.
In web2project, we've just added namespace support in v2.0 (but we don't require PHP 5.3) because our classes - like Date, DBQuery, Mail, and a few others - collided with things pretty easily. While we don't have an intention to add an external framework to the system, someone else could pretty easily if they wanted.
Are there less verbose ways of solving the problem? Potentially.. but this worked in the Java world with their piles of libraries, so it's not all new space.
Perhaps if you broaden your view a little :)
"As far as I can tell, the only reason we have namespacing in PHP is to fix the problem of classes (+ functions & constants) clashing with others classes of the same name." - yes this is a huge issue which has caused problems for many years in every language that doesn't have namespaces - everybody makes a class Database, Date, URL etc and this fixes it :)
"The problem is that most frameworks setup their autoload and filesystem hierarchy to reflect the names of the classes. And no-one actually require()s or include()s files anymore." - well actually they do, often, just because some common frameworks have had to come up with practises to work around a lack of namespaces, doesn't mean those work arounds or hacks should null and void the 'real' fix to the issue for all :)
follow?
I realize that you cannot "undeclare" or "redeclare" a class in PHP, nor can I use extension methods as in C# > 3.0. So, here's my situation.
I'm working with someone else's code base. They must have been using a slightly older version of PHP where a DateTime class did not exist, so they included an open source library that adds Time, Date, and DateTime classes. Well, I go to deploy this to the client's server, and this class is already defined. To resolve the crash it causes, I add a "if class is not defined" check, however it seems that (as you'd expect) the api of the classes are not exactly the same, and somewhere in the code it may or may not be using undefined methods of the DateTime class included with the version of the language being used.
Are there any ways to unload a class, or any creative ways you can think of to get around this. I'd prefer not to have to search all files for all references and maybe rename the class. That sounds messy, but I guess it may be my only option. I'd much prefer a fix that happens in one place and one place only.
Thanks.
You may want to enclose the class definition inside a Namespace. Then you declare a "using namespace xxx" ( I don't remember the exact PHP syntax ) inside every file that uses the current DateTime class.
This is not exactly a "one place and one place only" solution, but, at least you don't have to replace every reference to the class, just reference the namespace at the beginning of the files.
Hope this helps.
Some good ideas here...
Using a Namespace - suggested by Petruza - is probably the best way to handle it... but it's PHP 5.3 only.
Alternatively, Robert's suggestion of refactoring the code is a reasonable idea but will likely take a good deal of time and effort. Since this will have to be done eventually, you might want to do that.
Alternatively, if getting the system online is the top priority and you can refactor it afterwards, you might be able to rename the class and all references to it. By adding a prefix - DateTime to company_DateTime - you're namespacing it the old way and your classes should coexist without much difficulty. Of course, if you have to update the supporting libraries any time soon, this is going to make life more difficult, so don't blindly jump into this one...
Perhaps this can help: Unload dynamic class in PHP.
I would attempt to migrate the code base to the more recent version of PHP. If you are going to use the code in production on the new PHP version, then you need to test it on that version anyway. If you do not want to invest the efforts to update and test the code base you should probably require the older version to be installed on the target system. Otherwise there is no way you can guarantee that the code you have works correctly.
And with proper tools searching through a ton of files and identifying all occurrences of those classes isn't too hard.
Well, this solution is really ugly and it should be used only as a last resort. Example, in Opencart the Category classes of "admin" and "catalog" both have the same class name, and if you want to use both within the same code piece, that is a really difficult task to do. So a workaround is to just rename the class which you are going to include. Something like this:
$file = file_get_contents("path/to/file.php");
$file = str_replace("OriginalClassName", "ModifiedClassName", $file);
// must remove <?php tag from the beginning
eval(substr($file, 5));
then instead of new OriginalClassName() use the new class name new ModifiedClassName().