![]() |
This principle says to favor many small, fine grained interfaces vs. one large one. Interfaces should be based on behavior rather than "it's one of these classes". Think of interfaces that come with PHP. Traversable, Countable, Serializable, things like that. They advertise capabilities that the object possesses, not what it inherits from. So keep your interfaces small. You don't want an interface to have 30 methods on it, 3 is a much better goal.
3.1.4 D - Dependency Inversion Principle
You've probably heard about this in other places that talked about Dependency Injection, but Dependency Inversion and Dependency Injection aren't quite the same thing. Dependency inversion is really just a way of saying that you should depend on abstractions in your system and not on its details. Now what does that mean to you on a day to day basis?
Don't directly use mysqli_query() all over your code, use something like DataStore->query() instead.
The core of this principle is actually about abstractions. It's more about saying "use a database adapter" instead of depending on direct calls to things like mysqli_query. If you're directly using mysqli_query in half your classes then you're tying everything directly to your database. Nothing for or against MySQL here, but if you are using mysqli_query, that type of low level detail should be hidden away in only one place and then that functionality should be exposed via a generic wrapper.
Now I know this is kind of a hackneyed example if you think about it, because the number of times you're going to actually completely change your database engine after your product is in production are very, very low. I picked it because I figured people would be familiar with the idea from their own code. Also, even if you have a database that you know you're sticking with, that abstract wrapper object allows you to fix bugs, change behavior, or implement features that you wish your chosen database had. It also makes unit testing possible where low level calls wouldn't.
4 Object calisthenics
This isn't a full dive into these principles, but the first two are easy to remember, provide good value, and can be immediately applied to just about any codebase.
4.1 No more than one level of indentation per method
This is a helpful way to think about decomposing methods into smaller chunks, leaving you with code that's clearer and more self-documenting. The more levels of indentation you have, the more the method is doing and the more state you have to keep track of in your head while you're working with it.
Right away I know people will object to this, but this is just a guideline/heuristic, not a hard and fast rule. I'm not expecting anyone to enforce PHP_CodeSniffer rules for this (although people have).
Let's run through a quick sample of what this might look like:
- public function transformToCsv($data)
- {
- $csvLines = array();
- $csvLines[] = implode(',', array_keys($data[0]));
- foreach ($data as $row) {
- if (!$row) {
- continue;
- }
- $csvLines[] = implode(',', $row);
- }
- return $csvLines;
- }
We know we need to vastly simplify the contents of the foreach loop (or remove it entirely) so let's start there.
- if (!$row) {
- continue;
- }
- This first bit is easy. All this is doing is ignoring empty rows. We can shortcut this entire process by using a built-in PHP function before we even get to the loop.
- $data = array_filter($data);
- foreach ($data as $row) {
- $csvLines[] = implode(',', $row);
- }
- $data = array_filter($data);
- $csvLines =+ array_map('implode', $data, array_fill(0, count($data), ',');
4.2 Try not to use else
This really deals with two main ideas. The first one is multiple return statements from a method. If you have enough information do make a decision about the method's result, go ahead make that decision and return. The second is an idea known as Guard Clauses. These are basically validation checks combined with early returns, usually near the top of a method. Let me show you what I mean.
- public function addThreeInts($first, $second, $third) {
- if (is_int($first)) {
- if (is_int($second)) {
- if (is_int($third)) {
- $sum = $first + $second + $third;
- } else {
- return null;
- }
- } else {
- return null;
- }
- } else {
- return null;
- }
- return $sum;
- }
- public function addThreeInts($first, $second, $third) {
- if (!is_int($first)) {
- return null;
- }
- if (!is_int($second)) {
- return null;
- }
- if (!is_int($third)) {
- return null;
- }
- return $first + $second + $third;
- }
5 Unit testing
Unit testing is the practice of writing small tests that verify behavior in your code. They are almost always written in the same language as the code (in this case PHP) and are intended to be fast enough to run at any time. They are extremely valuable as a tool to improve your code. Other than the obvious benefits of ensuring that your code is doing what you think it is, unit testing can provide very useful design feedback as well. If a piece of code is difficult to test, it often showcases design problems. They also give you a safety net against regressions, and that allows you to refactor much more often and evolve your code to a cleaner design.
5.1 Tools
There are several unit testing tools out there in PHP, but far and away the most common is PHPUnit. You can install it by downloading a PHAR file directly, or install it with composer. Since we are using composer for everything else, we'll show that method. Also, since PHPUnit is not likely going to be deployed to production, we can install it as a dev dependency with the following command:
composer require --dev phpunit/phpunit
5.2 Tests are a specification
The most important role of unit tests in your code is to provide an executable specification of what the code is supposed to do. Even if the test code is wrong, or the code has bugs, the knowledge of what the system is supposed to do is priceless.
5.3 Write your tests first
If you've had the chance to see a set of tests written before the code and one written after the code was finished, they're strikingly different. The "after" tests are much more concerned with the implementation details of the class and making sure they have good line coverage, whereas the "before" tests are more about verifying the desired external behavior. That's really what we care about with unit tests anyway, is making sure the class exhibits the right behavior. Implementation-focused tests actually make refactoring harder because they break if the internals of the classes change, and you've just cost yourself the information hiding benefits of OOP.
5.4 What makes a good unit test
Good unit tests share a lot of the following characteristics:
- Fast - should run in milliseconds.
- No network access - should be able to turn off wireless/unplug and all the tests still pass.
- Limited file system access - this adds to speed and flexibility if deploying code to other environments.
- No database access - avoids costly setup and teardown activities.
- Test only one thing at a time - a unit test should have only one reason to fail.
- Well-named - see 5.2 above.
- Mostly fake objects - the only "real" objects in unit tests should be the object we're testing and simple value objects. The rest should be some form of test double
5.5 When testing is painful
Unit testing forces you to feel the pain of bad design up front - Michael FeathersWhen you're writing unit tests, you're forcing yourself to actually use the class to accomplish things. If you write tests at the end, or worse yet, just chuck the code over the wall for QA or whoever to write tests, you don't get any feedback about how the class actually behaves. If we're writing tests, and the class is a real pain to use, we'll find out while we're writing it, which is nearly the cheapest time to fix it.
If a class is hard to test, it's a design flaw. Different flaws manifest themselves in different ways, though. If you have to do a ton of mocking, your class probably has too many dependencies, or your methods are doing too much. The more setup you have to do for each test, the more likely it is that your methods are doing too much. If you have to write really convoluted test scenarios in order to exercise behavior, the class's methods are probably doing too much. If you have to dig inside a bunch of private methods and state to test things, maybe there's another class trying to get out. Unit testing is very good at exposing "iceberg classes" where 80% of what the class does is hidden away in protected or private code. I used to be a big fan of making as much as possible protected, but now I realized I was just making my individual classes responsible for too much, and the real solution was to break the class up into smaller pieces.
Written by Brian Fenton
If you found this post interesting, follow and support us.
Suggest for you:
Learning PHP 7: From the Basics to Application Development
The Complete PHP 7 Guide for Web Developers
Up to Speed with PHP 7
Learn PHP 7 This Way to Rise Above & Beyond Competion!
PHP MySQL Database Connections
The Complete PHP with MySQL Developer Course (New)

No comments:
Post a Comment