Using Interfaces in an custom OO PHP framework
I'm working on a fairly simple OO PHP framework (not really important in this case I guess..), with the following basic structure:
application/
classes/
controllers/
includes/
models/
views/
classes/
includes/
I know that using interfaces rather than hardcoding classes is good practice for OOP, but I'm not sure what best practice is when it comes to the actual location/structure of interface directories and files.
Should interfaces be separated into multiple files under a directory:
interfaces/
iDatabase.php
iRouter.php
or should they all be pu开发者_如何学运维t in a single file since they're not that large:
includes/
interfaces.php (with all Interfaces inside)
With the first option I could use an autoloader to load interfaces and not have to load every file when not all may be used, while with the second option they would all be loaded initially but it would save me having to load multiple files each time.
What are your thoughts? Am I totally looking at this the wrong way (I tend to do this with most of my problems until someone steers me in the right direction! haha)
Thanks heaps!
Ryan
Edit 2011-02-07:
After reading the answers I've been given so far, I've tried out a few things.
Assuming the classes below get autoloaded from the exact location on the disk (Database_Database would get loaded in 'classes/Database/Database.php'), would this set up be effective?
class Database_Mysql_Database extends Database_DatabaseAbstract implements Database_Database {}
Database_Mysql_Database is a normal class, Database_DatabaseAbstract is an abstract class with the basic methods common to different types of databases, Database_Database would be the interface that users would typehint against to ensure compatibility with their classes.
Am I on the right track?
Personally, I would suggest that you put interfaces and exceptions where they are semantically appropriate. There's no reason to lump them all into one folder away from classes. But at the same time, don't put them next to the concrete implementations just for the sake of it. I'll give an example.
Let's say we're dealing with a database abstraction layer. You'll have an iDatabase
interface, and an iDatabaseDriver
interface. Say your folder (and class) structure is like this:
/classes/database/idatabase.php
/classes/database/database.php
/classes/database/drivers/mysql/databasedrivermysql.php
/classes/database/drivers/postgres/databasedriverpostgres.php
Now, there are 2 logical places to put iDatabaseDriver
. You could put it under database, or under drivers. Personally, I would put it under database, since it's kept close to where it's needed (since it's more likely that Database
requires an iDatabaseDriver
, so the dependency is there).
So, with that, you can see that sometimes it's semantically appropriate to put the interface right next to the concrete implementation. But other times it's more appropriate to put the interface next to the dependency than the concrete implementations.
Now this example is a gross-over-simplification, but I think it should get the point across.
Have rules for your naming and storage of interfaces
Come up with a system for organization of code. That way it's more predictable and easier to autoload. Plus, it becomes far easier to maintain when you can tell where something's supposed to be by the rules
Abide by those rules!
This is more important than having rules. If you don't follow the rules, it's worse than not having them at all since you're expecting something that's not going to happen.
Favor semantic relationships over code-level relationships
The semantic relationship between an interface and its concrete implementations is more important than the relationship that an interface is an interface. So put semantically related code in the same (or similar) places.
Edit: Regarding naming and your edit:
Personally, I hate things like Database_Database
. While it may make sense given the structure of the application, it makes no semantic sense whatsoever. Instead, what I like to do in my autoloader(s) is to test for the file, and if it doesn't exist but the directory does, test for the same file inside of that directory. So, Database
would result in a check in /database.php
and if that fails, /database/database.php
. It removes the need for double naming. Database_DatabaseAbstract
would become Database_Abstract
. So your Database_Mysql_Database
could become Database_Mysql
stored in /database/mysql/mysql.php
(which to me seems cleaner).
As far as your naming convention of abstract classes and the such, I personally prefer to identify interfaces by the name. It makes it easier to understand at a glance (You know that public function foo(iDatabase $database)
is looking for an instance of the interface instead of an abstract class or concrete class). Now, there are two real ways of doing this.
Append
Interface
to the name, soDatabase_Database
would becomeDatabase_Interface
. I personally feel this is a bit too verbose for my needs, however the benefit here is that all of your special class types (Exceptions, Interfaces, Iterators, etc) can simply be mapped like this. The class name tells you exactly what you have with no ambiguity whatsoever.Prepend the entire sequence with
i
. SoDatabase_Database
would becomeiDatabase
which would then be translated in the autoloader to/database/interface.php
. Then, if you had deeper interfaces,iDatabase_Mysql_Query
could work as well (Which would map to/database/mysql/query/interface.php
.
As far as the abstract class is concerned, I wouldn't do that. The fact that a class is abstract shouldn't really have anything to do with its semantic meaning. The abstract nature is a coding construct and not a semantic one (the abstract class is used for nothing other than inheritance since you're using the interface for type-checking). Therefore I would not recommend including Abstract
in the class name. Just call it Database
and be done. It reads better semantically (IMHO) and conveys the same meaning.
I hope that helps and makes sense...
The standard way is to treat them exactly as you would treat classes - store them in separate files, load with autoloader. Also:
with standard naming conventions you would call the interfaces Database and Router, not iDatabase and iRouter.
don't think about the performance before profiling: how can you be sure that the impact of having all the interfaces parsed with every single request does not offset loading many files? the files will be in a buffer anyway.
EDIT: (after the question got edited).
You want:
- Database (classes/Database.php), an interface for type hinting / code completion (if you insist on having an interface in the first place, but we are not discussing that);
- Database_Abstract (classes/Database/Abstract.php), a class that implements Database and has the common, abstract functionality; users are never aware it exists;
- Database_Mysql (classes/Database/Mysql.php), a class that extends Database_Abstract. It does not have to implements Database (Database_Abstract already does it);
You could also prepend all the paths (and names) with the name of your framework; this way if you use some classes from ZF, say, for PDF generation, you can keep the same autoloader and the same classes directory and not have the two mixed up.
If you are really into the OOP craze, you might add:
- Database_Factory (classes/Database/Factory.php) - a class that has a single method called createDatabase; the method has a doc comment of /** @returns Database */ and accepts a database configuration (as a string or maybe an array, or maybe a Database_Config object, if you are a hard core); this would be necessary to really decouple the implementation from the interface.
As for the answers that give you some different ideas for naming files, consider them, but take into account that: - uniformity is a great value on its own; - if you stick to pear convention, you can use the same autoloaders to load pear classes and a lot of PHP libraries; - if you stick to pear convention, you get used to modern PHP.
In general, endeavor to:
- Use an autoloader.
- Have one file contain one class.
- Keep interfaces near their concrete implementations.
Here is a good article on the subject:
http://weierophinney.net/matthew/archives/254-Why-PHP-Namespaces-Matter.html
If you are using PEAR or Zend naming conventions, the interface will be in a the components folder.
I.e., the a class Myprefix_Router_Interface translates into the path /Myprefix/Router/Interface.php
This is an excellent way to compartmentalize your components, and makes organization logical when looking at just the class name (you know exactly where the specific files are located).
精彩评论