Just today I posted a forum poll on whether PHPixie 3 components should be named in a more creative way to underline their modularity and independence from the framework itself. People are more likely to use a Sprite library for their image processing than PHPixie/Image which looks like it was ripped out from the framework.
But there is a huge problem with such naming:
1
2
3
4
5
6
7
//This:
$orm = new \PHPixie\ORM();
$orm->getModel('fairy');
//Looks much more sane than this:
$unicorn = new \PHPixie\Unicorn();
$unicorn->getModel('fairy');
I was always under the impression that branding should be separated from the actual code as much as possible, this is why even though PHPixie often mentions fairies in the examples, you’ll never see something like a Model::fairyDust() method. So then I considered keeping the class names as they were and just saying that the library is called Unicorn on the site. I then could use that name for branding to make it recognizable, but keep it out from the actual code.
And now I think I found a simple method to have both branding and concise class names. All I need to do is shift the classes down one namespace.
1
$orm = new \PHPixie\Unicorn\ORM();
This is such a simple solution, but it solves everything. I am not terribly concerned that the classes will grow a slightly longer namespace. Especially so considering that it all can be hidden in a use statement like:
1
2
use PHPixie\Unicorn\ORM;
$orm = new ORM();
Now I think that this might be a great idea for other projects to adapt too. What do you think?
P.S. I’m not actually going to name the ORM component Unicorn, so don’t worry =)
Now I must admit that what follows is pretty useless, but I had some spare time so I decided to to do it anyway.
Since version 5.6 PHP supports a new Splat operator. Among other things you can use it instead of the call_user_func_array function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
call_user_func_array('do_something', $params);
//Will now become
do_something(...$params);
1
This new syntax obviously looks much better and also lets you use IDE features. But does it perform any better ? just for fun first let's check the opcodes:
1
call_user_func_array('test', $params);
/**
2 0 > SEND_VAL 'test'
1 SEND_VAR !0
2 DO_FCALL 2 'call_user_func_array'
4 3 > RETURN 1
*/
test(...$params);
/**
2 0 > INIT_FCALL_BY_NAME 'test'
1 <165> !0
2 DO_FCALL_BY_NAME 0
3 3 > RETURN 1
*/
To be honest that wasn’t very useful =) It basically tells us the same thing that was already obvious, the difference in performance is going to be the overhead of calling call_user_func_array. So now let;s run some loops:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function test($a, $b, $c)
{
}
$params = array(1,2,3);
$t = time();
for($i=0; $i<100000000; $i++)
call_user_func_array('test', $params);
echo time()-$t."\n";
$t = time();
for($i=0; $i<100000000; $i++)
test(...$params);
echo time()-$t."\n";
/**
49
13
*/
So using the splat operator is about 3 times faster! Obviously we’re talking nanoseconds here so having a few call_user_func calls makes practically 0 difference. And if your code heavily relies on it, you’re probavly doing it wrong in the first place.
Hope you enjoyed this small trip into the world of useless benchmarks =)
This is just a quick post to share a few small methods that I now use frequently for writing more complex tests. PHPUnit has a rather verbose way of setting mock expectations which can rapidly turn into lots of copy-pasted code. Multiple times I have tried to come up with a nice and short way to define all the different kinds of expectations in the most laconic way. So here is what I hope to be a perfectly condensed set of shorthands, lets start with usage examples first:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//Mock a class
$mock = $this->quickMock('PHPixie\Fairy');
//Mock an abstract class including all abstract methods
$mock = $this->abstractMock('PHPixie\AbstractFairy');
//Here goes:
$this->method($mock, 'hello', 5);
$mock->hello(); //5
$this->method($mock, 'hello', 5, [1, 2]);
$mock->hello(1, 2); //5
$mock->hello(3); //Fail
$this->method($mock, 'hello', 5, [1, 2], 0);
$this->method($mock, 'hello', 6, [3], 1);
$mock->hello(1, 2); //5
$mock->hello(3); //6
$f = function($x) { return $x+1;};
$this->method($mock, 'hello', $f);
$mock->hello(1); //2
$mock->hello(3); //4
//For some rare cases when you actually want to return the function
$this->method($mock, 'hello', $f, null, null, true);
$mock->hello(1); // $f
And here is the actual code, with more documentation included. Feel free to rip out these methods into your existing base tetcase.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class ExtendedTestCase extends PHPUnit_Framework_TestCase
{
/**
* Mocks a class without requiring constructor parameters
*
* @param string $class Class to mock
* @param array $methods Specific methods to mock.
* Mocks all methods by default.
*
* @return PHPUnit_Framework_MockObject_MockObject Mock instance
*/
function quickMock($class, $methods = array())
{
return $this->getMock($class, $methods, array(), '', false);
}
/**
* Mocks an abstract class without requiring constructor parameters.
* Provides a nice workaound for mocking abstract methods.
*
* @param string $class Class to mock
* @param array $methods Specific methods to mock.
* Mocks all methods by default.
*
* @return PHPUnit_Framework_MockObject_MockObject Mock instance
*/
function abstractMock($class, $methods = array())
{
if(empty($methods)){
$reflection = new \ReflectionClass($class);
foreach($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method)
$methods[]=$method->getName();
}
return $this->getMockForAbstractClass($class, array(), '', false, false, true, $methods);
}
/**
* Sets method expectations
*
* @param PHPUnit_Framework_MockObject_MockObject $mock Mocked instance
* @param string $method Method to set expectations for
* @param mixed $return What the method should return.
* If this is a Callable, e.g. a function
* then it will be teated as returnCallback ()
*
* @param array $with Array of expected arguments.
* The expectations are set to be strictly equal
* so it's safe to pass instances here.
*
* @param integer $at The position at which the method is expected to be called.
* If this is null then all other expectations will apply to all calls
*
* @param boolean $returnCallback Whether to return $return even if it is a callback.
* Used for mocking methods which may return functions.
*
* @return PHPUnit_Framework_MockObject_MockObject Mock instance
*/
function method($mock, $method, $return, $with = null, $at = null, $returnCallable = false) {
$expects = $at === null ? $this->any() : $this->at($at);
$method = $mock
->expects($expects)
->method($method);
if ($with !== null) {
foreach($with as $key => $value) {
$with[$key] = $this->identicalTo($value);
}
$method = call_user_func_array(array($method, 'with'), $with);
}
$method->will($this->returnValue($return));
if (!$returnCallable && is_callable($return)) {
$method->will($this->returnCallback($return));
}else {
$method->will($this->returnValue($return));
}
}
}
I have been using this extensively for a long time now, and find the setup to really speed up my time with PHPUnit. Hopefully it will also serve you well =)
I’m continuing my list of useful topics for web developers, especially those using PHP. Now let’s tackle some things that would separate a Senior level developer. While one might easily argue that a Senior is first and foremost defined by experience and not theory, it’s really hard to measure experience on its own.
Can a person who has been solving the exact same problem in the exact same framework or CMS for a long time be truly considered a Senior Developer? Well maybe if we add the name of the CMS to the title and name him a Senior WordPress Developer that would be more fitting then. To be considered truly experienced a developer must have a wide range of knowledge and skills that he could consult and apply. Just being able to “google it” is good enough for a mediocre programmer, while a Senior one should have a lot of knowledge under his belt. So here goes:
Memory management
Data Structures
Programming paradigms
Architectural
Networking
SQL
NoSQL
XML
Security
HTML, CSS
I’m really lost as to what include here, since the whole field seems to be more experience driven than any other. If you have any ideas, please comment those.
Optimization
PHP
If you disagree on some points or would like to add your own please comment, I’d love to hear your opinion.
So my last post concerning Junior developers got some rather expected feedback that it was really pushing the line of what a “Junior” is. To be fair perhaps I should have named that post like I did that one. Maybe it doesn’t really matter that much if the person knows those things at the moment, rather that he should be able to grasp them when needed.
As for what a Middle level developer should know I think I can allow myself to push the line quite far. As I believe that the gap between Middle and Senior levels should be based more on experience than on theory. Obviously it’s the job of a Senior to know some more obscure things that don’t come up that often, but a Middle level developer should be self-sufficient. But please treat is as a suggested reading list, rather than “you have to be this tall” plank.
I received a few comments on why I focus little on actual technologies like libraries and frameworks. The reason is that they don’t really matter in the long run and may change every 2 years. You could also check out the “ If carpenters were interviewed as programmers” to understand my view on this.
General application runtime
This is a bit beyond the pale for web developers, but understanding the basics of application runtime may help prevent some bugs, like stack overflow for example.
Data structures
These actually come up often. In PHP arrays can behave like a lot of fundamental data structures, so you might have been using one for ages, and just didn’t know the proper name.
Programming paradigms
I’m unsure whether I should include functional programming here, since although it has become increasingly popular, especially after we got non=blocking I/O, but not everyone needs that.
Architectural
Networking
Relational Databases
NoSQL Databases
This is tricky, as there are so many and they differ a lot between themselves. But I guess it would be worthwhile to familiarize oneself with at least these ones:
HTML, CSS
Security
Development process
Optimization
PHP
I actually want to make these lists useful, so if you have any ideas or would like to suggest entire sections please comment. Such things are far better done by a community than a single person.
So today I’ve decided to take a little break from writing PHPixie v3 and do some blogging instead. Since my old post on test tasks for interviews got some public attention I decided to broaden the topic a bit with a list of things that I think every web developer must know. At my previous job we had a huge checklist of things that you needed to know to pass to the next level. While some may argue that such an approach is too formal, I find that it serves as a great opportunity to learn new things you might have never knew existed. And it also made sure that people higher in the hierarchy were at least a bit more knowledgeable than the ones they managed. So here is my version of such a list, but since It’s probably going to be huge I’ll split it into multiple posts.
Some people may say: My job is creating forms in WordPress, why would I learn things that have no direct connection to my job and that I won’t ever need in real life. And my answer is that if you don’t learn them, then making those WordPress forms will be your job for your entire life.
So let’s start with what I think is needed for a Junior developer. The distinction I want to make here is that “Junior” doesn’t mean “someone who just started developing”, but rather a person who has sufficient skill to be able to solve most of the tasks efficiently but needing guidance from time to time.
General programming awareness
OOP
Architectural
Networking
SQL
HTML, CSS and markup
This one heavily relies on actual experience, so I’ll only concentrate on theory:
Javascript
Yes, this gets its own section
Security
Development process
Optimization
PHP
Finally =)
That is about it.I tied to make the items easily googleable, and most of them are on wiki. And if you think than I’m really overkilling the “Junior” level, you’ll be really surprised how many of these topics can be covered in just 2 evenings spent on Wikipedia. Just try it =)
Since some of you have been anxious to see what PHPixie v3 is going to be and in order to get some feedback I decided to release two finished libraries from the third branch. And yes, I’m callng them ‘libraries’ and not ‘modules’ because they are now decoupled from the framework and can be used with any project.
They are PSR-2 compliant and fully tested with 100% code coverage.
https://rawgithub.com/PHPixie/Config/master/tests/report/index.html
https://rawgithub.com/PHPixie/Database/master/tests/report/index.html
I haven’t gotten around to writing documentation for classes yet though, obviously I’ll add them before releasing the next version of the framework. This post is going to be a quick introduction into the available features.
Configuration Library
Has now been moved to a separate library and adds new ways of defining and accessing your settings. Three configuration storages are now supported:
As usual you can access your values using the dot notation $config->get(‘forest.meadow.fairy.name’), but what if you have a Fairy class that should know only about what is under the forest.meadow.fairy and not know about hwo other configuration options are layed out? Also what if you swap ‘meadow’ for ‘field’, now you have to replace all references to that options in all classes, but now we have a solution.
Introducing Slices
You can slice your configuration to get a subset of it:
1
2
3
4
5
6
//instead of
$config->get('forest.meadow.fairy.name')
//you can do
$meadowConfig = $config->slice('forest.meadow');
$meadowConfig->get('fairy.name');
So now as you go deeper into class hierarchy you can slice your config as you go and pass it along.
Slices have additional methods like set() and remove() which allow you to modify configuration options and changes that you make to one slice will populate to other slices. Calling persist() on the storage after any of the slices has been modified will save your new options.
Directory Storage
Allows you to use both files and folders to specify configuration options, consider the following directory structure:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
config.php
config/
--forest.php
--forest/
----meadow/
------fairy.php
And here is the order in which it will look for your option,
if some file doesn't exsist it will skip that possibility.
1) 'name' in config/forest/meadow/fairy.php
2) 'fairy.name' in config/forest/meadow.php
3) 'meadow.forest.name' in config/forest.php
4) 'forest.meadow.forest.name' in forest.php
The fact that your configuration is split into files is entirely hidden, so that if you do $config->get(‘forest’) it will retrieve all of the data as one single array with nested subarrays.
Combining directory storage with slicing will make a perfect solution for allowing multiple languages on your site, for example you could pass $config->slice(‘lang.english’) to your view and the use $slice->get(‘cat’, ‘cat’) to get translations, with default value specified for when it’s not translated yet.
Database Module
The database module has been hugely updated and there is a lot to talk about, so lets instead focus on a few examples that utilize it to give you a good impression of whats inside.
To install use this in your composer.json
1
2
3
4
5
6
{
"require": {
"phpixie/config": "3.*@dev",
"phpixie/database": "3.*@dev"
}
}
And lets give it a go:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
require_once( __DIR__.'/vendor/autoload.php');
//GEt configuration library
$pixieConfig = new \PHPixie\Config;
//Create config from array
$config = $pixieConfig->dataStorage(array(
//MongoDB connection
'default' => array(
'connection' => 'mongodb://localhost',
'driver' => 'mongo',
'user' => 'mongouser',
'password' => 'mongopass',
'database' => 'test'
),
//Mysql connection
'mysql' => array(
'connection' => 'mysql:host=localhost;dbname=pixies',
'driver' => 'pdo',
'user' => 'root',
'password' => ''
)
));
//Now initialize database library with our config
$pixieDatabase = new \PHPixie\Database($config);
//Get the 'default' connection, MongoDB in our case
$conn = $pixieDatabase->get();
//Inserting entities
$conn->query('insert')
->collection('fairies')
->data(array(
'name' => 'Trixie',
'color' => 'blue',
'pets' => array(
'foxes' => 3
),
'about' => array(
'likes' => array('singing', 'flying')
)
))
->execute();
//Inserting multiple entities at once
$conn->query('insert')
->collection('fairies')
->batchData(array(
array(
'name' => 'Tinkerbell',
'color' => 'green',
'about' => array(
'likes' => array('singing', 'dancing')
)
),
array(
'name' => 'Flora',
'color' => 'yellow',
'about' => array(
'likes' => array('sleeping')
)
)
))
->execute();
//Using the query builder we get an equivalent of
//name = 'Trixie' OR (about.likes has 'dancing' AND about.likes has 'singing')
$fairies = $conn->query('select')
->collection('fairies')
->where('name', 'Trixie')
->orWhere(function($q) {
$q
->_and('about.likes', 'dancing')
->_and('about.likes', 'singing');
})
->orderBy('name', 'asc')
->limit(2)
->execute()
->asArray();
print_r($fairies);
//Updating some fields
$conn->query('update')
->collection('fairies')
->data(array(
'pets' => array(
'foxes' => 5
)
))
->whereNot('pets.foxes', '>', 5)
->execute();
//Deleting
$conn->query('delete')
->collection('fairies')
->where('name', 'Tinkerbell')
->execute();
//Take a look at what got left in the database
$fairies = $conn->query('select')
->collection('fairies')
->execute()
->asArray();
print_r($fairies);
//And clean up by deleting everything
$conn->query('delete')
->collection('fairies')
->execute();
//Now lets try MySQL
$conn = $pixieDatabase->get('mysql');
//Note how batch insertion has different syntax for SQL queries
//The first parameter defines columns while the second one defines rows
$fairies = $conn->query('insert')
->table('fairies')
->batchData(array('name', 'likes', 'pets', 'flowers'), array(
array('Tinkerbell', 'singing', 4, 5),
array('Tinkerbell', 'dancing', 6, 2)
));
//Selecting fields with aliases and joinging tables
//You can define more complex on() conditions
//using methods like orOn(), etc..
$fairies = $conn->query('select')
->fields(array(
'fairy' => 'fairies.name',
'trees' => 'trees.name'
))
->table('fairies')
->join('trees')
->on('fairies.id', 'trees.fairy_id')
->execute()
->asArray();
print_r($fairies);
//The '*' after an operator
//specifies that 'flowers' is a name of a column
//and not a string value
$fairies = $conn->query('select')
->table('fairies')
->where('pets', '>*', 'flowers')
->execute()
->asArray();
print_r($fairies);
There is going to be much more documentation on both modules coming when the rest of the framework is done, but I hope this was be enough to get you to try using it. As I said, it is already tested an working properly, so you might as well give it a go now =)
P.S. The last week I’ve been working day and literally night to get this done, tested and released so finally I’m going to get some rest =)
Today I started refactoring the code I have for PHPixie 3 to comply with PSR standards. Thankfully there is already a great tool for this, the PHP CS Fixer, that will replace your tabs with spaces, put brackets appropriately etc. But there is one feature it doesn’t have, the option to convert snake_case() to camelCase(). Obviously there is a good reason for that, a formatter shouldn’t be meddling with the actual logic.
Never the less I still needed to convert my snake case entities, so I wrote a quick script that will do just that, here it is:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function snakeToCamel($val) {
preg_match('#^_*#', $val, $underscores);
$underscores = current($underscores);
$camel = str_replace(' ', '', ucwords(str_replace('_', ' ', $val)));
$camel = strtolower(substr($camel, 0, 1)).substr($camel, 1);
return $underscores.$camel;
}
function convert($str) {
$name = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
$snake_regexps = array(
"#->($name)#i",
'#\$('.$name.')#i',
"#function ($name)#i",
);
foreach($snake_regexps as $regexp)
$str = preg_replace_callback($regexp, function($matches) {
//print_r($matches);
$camel = snakeToCamel($matches[1]);
return str_replace($matches[1],$camel,$matches[0]);
},$str);
return $str;
}
$path = $argv[1];
$Iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
foreach($Iterator as $file){
if(substr($file,-4) !== '.php')
continue;
echo($file);
$out = convert(file_get_contents($file));
file_put_contents($file, $out);
}
To use it, just pass the path to your project as a parameter:
1
php convertCase.php /projects/project1/
Ideally you would run it on both your classes and tests, then run PHP CS Fixer, and then run your tests to make sure nothing broke.
Hope it helps someone port their project to PSR faster
Looking around frameworks and libraries I see this strange notion of abstracting dependency injection into xml, yml, arrays, callbacks and a whole bunch of other things. And it’s very hard for me to find any legitimate reason for doing so, especially since this approach actually reduces the flexability of your dependency injection.
To illustrate my poin’t I’m going to create an example dependency injection factory which follows the Pure Fabrication principle of GRASP. It will represent a part of the application responsible for creating Fairies for protecting Trees in a Forest. There are a number of different types each fairy could be, and different types get different spells they can cast.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace Fairies;
class Factory {
public function fairy($fairy_type) {
//Each fairy type has a corresponding spell ability
//First we need to determine which magic type to use
$spell_type = $this->get_spell_type($fairy_type)
//Now build an instance of that magic
$spell = $this->spell($spell_type);
return new Fairy($fairy_type, $spell);
}
protected $fairy_spells = array(
'pixie' => 'heal',
'sylph' => 'wind'
);
protected function get_spell_type($fairy_type) {
return $this->fairy_spells[$fairy_type];
}
public function spell($spell_type) {
//Determine class name based on spell type
$class = 'Fairy\Spell\'.$spell_type;
return new $class();
}
}
Now we have a nice class for creating fairies. You can easily write tests for this class to ensure that all functions return correctly built instances. You could also define it as an interface and pass this factory around classes that would need to use it. All this while maintaining a nice API and having full IDE support with typehinting. Additionaly we expose only some logic like building actual fairies and spells, but we hide the logic behind determining which fairy gets which spell.
Now let’s suppose we need another factory for building trees. That new factory must now be able to build a fairy to assign it to protecting that tree. All we need to do is inject our Fairy factory as a dependency like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace Trees;
class Factory {
protected $fairies_factory;
//Forest is a service, so it's only going to be built once
protected $forest;
public function __construct($fairies_factory) {
$this->fairies_factory = $fairies_factory;
}
public function forest() {
if($this->forest === null)
$this->forest = $this->build_forest();
return $this->forest;
}
protected function build_forest() {
return new Forest;
}
public function tree($tree_type, $fairy_type) {
$forest = $this->forest();
$fairy = $this->fairy_factory->fairy($fairy_type);
return new Tree($tree_type, $fairy, $forest);
}
}
Using this strategy we can build factories on top of other factories, separating and layering them. This will give us easy testability and separation of concerns. If I pass a Tree Factory to a class I’m sure that that class will have no need in creating fairies or magic spells, by splitting factories I’m also splitting their interfaces, making it easier to see what the class is actually going to use.
Now let’s imagine we’re doing this using Pimple, it would look something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$container = new Pimple();
//Now isn't this convoluted?
$container['fairy'] = $container->protect(function($fairy_type) use($container){
$spell_type = $container['spell_type']($fairy_type);
$spell = $container['spell']($spell_type);
return new Fairy($fairy_type, $spell);
});
$container['spell_type'] = $container->protect( function($fairy_type) {
//As we don't have protected properties anymore
//and we don't want to expose the fairy-spell map
//we have to put it inside this method
$fairy_spells = array(
'pixie' => 'heal',
'sylph' => 'wind'
);
return $this->fairy_spells[$fairy_type];
});
$container['spell'] = $container->protect( function($spell_type) {
//Determine class name based on spell type
$class = 'Fairy\Spell\'.$spell_type;
return new $class();
});
//Good luck documenting these functions btw
This isn’t OOP at all, we had to resort to thing like use and sacrifice some flexability (since we had to move the spell map to the method). We also get no IDE completion, and have to pass a huge container around all classes if we ever need to build something.
Now someone is definitely going to say this: “You are using Pimple wrong, it should instead be used for holding services, like in the docs.”. And this is my reply: Yes, because Pimple is not a Dependency Injection container, it is a Service Locator. A true DI container should not only allow you to create services but also build instances of classes while automatically injecting required shared instances into those. To get this functionality out of Pimple you are still going to write factory classes, so what’s the point?
Approaches based on XML and YML are even worse, because now instead of using classes and interfaces to get dependencies I have to lookup how some other developer decided to name a particular instance, e.g. how in symfony2 to inject doctrine you need to add ‘@doctrine.orm.entity_manager’ to your YML file.
It seems that all of these approaches were created int he time when DI was something foreign to PHP and people needed things to ease them into it. Right now, when everybody is doing it let’s just ditch this thing ok? Can we just use proper classes and factories instead of some weird annotations and configurations please?
Before I start this, let me emphasize on that I’m going to be talking about Test Driven Development specifically and not Unit Testing in general. For some reason people confuse not following TDD with not writing tests at all, so this little disclaimer should take care of angry comments.
Now let me take you on a whole new route, we’ll dabble in some psychology a bit, specifically Jungian theories. One thing he is definitely famous for is describing sets of psychological types, that may be used to classify human behaviour. One of such classifications concerns with our preferences when making decisions and basically defines which route we take when solving problems. It is represented by two dualities, Thinking vs Feeling and Sensing vs Intiition.
Here are some brief examples:
Feeling – deciding not to accept the job offer because of getting a bad vibe from the HR.
Sensing – tasting food, following defined steps.
Intiuition – feeling the taste of the cake in your mouth just by looking at its picture, trying to find a larger picture.
This also divides people into rational and arational types. Arational differs from irrational because irrational implies that a particular judgement has been made against common sense, while arational means that a something else has been used instead of rationality.
Now a lot of the science/tech guys would find themselves as to being rational people, and it usually is so. A lot of the developers I know are of the Thinking/Sensing type and they hold the majority when it comes to defining methodologies.
And that is how TDD was born. It’s a step-by-step process of first coming up with general architecture, then writing tests for it and then implementing it. you have to have a pretty clear vision of what you are building to get this to work for you and it rewards you with 100% test coverage at the end. This is great, but…
…it definitely sucks for me. Whenever I think I have architecture figured out, I find myself scratching it after about an hour into implementing it. I’m actually more productive when I start writing a random class in the middle of the soon-to-be application trying to get a feel of what services and features I will need. Then being half done with that class, I forget about it and immediately move to some other one, trying to get more clues about what would be the best class hierarchy. Sort of like when playing minesweeper you start by uncovering random squares to get a nice feel of your surroundings. and when I’m at 90% with being done with everything, sometimes an awesome idea comes to my mind but it requires me to rewrite 30% completely.
Now how would I couple this with TDD? I tried it, and all that came from it was me writing tests for classes that won’t even be there in the final release. So I decided to stick to the good old style of writing tests after I’m 100% sure that a class won’t change later on, e.g. after it’s done.
I guess at this point I betrayed myself to be of the Feeling/Intuition type =) And that is the point of my post here. Different people require different methodologies , – so don’t feel bad if some methodology doesn’t work well for you, as it may just be that it goes against the way your brain operates. Developers who are in charge of their teams should definitely keep this in mind. Because forcing a logical approach to intuitive people is as much a fallacy as teaching left-handed children right-handed writing. Now that i think of it it seems to be that Intuitive people prefer to work alone and usually become freelancers, since it gives them more perceived freedom than a company worker feels.
P.S. Don’t be lazy, write tests, they help, a lot.