Monday, 30 January 2017

PHP: accessing an undefined array element yields a notice. Or does it? WTF, PHP?

G'day:
So we encountered this today:

<?php
$array = [1, 2, 3];

var_dump($array['id']); // this yields a warning
var_dump($array[0]['id']); // none of these do
var_dump($array[0][0]['id']);
var_dump($array[0]['id'][0]);

As indicated, this yields results:

C:\temp>php shite.php
PHP Notice:  Undefined index: id in C:\temp\shite.php on line 4
PHP Stack trace:
PHP   1. {main}() C:\temp\shite.php:0

Notice: Undefined index: id in C:\temp\shite.php on line 4

Call Stack:
    0.0002     353472   1. {main}() C:\temp\shite.php:0

C:\temp\shite.php:4:
NULL
C:\temp\shite.php:5:
NULL
C:\temp\shite.php:6:
NULL
C:\temp\shite.php:7:
NULL

C:\temp>

I think this behaviour is bloody daft. Accessing something that doesn't exist should be more than a notice, and it shouldn't then go ahead and return "null" anyhow. And giving a notice and returning null is possibly the worst way of combining possible actions here. What am I supposed to do with that? Other than bury my head in my hands a weep.

Now this is partially documented on the Arrays page of the docs:

Note:
Attempting to access an array key which has not been defined is the same as accessing any other undefined variable: an E_NOTICE-level error message will be issued, and the result will be NULL.
Note:
Array dereferencing a scalar value which is not a string silently yields NULL, i.e. without issuing an error message.

That's shit, but... well: it's not that surprising (I mean... it's PHP, right?). But how come

$array['id']

incurs the warning, but this doesn't:

$array[0]['id']

This just seems to make fairly unhelpful behaviour just that much less helpful.

I'm sure there's a reason for this. Not a good one, I hasten to add, but no doubt there's a reason.

Anyone know what it is?

Update:

Thanks to Dave (comment below) for pointing out there's a bug been raised for this: 68110.


Righto.

--
Adam

Thursday, 26 January 2017

Survey results: most useful response first

G'day:
I've got the results in from that survey, and started looking through them last night. I haven't really organised anything yet, so nothing useful to report, but this response stood out. The survey had 16 questions, but they left them all blank except question 15:

  1. What else do you think might be useful to share?
    You are toxic.
That's it. That's what they decided to write. That's what they figured was worth investing their time in.

Now I'll not contest whether or not I'm toxic, but I'm not sure that in a community survey aimed at understand how best to help people migrate their skills from CFML is really the best place for it. They coulda just emailed me (my email address is in the "Communications policy" link to the right there). But what I will conclude is if they read the survey questions, and decided that was a sensible, relevant and effective answer to make, they should perhaps be worrying less about how toxic I might or might not be, and more concerned about their own state of mind.

But still: at least I got a new quote for my banner line.

Yours toxically,

--
Adam

Tuesday, 24 January 2017

I'm a dick: part 4: why I'm a dick

G'day:
Sigh.

So other the page few days I've posted these articles:

This was all around the survey I was running (Survey: CFML usage and migration strategies), which freeonlinesurveys.com had closed access to unless I upgraded to a pay-for account. I assumed they were being dodgy, and reacted poorly to this.

A very patient user support person contacted me this evening, and pointed out this screen, when I first went to launch the survey:


And I needed to click on the "OK" button to proceed. I will admit I scanned the first coupla options and went "yeah yeah, same as the pricing page, where's the button to click?", but didn't get as far as the relevant bullet point, or it didn't sink in, or whatever.

However one spins it: they did tell me this information, I simply chose not to pay attention to it.

In freeonlinesurveys.com's favour, the offered to release the data anyhow. I have said to them "no, ballocks to that: it's my bad, I'll pay for it".

Sigh.

Let's join in a chorus of "Cameron's a dick".

But at least I can now start assessing those results, and try to work out how to feed back to you.

Righto.

--
Adam (who is a dick)

I'm a dick: part 3 Email sent to Bristol Trading Standards office re freeonlinesurveys.com

G'day:
What I said in this article turns out to be erroneous. I will post a follow-up to it shortly, but first things first, I'm taking the content down as I don't want it on Google.

But equally I don't want to hide what I said, so I'm gonna re-insert it as an image, so y'all can see the original.

I'll link to the follow-up once I've written it.

Righto.

--
Adam




I'm a dick: part 2 T&Cs located! partial correction on the freeonlinesurveys.com stitch-up

G'day:
What I said in this article turns out to be erroneous. I will post a follow-up to it shortly, but first things first, I'm taking the content down as I don't want it on Google.

But equally I don't want to hide what I said, so I'm gonna re-insert it as an image, so y'all can see the original.

I'll link to the follow-up once I've written it.

Righto.

--
Adam





Saturday, 21 January 2017

I'm a dick: part 1 Survey results: we've been stitched-up by freeonlinesurveys.com

G'day:
What I said in this article turns out to be erroneous. I will post a follow-up to it shortly, but first things first, I'm taking the content down as I don't want it on Google.

But equally I don't want to hide what I said, so I'm gonna re-insert it as an image, so y'all can see the original.

I'll link to the follow-up once I've written it.

Righto.

--
Adam



Saturday, 14 January 2017

Incredibly: a reader asks me for help with PHP & Nginx

G'day:
Jesus fuck, you lot. Don't ask me shit about systems support / server application config / all that godawful shit that should be consigned to the Systems Support Team (sorry to my mates in this role, but I fucking hate it, and became a dev so I didn't have to do it any more).

Right so ages ago I wrote an article "PHP: getting PHP 5 and PHP 7 running side by side on the same machine (a better way)", and someone recently asked me how do do the same on Nginx. Well it's "slow news night" here in my life: I'm just at the pub in Galway passing time by getting pissed on Guinness (I'm on me fifth pint, and even I can tell the writing here is reflecting that) and writing blog articles, so I had a look at it. It's pretty easy, as it turns out.

Firstly: I am no expert on Nginx. I don't like web servers. I try not to ever have to use them. I know enough about IIS to know it's a pain in the arse but I can do what I need if I have to; and I know enough about Apache to get things working. I don't even know why Nginx exists. I'm guessing the reason is "to annoy Adam, cos it's just one more bloody thing he'll need to know about at some point". Sigh. I got PHP working on Nginx once before ("PHP: getting my dev environment running on Nginx instead of Apache"), and other than that have converted some rewrite rules from Apache to Nginx (man: does Nginx suck compared to Apache for those!).

This is a different laptop from the one I did that other exercise on, so whilst I had Nginx on here (for the rewrite exercise, which was work-related and this is my work laptop), I decided to start from scratch. I deleted what I had installed (where for Nginx "installed" means "unzipped").

Here's what I did:
  1. grabbed the latest Windows Nginx download from their site. I just googled "nginx windows download" to find that.
  2. Unzipped it. Relocated it to my apps dir: c:\apps\nginx
  3. Stopped Apache (which listens on port 80), and instead started Nginx (just run nginx.exe from the dir above). By default it listens on localhost and port 80.
  4. Browse to http://localhost/ and verified the Nginx default index.html page was served. OK, so it works as a baseline. Always test the baseline before proceeding with any customisations of things.
  5. Set-up a coupla test hosts in my hosts file (C:\windows\system32\drivers\etc\hosts... make sure to start yer text editor as an admin, otherwise you won't be able to save it):
    127.0.0.1 php5.nginx.local
    127.0.0.1 php7.nginx.local

    I foresaw that to run both PHP5 and PHP7, Nginx is gonna need to differentiate between which one wants to use, and using different host names seemed to make sense and be easy.
  6. I edited my nginx.conf file (in the conf subdir of the one above) to know about these two hosts:
    http {
        # [...]
    
        server {
            listen       8800;
            server_name  php5.nginx.local;
            root   c:/src/php/php.local/www;
    
            # [...]
        }
    
        server {
            listen       8800;
            server_name  php7.nginx.local;
            root   c:/src/php/php.local/www;
    
            # [...]
        }
    }
    

    Where I've elided stuff, it's the same as it was before. The chief considerations here are:
    • having a server for both PHP5 and PHP7;
    • having them listen on each of those two new hosts I set up;
    • setting the root to be where my PHP code is. This is the same for both in this case, as I want to serve exactly the same code via both 5 and 7;
    • oh and I'm listening on port 8800 as I don't want Nginx to interfere with my normal Apache install (I'll be sticking with Apache after this experiment, thanks).
    Note that this config will still not serve PHP, but it'll at least run.
  7. When one runs Nginx from the console it hogs the prompt, so to stop it one needs to run another console and call nginx -s stop. We need to do this to test the config changes. So I did that, and used the other console to start it again.
  8. I browse to each of http://php5.nginx.local:8800 and http://php7.nginx.local:8800 to test they were working. They were running, but giving a 403 cos I didn't have an index.html in that directory, nor did I have directory browsing switched on (which for my test code I do, as it makes finding stuff easier).
  9. I told Nginx to allow directory browsing for each of the server configs:
    location / {
        index  index.html index.htm;
        autoindex on;
    }
    

    Do not do this in production. Well: don't do anything I say in production.
  10. I cycled Nginx again and tested both hosts:

    Index of /


    ../
    code-coverage-reports/                             15-Nov-2016 13:33                   -
    community/                                         30-Jun-2016 11:47                   -
    experiment/                                        15-Nov-2016 08:47                   -
    library/                                           30-Jun-2016 11:47                   -
    stackoverflow/                                     30-Jun-2016 11:47                   -
    deleteme.php                                       30-Jun-2016 11:47                 233
    gdayWorld.html                                     30-Jun-2016 11:47                  11
    gdayWorld.php                                      30-Jun-2016 11:47                  50
    phpinfo.php                                        30-Jun-2016 11:47                  21
    utf8.html                                          21-Dec-2016 08:21                 133
    


    So that's all good except for me not having deleted that file that perhaps I meant to, a while back ;-)
  11. Next I just followed the instructions from me other blog article, getting PHP to listen out to traffic coming in from Nginx (the code below is in a batch file):
    start C:\bin\RunHiddenConsole.exe C:\apps\php\5\5\php-cgi.exe -b 127.0.0.1:8550 start C:\bin\RunHiddenConsole.exe C:\apps\php\7\1\php-cgi.exe -b 127.0.0.1:8710
    Note that each of them is listening on a different port.
  12. And then tell Nginx to pass PHP requests across to PHP:
    server {
        # [...]
        location ~ \.php$ {
            fastcgi_pass   127.0.0.1:8710;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include        fastcgi_params;
        }
        # [...]
    }
    

    That's the one for PHP7, but the PHP5 one is the same except for the port.
  13. I restart Nginx again, and this time hit phpinfo.php on each host:
    {i'd show you proof, but BlogSpot is refusing to let me put two images inline in an <ol> list, it seems. Trust me... it works).
  14. Hurrah!
  15. The last thing I tried to do is to wrap my batch file that starts Nginx into a service, using RunAsService.exe, but this is my work laptop and it's locked-down too tight for me to run that. But... well... the batch file works.
So, anyway... that's it. That's what one needs to do to get Nginx serving two different versions of PHP. My will to live has been sapped by even having to think about this sort of shit, so I'm going back to my Guinness (two pint further ahead than I was when I started this exercise).

Sorry this one is a bit scrappy but... well... I'm drunk.

Righto.

--
Adam

PHP: 2000 words on elseif (and PHPMD)

G'day:
This is another one that just lives up to the definitive notion of a blog: a web log; a log of what I'm doing. But, like... web-based. A blog. What I basically mean is that I'm not sure I'm showing any insight here (look: I basically know I'm not), but it's what I looked at today, so it's what I'm writing about today.

We're trying to lift our game when it comes to our code "Cleanliness" (in the RC Martin "Clean Code" sense), and we've started more rigorously using some code review tools recently, such as PHPCS and PHPMD. They're both pretty good, and I recommend them to anyone using PHP. Almost everyone probably already is: I'm always late to these games.

One PHPMD rule that tends to irk me is the one that declares "[one's code] uses an else expression. Else is never necessary and you can simplify the code to work without else". Now: generally this is true. One can usually refactor one's code to stick it in a helper method and early return in the if logic, then just the rest of the code after the if block is what happens in the else case. This is good because it helps one's code be "closer to the left-edge"; ie: it's not indented because it's not in a logic block any more. The more one needs to indent one's code: the more it probably smells. It's got to the stage with our code now that a double nesting of logic-control statements almost gets the same reaction from code reviewers as when they see a comment in our code; "hey, gather round everyone: Adam's made a comment. Let's look at it!" (Adam shrinks into the carpet and starts getting his excuses ready. Later he refactors the code to be clearer, and the comment goes; the code reviewers are happy). The problem for me is that I'm not clever enough sometimes, and it takes me a coupla takes before I see how to get rid of a wayward else. At least now I usually pick it up and work it out during my "self code review" phase, and sort it out. And PHPMD helps with this.

Today I was reviewing a colleague's code, and noted they had not an else but an elseif. And I triumphantly(*) pinged them with a comment reminding them to run PHPMD before submitting their pull request.

(*) because I'm petty.

It turns out that they had run PHPMD, and it hadn't pulled 'em up. Initially (again: cos I'm petty) I didn't believe them and knocked together my own SSCCE and looked at it.

This demonstrates the problem:

function takeAChance(){
    $firstChance = (bool) rand(0,1);

    if ($firstChance) {
        echo "firstChance was true";
    } else {
        echo "firstChance was false";
    }
}

takeAChance();

And now running PHPMD over it:

C:\src\php\php.local\src\phpmd>phpmd else_block.php text phpmd.xml
C:\src\php\php.local\src\phpmd\else_block.php:8 The method takeAChance uses an else expression.
Else is never necessary and you can simplify the code to work without else.

C:\src\php\php.local\src\phpmd>


OK, fair enough. Oh btw, the solution to this is to early return:

function takeAChance(){
    $firstChance = (bool) rand(0,1);

    if ($firstChance) {
        echo "firstChance was true";
        return;
    }
    echo "firstChance was false";
}

(note: yeah yeah, in the real world I'd not be echoing from a function. I get that. Not the point here)

OK, now here's an SSCCE of the sort of thing we had in this pull request:

function takeAChance(){
    $firstChance = (bool) rand(0,1);
    $secondChance = (bool) rand(0,1);

    if ($firstChance) {
        echo "firstChance was true; don't care what secondChance was";
    }elseif($secondChance){
        echo "well at least secondChance was true";
    }
}

And running PHPMD:

C:\src\php\php.local\src\phpmd>phpmd elseif_block.php text phpmd.xml

C:\src\php\php.local\src\phpmd>

No problems. Grrr. This does not make sense to me. If we add an actual else in there:

function takeAChance(){
    $firstChance = (bool) rand(0,1);
    $secondChance = (bool) rand(0,1);

    if ($firstChance) {
        echo "firstChance was true; don't care what secondChance was";
    }elseif($secondChance){
        echo "well at least secondChance was true";
    }else{
        echo "both were false";
    }
}

And PHPMD says:
C:\src\php\php.local\src\phpmd>phpmd if_elseif_else_block.php text phpmd.xml
C:\src\php\php.local\src\phpmd\if_elseif_else_block.php:11
The method takeAChance uses an else expression.
Else is never necessary and you can simplify the code to work without else.

C:\src\php\php.local\src\phpmd>


And, yeah, it's true... but the elseif seems to me to be as much an issue here as an else. We can short-circuit both easily enough with early returns:

function takeAChance(){
    $firstChance = (bool) rand(0,1);
    $secondChance = (bool) rand(0,1);

    if ($firstChance) {
        echo "firstChance was true; don't care what secondChance was";
        return;
    }
    if($secondChance){
        echo "well at least secondChance was true";
        return;
    }
    echo "both were false";
}

Equally, to me a PHP elseif (note this is an actual command, it's not a concatenation of else if like one might expect), really is, functionally, a "single-statement else", just with the statement itself being an if statement. What I mean is remember that the if / elseif / else statement doesn't need to use blocks (ie: a bunch of statements within braces), it could be a single statement:

function takeAChance() {
    $firstChance = (bool) rand(0,1);

    if ($firstChance)
        echo "firstChance was true";
    else
        echo "firstChance was false";

}

(what an abomination: almost looks like Python. Bleah).

Anyway... so yeah, one doesn't need to use braces if there's only one statement to execute based on the result of the condition. Don't do this. But it's possible.

What I'm getting at is this:

if ($firstChance) {
    echo "firstChance was true; don't care what secondChance was";
}elseif($secondChance){
    echo "well at least secondChance was true";
}

Is basically like this:

if ($firstChance) {
    echo "firstChance was true; don't care what secondChance was";
}else{
    if($secondChance){
        echo "well at least secondChance was true";
    }
}

Just without the braces on the else:

if ($firstChance) {
    echo "firstChance was true; don't care what secondChance was";
}else
    if ($secondChance){
        echo "well at least secondChance was true";
    }

Compare this again to the elseif version:

if ($firstChance) {
    echo "firstChance was true; don't care what secondChance was";
}elseif ($secondChance){
    echo "well at least secondChance was true";
}

The only difference there is the whitespace between the else and the if. And logically there is no difference, as far as I can tell.

I tested all variations I could think of of elseifs, elses, braces, no braces, and the like (see the samples on GitHub), and ran PHPMD over them:

C:\src\php\php.local\src\phpmd\samples>phpmd . text phpmd.xml
C:\src\php\php.local\src\phpmd\samples\else_block.php:8 The method takeAChance uses an else expression. Else is never necessary and you can simplify the code to work without else.
C:\src\php\php.local\src\phpmd\samples\else_if_block.php:9      The method takeAChance uses an else expression. Else is never necessary and you can simplify the code to work without else.
C:\src\php\php.local\src\phpmd\samples\if_elseif_else_block.php:11      The method takeAChance uses an else expression. Else is never necessary and you can simplify the code to work without else.

C:\src\php\php.local\src\phpmd\samples>

Hmmmm... it's not a case of else clauses being a problem it seems. It's a problem with else blocks being a problem.

It's complaining about this:

if ($firstChance) {
    echo "firstChance was true";
} else {
    echo "firstChance was false";
}


But not about this:

if ($firstChance) {
    echo "firstChance was true";
} else 
    echo "firstChance was false";
 


The only difference being the lack of braces on the second one.

Now I guess this makes an element of sense. The "untidiness" of large logic blocks is that they are more of a pain to navigate across when maintaining the code. With an if/else statement one needs to pay attention to where the if is, where the else is, which code is in which clause, and where the whole thing ends. This is not a problem if one keeps the code clean so each block has only 1-2 statements in it, but we've all seen if/elses that go on for miles and miles worth of scrolling down (and, it's worth pointing out: that is shit code if it's written like that. Don't do it). It's exacerbated by further nested logic within that. Fair enough.

But with a non-block else case - which intrinsically can only be one statement - the problem of unclean block sizes can simply not occur. Also fair enough.

But in that case, the warning should not be able else, intrinsically; it should be about the size of the blocks. And the size rule should probably apply to the if block too! If a single-statement else is OK, then a single-statement else block should be OK too.

I think the solution to this is that any else clause should be flagged: be it a single statement, or be it a block. So intrinsically an elseif should be flagged too. I'll raise this with the PHPMD bods.

Whilst on this: they should probably swing the other way and actually flag any instances of single-statement logic clauses: ifs and loops should always have braces. That's a widely accepted truism in curly-brace-language programming. I realise PHPCS does pick up on these (I only realise it just now cos I just tested it), but if PHPMD is busying itself with this sort of thing too; it should do a thorough job. In my view brace-less control logic is mess.


Lastly on this, I'm slightly irked by this, from the PHPMD Clean Code Rules page:

ElseExpression

[...]

An if expression with an else branch is never necessary.

It is terribly pedantic of me, but… it's not an expression. PHP flow control statements are…statements, not expressions. An expression is a specific thing: it is some code that represents a value. In PHP if/else statement does not represent a value. One cannot do this:
$message = if ($firstChance) {
    "firstChance was true";
} else {
    "firstChance was false";
}

If you cannot have some code on the right-hand side of an assignment (or anywhere else a value might be expected), then it's not an expression. A counterpoint to this, one can do this in Ruby:

message = if firstChance
    "firstChance was true";
else
    "firstChance was false";
end

puts message

This is because "everything is an object" in Ruby, I guess: even statements. In this case it seems the value of the if/else statement is that of the last statement in the relevant clause that gets executed by the logic. I'm buggered if I know whether Ruby best practices would encourage that (and by that I mean: I really actually don't know!), but I dunno that I'd be doing that sort of thing. Still: it demonstrates an if statement that is also an expression. Which a PHP one is not.

This sort of thing wouldn't bug me if it was in a casual conversation, but in an official implementation, I think it's a bit… ah… well let's saynit could just be done properly. But then again I guess this is PHP. And "doing it right" doesn't seem to be something PHP concerns itself with too much. It seems to be more "just get it done".

All this aside, I think PHPMD is a bloody handy tool, and whether or not I agree with the minutiae of some of its analysis or its turn of phrase is minor. It's helped my code a lot in the last coupla weeks. And I think our code in general is better of for using tools like this.

So… yeah. That was what I was doing yesterday (it was Fri when I started, it's now Sat evening and I'm in the pub). not scintillating stuff, but the examination of what was going on and the general experimentation was useful (to me, anyhow), I think. And it's enabled me to write a 2000-word blog article which is basically about elseif expressionsclauses.

Righto.

--
Adam

Monday, 9 January 2017

Testing: Too much detail, Cameron?

G'day:
Just really quickly here. Here's a question I posted on the Testing sub-channel of the #CFML Slack channel:

Question: I have a function which is basically a cron job which gets stuff from one data repository, monkeys with it, then sends the monkeying back to a different repository.

I log stuff along the way, for example:
  • "process started"
  • "got n records"
  • "need to update m records"
  • "job done"

Currently I am actively testing that "got n records" reflects the right value for n (where n in the context of the test is a test value coming back from a mock), and similarly with "need to update m records".

On one hand I think "well that's an important partial 'result' from the process, so - yes - test for it".

On the other hand I go "hmmm... is it? Or is that implementation detail?"

Thoughts?

That's it, really. Not an answer to anything today; just a question for you. What do you think?

--
Adam

Saturday, 7 January 2017

Survey: CFML usage and migration strategies

G'day:
This comes off the back of a few unrelated discussions I've had recently about people having moved on from CFML, and there being a reasonable desire to encourage and facilitate other people to do so too. There's a feeling that a lot of CFML devs might move away from CFML if they were helped to, but lack the confidence to make the leap themselves, or know where to start.

One suggestion floated was to start a project similar to ColdFusion UI the Right Way, but basically covering any part of CFML. Someone's suggested they might help giving examples of how to do stuff in Go; and I'd definitely help out giving examples in PHP (which might encourage people to stick with CFML, I dunno! ;-)

The motivation for this is that I think persisting with CFML is - for a lot of people in a lot of situations - career "assisted dying". I think for most CFML devs it's deleterious to stick with it. I'd like to help show these devs that other languages aren't scary, and indeed a bunch of them are similar in ease of use as CFML claims it is.

I've created a survey on freeonlinesurveys.com. I used to use Survey Monkey for this, but they now limit their free surveys to ten questions, and this one has 15. I've not tried freeonlinesurveys.com (either as a provider or a consumer), so I hope this works out. The UI for creating the surveys sure was easy to use though.

Anyway, the survey is here: "CFML usage and migration strategies". Go fill  it in if you can be arsed. To let you know what yer getting into, the survey is 15 free-flow questions, as follows. I've included my own answer for each question:
  1. Provide a brief comment about youself (don't worry about your CFML usage or dev work just yet: this is just about you). Don't worry if you'd rather not give too much detail, that's cool.
    This is my own answer: Adam Cameron. I'm London-based and (breaking my own rule slightly here) have been a dev for 16 years and in the IT industry for... blimey... 23 years.
  2. How did you come to be a developer, and are you primarily a developer or is it an adjunct to another role (like a sysop or designer or something like that)?
    I did programming at polytech, and loved it. I then took the first job that presented itself to me, which was as a sysop & desktop support bod on a NetWare network. I started using CFML there just before I went abroad for a few years. On return I got offered a job as a CFML dev with no professional programming experience at all.
  3. Summarise your CFML usage timeline (just timeline for this one). Include things like what year you started, when you moved on, if you have. (or what's the time timetable for moving on if it's just planned). Mention versions in this one.
    I started in 2001, and stuck with it until 2014. I shifted to PHP because the company I work for did. During that time I used all versions of CF from 4.5->9 professionally, but also have good knowledge of 10-12 as well, as I've been involved heavily in the CFML community and testing of ColdFusion's CFML implementation. I've never used any other CFML platform other than ColdFusion, other than experimentally & community participation.
  4. During that time was it your primary or sole dev language, do you think? Or was it always an adjunct to some other language? How did it fit within the mise en scene of your daily usage? For the purposes of this answer, let's consider "a wee bit of client-side JS and a bunch of HTML & CSS" as a given. Only mention those if they represent a significant part of your work.
    Yup. I did purely CFML from 2001-2014.
  5. Did you work on just in-house code bases for your employer, or did you also work on third party code bases - like open source projects - too.
    Just in house, and for client applications when I worked in a studio.
  6. If you're still primarily a CFML developer... why? That's not a loaded question, and I'm not suggesting that you're wrong for being where you are. It's just good framing information. Don't answer this if you've moved on from CFML: the next question is for you.
    I've moved on.
  7. If you've moved on from CFML: why? Did you just change jobs? Did other languages you were using just seem more appealing? Over time did you find yourself using CFML less and less? Did you actively change because of career-longevity considerations? What language(s) did you move to? Stuff like that.
    I was offered a job doing CFML, with no professional programming expertise at all, and it was a good way of getting away from doing network / hardware / desktop suppport work, which I didn't enjoy. That was my only reason for even starting with CFML: it was easy to pick up. I stuck with it cos I am lazy, and couldn't be arsed changing, and it paid the bills. By the time I decided I'd had enough I was tithed to my employer, so had to stick with them. We started a move to C# but that didn't take as we got bought out by a PHP shop. So now I do PHP. It was a good opportunity to shift languages, even if PHP would not have been my choice.

    My chief concern with changing languages is having to take a pay cut from "senior" dev to being a newbie in another language. I cannot sustain a pay cut.
  8. Do (/did) you use primarily or solely ColdFusion; or Lucee or Railo; or some variation of BlueDragon? In what proportions? If you migrated from one to another: why?
    ColdFusion. I never used any of the others professionally, but have messed around with all of them a fair bit.
  9. Do (/did) you participate in any CFML-based open source projects? To what degree (like you are the owner or primary committer of the project; or just a single commit; or raised some bugs but never actually coded anything; or wrote some docs, or whatever)?
    I raise bugs when I find them, and have helped a bit with cfdocs.org and luceedocs too. But never submitted a pull req.
  10. And what about in other languages?
    I've raised a bug in PHP 7 which got fixed. I have not directly contributed to any side projects though.
  11. What is or was - for you - the best feature of CFML which has you going "yeah, that's pretty cool actually". List more than one if you like. Importantly: had you compared similar functionality to how it's done in other languages, or was it just based on liking the CFML feature?
    That it's loosely and dynamically typed,which cuts down on pointless boilerplate code like one sees in Java. I didn't really compare to other languages too much.
  12. Are there any CFML features that would have fallen into that category for you when you were doing CFML, but ended up not being as cool as you thought when you looked at other languages?
    <cfquery> isn't really any easier to use than how it's done in any other language. That's a myth.
  13. What is it about CFML (or the underlying ColdFusion / Lucee / etc platform) you like the least? Explain why, if poss. Again, list as many or as few as you like. If this/these contributed to you moving on (if you have, I mean), mention that too.
    The vendor attitude. Adobe is slack at their job with ColdFusion, because they have too "enterprise" an approach to something that should be dynamic and agile. The community is very small and shrinking. Community expertise seems to migrate away, rather than steward the community (most of the community stalwarts still around aren't even CFML devs any more!)
  14. If there was a project similar to "ColdFusion UI the Right Way", but aimed at any part of CFML (like how to make a DB query in PHP instead of CFML for example), would you be keen to help on it? Would it be of interest to you to be a "user" of it?
    Well yeah. It was partly my idea.
  15. What else do you think might be useful to share?
    nothing really
So you could be writing a lot of stuff there: that's excellent if you do. Or you could write as little as you like: also cool, just slightly less so.

One of my Twitter contacts said that if I furnished the questions, they'd blog their thoughts. That's another thing you could do instead of doing the survey if you like: just let me know about it, as I don't follow any technical blogs these days (CFML or otherwise). Or you could reply in a comment here (although for me the survey would be easier to deal with, to be honest). Just whatever.

If you could also circulate word about this to your CFML contacts, that'd be cool too. I dunno how much notice the CFML community takes of me any more. Cheers.

That URL again: https://freeonlinesurveys.com/s/AuQ2WWKy#/0.

Or just RT it:



Righto.

--
Adam

PHP: looking into usage of the ::class constant

G'day:
There's not much to this article. It's arisen from the fact there's a paucity of docs on this subject on the PHP website, and they're quite hard to google for (given Google still insists on ignoring punctuation in searches, for some daftly ignorant reason. So searching for "php ::class" just gets one results for "php class". Not the same thing. Similarly seaching for "php ::class constant" doesn't help.

For the record, the two relevant docs pages are:


Update:

Kalle Sommer Nielsen has been in touch via Twitter and has pointed me to the original RFC for ::class, too: Request for Comments: Class Name Resolution As Scalar Via "class" Keyword. Now to be honest, the content of that RFC would make for better documentation than the current docs furnish. Obviously it'd need a slight tweak to reword some of the questions as statements, and if there were any changes between RFC and what was adopted, those should be included too. Kalle also said he's tweaked the docs slightly too, which is cool. Nice work fella!

I include those there as much for me to be able to easily re-find them as anything else.

In truth there's not much to this ::class construct. I just wish the docs were easier to locate. When discussing the feature, it's nice to be able to point to some docs. Or now: a blog article ;-)

So what does it do?

All classes have a built-in constant (::class) that contains a string that is the class's name. Same as what the get_class function will return for an object, but it works on the actual class too (because it's a class constant). Here are some examples:

<?php

namespace me\adamcameron\cc;

use com\example\other\SomeClassToAlias as AliasedClass;
use com\example\other\SomeClassToAlias;
use com\example\other\SomeOtherClass;

require_once realpath(__DIR__ . '/../vendor/autoload.php');

echo "Using ::class" . PHP_EOL;
printf("SomeClass: %s%s", SomeClass::class, PHP_EOL);
printf("SomeOtherClass: %s%s", SomeOtherClass::class, PHP_EOL);
printf("SomeClassToAlias as AliasedClass: %s%s", AliasedClass::class, PHP_EOL);
printf("SomeClassToAlias: %s%s", SomeClassToAlias::class, PHP_EOL);
printf("PHPUnit_Framework_Exception: %s%s", \PHPUnit_Framework_Exception::class, PHP_EOL);

$someClass = new SomeClass();
$someOtherClass = new SomeOtherClass();
$someAliasedClass = new AliasedClass();
$someClassToAlias = new SomeClassToAlias();
$phpunitFrameworkException = new \PHPUnit_Framework_Exception();

echo PHP_EOL . "Using get_class() on instance" . PHP_EOL;
printf("SomeClass: %s%s", get_class($someClass), PHP_EOL);
printf("SomeOtherClass: %s%s", get_class($someOtherClass), PHP_EOL);
printf("AliasedClass: %s%s", get_class($someAliasedClass), PHP_EOL);
printf("SomeClassToAlias: %s%s", get_class($someClassToAlias), PHP_EOL);
printf("PHPUnit_Framework_Exception: %s%s", get_class($phpunitFrameworkException), PHP_EOL);

In the example all the classes in the me\adamcameron or com\example namespaces are just empty classes, eg:

<?php

namespace me\adamcameron\cc;

class SomeClass {}

I've also included a PHPUnit class there as PHPUnit is a lib I'm aware of that doesn't use namespaces (dunno why), and wanted to see how that behaved, in case there were quirks: seemingly not.

Here's the output:

C:\src\php\php.local\src\oo\classConstant\src>php testWithNamespace.php
Using ::class
SomeClass: me\adamcameron\cc\SomeClass
SomeOtherClass: com\example\other\SomeOtherClass
SomeClassToAlias as AliasedClass: com\example\other\SomeClassToAlias
SomeClassToAlias: com\example\other\SomeClassToAlias
PHPUnit_Framework_Exception: PHPUnit_Framework_Exception

Using get_class() on instance
SomeClass: me\adamcameron\cc\SomeClass
SomeOtherClass: com\example\other\SomeOtherClass
AliasedClass: com\example\other\SomeClassToAlias
SomeClassToAlias: com\example\other\SomeClassToAlias
PHPUnit_Framework_Exception: PHPUnit_Framework_Exception

C:\src\php\php.local\src\oo\classConstant\src>

So you see there are no surprises really: ::class just returns the fully-qualified path of the class concerned. One thing to not is that it does not pay any attention to aliasing, whether on the class itself or on an object of that aliased class. I guess an alias is just for clarity in source code, rather than any sort of "renaming" exercise.

Where does one use ::class constants?

Well my primary usage is when mocking, using PHPUnit. Instead of this:

$mockedThing = $this->getMockBuilder('path\to\class\being\mocked\MockThisClass')
    ->disableOriginalConstructor()
    ->setMethod(['someMethod'])
    ->getMock();

We just have this:

$mockedThing = $this->getMockBuilder(MockThisClass::class)
    ->disableOriginalConstructor()
    ->setMethod(['someMethod'])
    ->getMock();

(and PHPStorm even includes the use statement for me, automatically):

use path\to\class\being\mocked\MockThisClass;

It's good to keep all the class pathing references together at the top of the file.

One flaw in this constant is this behaviour:
<?php

require_once realpath(__DIR__ . '/../vendor/autoload.php');

echo "Using ::class" . PHP_EOL;
printf("SomeNonExistentClass: %s%s", SomeNonExistentClass::class, PHP_EOL);

Any sensible person would probably want an error to be thrown there. But... no. PHP does this:

C:\src\php\php.local\src\oo\classConstant\src>php testWithNonExistentClass.php
Using ::class
SomeNonExistentClass: SomeNonExistentClass

C:\src\php\php.local\src\oo\classConstant\src>

Groan. Why does PHP insist on being "helpful" like this. The class doesn't exist! Just say that. FFS.

Oh well... that sort of thing is almost to be expected of PHP, I guess. Sigh. Well this ::class constant mostly a well-implemented, if not glamourous, small feature in PHP. And now I seem to have documented ::class more than PHP itself has. Heh.

Speaking of documentation, I can't help but think this ::class constant should also be mentioned on a coupla other pages in the PHP docs:

It'd be a good fit for both of those pages, plus that's where Google lands you if you search for it.

That's it. I have to dash to go visit my son for a few hours. Thanks to my colleague Carlos for pointing this whole thing out to me, btw. Both the ::class constant construct itself, but also the observation to be made about aliases. Nice one, fella.

Righto.

--
Adam

PS: apologies for the blatant SEO keyword stuffing of ::class constant in this article ("oops... I did it again"). It was by design.

Wednesday, 4 January 2017

PHP: looking at the Chain of Responsibility pattern

G'day:
We're working on a proof of concept for some stuff at work at the moment, and I've been put on a code review of the first round of the code. Part of the code is dealing with conditionally getting some data from cache, or if it ain't there, getting it from the DB instead: a fairly common trope. Normally I'd fall back on the decorator pattern for this, indeed I've written about it in the past ("Using a decorator pattern to reduce inappropriate code complexity"). I didn't see this in play in this first tranche of code, so suggested its use. The authoring dev suggested they felt that the decorator pattern was not a good fit for this, so they were using the Chain of Responsibility pattern instead. Another of our devs - when prodded - tended to agree.

I gave this some thought, read-up on the Chain of Responsibility pattern (and some more on the Decorator Pattern), and wasn't quite seeing it.

Just to back up a bit... if yer like me and kinda knew about the Chain of Responsibility pattern, but wasn't intimately familiar with it: read that Wikipedia link above, and do some googling of yer own. But in short it's characterised by having a task that needs doing, and having a sequence (a chain) of agents that possibly can (or possibly cannot) resolve all or part of the task. The process is that the task is passed to the chain of agents, and each in turn decides for itself whether it can fulfil the task in whole or in part. If an agent can resolve the whole task it does so: returning the result. If a given agent cannot fulfil the whole task (either it cannot fulfil it at all, or can only do part of it), it does its work, then passes responsibility on to the next task in the chain. Each agent knows two things: how to do its part of the job, and that it can pass work on to the "next" agent. It does not (and should not!) know what the next agent does, nor should it rely on what the next agent does. It simply does its job and returns it or passes on incomplete work to the next agent in the chain. I can't be arsed with a diagram: go google one.

Now: for two thirds of our challenge, I think the Chain of Responsibility pattern is a good fit: for the "Is it in the cache? Yes [/No. Is it in the DB? Yes/No]" bit. No qualms there. However it sticks in my craw once we get to the step after getting from the DB: putting it in the cache for next time. That step does not belong in the chain as it's not performing the same sort of task as the other two: getting the data. Second to that it's hitting the cache before and after the DB step, which makes it more seem like a decoration to me (of sorts... I might get back to the "of sorts..." bit later). Even if one decides "get from cache" is one agent's job, and "get from DB, oh and put it in the cache" is another agent's job, that latter agent is doing a mishmash of "unrelated" work, and seems to be a bit tightly-coupled to me, as well as having a wodge of code (that's a technical term) doing two things, rather than two wodges of code doing one thing each.

That said, I don't like being so emphatic about "that's wrong" without being happy about my understanding of things, so figured I should try to make it work in a way that sits well with me in a proof of concept. That and it seemed like good material for a blog article.

In my proof of concept I am fetching some Person data, for example:

$person = $personService->getById(1);

The PersonService defers to a "RetrievalHandler" for this:

class PersonService {

    private $retrievalHandler;

    public function __construct(PersonRetrievalHandler $retrievalHandler) {
        $this->retrievalHandler = $retrievalHandler;
    }

    public function getById($id) : Person {
        $record = $this->retrievalHandler->getById($id);

        if (is_array($record)){
            $person = new Person($record['firstName'], $record['lastName']);

            return $person;
        }
        return new Person();
    }
}


This just finds and returns a Person object, implement thus:

class Person {

    public $firstName;
    public $lastName;

    public function __construct($firstName=null, $lastName=null) {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }
}

As noted, the service uses a the RetrievalHandler to retreive stuff. A handler is implementation of this:

abstract class PersonRetrievalHandler {

    protected $nextHandler;
    protected $logger;

    public function __construct() {
        $this->nextHandler = new class {
            function getById(){
                return null;
            }
        };
    }

    public abstract function getById(int $id) : array;

    public function setNextHandler(PersonRetrievalHandler $nextHandler) {
        $this->nextHandler = $nextHandler;
    }
}

All it's required to do here is know how to use getById, and also it can setNextHandler. I'll come back to the code in the constructor later. Don't worry about that for now.

I've got two implementations of this. One for getting the thing from a caching service:

class CachedPersonHandler extends PersonRetrievalHandler {

    private $cacheService;

    public function __construct(CacheService $cacheService){
        parent::__construct();
        $this->cacheService = $cacheService;
    }

    public function getById(int $id) : array {
        if ($this->cacheService->exists($id)) {
            return $cachedPerson = $this->cacheService->get($id);
        }

        return $this->nextHandler->getById($id);
    }
}


And one for getting it from a database repository:

class DatabasePersonHandler extends PersonRetrievalHandler {

    private $repository;

    public function __construct(PersonRepository $repository) {
        parent::__construct();
        $this->repository = $repository;
    }

    public function getById($id) : array {
        $record = $this->repository->getById($id);
        if (count($record)) {
            return $record;
        }

        return $this->nextHandler->getById($id);
    }
}


Note this is very woolly and not production-sound code. It's just to demonstrate the concept.

Important to note in both these situations is the agents don't themselves do any of the work; they just try to get the work done by something else, and return it if done, or pass the task to the next agent if the current agent itself couldn't fulfil the requirement.

I've created a stub caching service and repository:

class CacheService {

    private $cache = [];
    private $logger;

    public function __construct(LoggingService $logger)
    {
        $this->logger = $logger;
    }

    public function get($key) {
        $this->logger->info("Cache hit for $key");
        return $this->cache[$key];
    }

    public function exists(string $key) : bool {
        $exists = array_key_exists($key, $this->cache);

        $this->logger->info(sprintf("Cache %s for %d", $exists ? 'hit' : 'miss', $key));

        return $exists;
    }

    public function put(string $key, $value) {
        $this->cache[$key] = $value;
    }
}


class PersonRepository {

    private $data = [
        [],
        ['firstName' => 'Tui', 'lastName' => 'Tahi'],
        ['firstName' => 'Rewi', 'lastName' => 'Rua'],
        ['firstName' => 'Tama', 'lastName' => 'Toru'],
        ['firstName' => 'Whina', 'lastName' => 'Wha']
    ];
    private $logger;

    public function __construct(LoggingService $logger)
    {
        $this->logger = $logger;
    }

    public function getById($id) {
        if (array_key_exists($id, $this->data)){
            $this->logger->info("Database hit for $id");
            return $this->data[$id];
        }
        $this->logger->info("Database miss for $id");
        return [];
    }
}


These also log activity in a very naïve way:

class LoggingService {
    public function info(string $message) {
        echo $message . PHP_EOL;
    }
}


All of this is stitched together in a factory, as there's a lot of moving parts:

class PersonServiceFactory {

    public static function getPersonService()
    {
        $loggingService = new LoggingService();
        $cacheService = new CacheService($loggingService);
        $personRepository = new PersonRepository($loggingService);

        $cachedHandler = new CachedPersonHandler($cacheService);
        $databaseHandler = new DatabasePersonHandler($personRepository);

        $cachedHandler->setNextHandler($databaseHandler);

        $personService = new PersonService($cachedHandler);

        return $personService;
    }

}


Note how the only knowledge each agent has of another is what a given agent's next handler might be. Speaking of next handlers. let's go back to that code in the PersonRetrievalHandler's constructor:

public function __construct() {
    $this->nextHandler = new class {
        function getById(){
            return null;
        }
    };
}


You'll remember each getById implementation will either fulfil the task and return the result, or it'll defer to the next agent in the chain. This is just a mechanism to make this "work" even if the last agent in the chain calls return $this->nextHandler->getById($id). The base class will take the call and just return null. I was semi-pleased to be able to use a class literal here (or anonymous class as PHP wants to call them). So this means I do not need to set the next handler on the $databaseHandler: it'll just use the inbuilt one if it cannot fulfil the requirement itself.

Right so we can now run this to see what happens. Remember the expectation is that the requested data will be fetched from the cache as a priority, but if not found: get it from the DB. Note that I am not doing anything here about putting the data into the cache if it wasn't there in the first place. I'll get to that. Anyway, here's some tests:

$personService = PersonServiceFactory::getPersonService();
$person = $personService->getById(1);
var_dump($person);
$person = $personService->getById(1);
var_dump($person);
$person = $personService->getById(4);
var_dump($person);
$person = $personService->getById(5);
var_dump($person);

Here we're:
  • getting the same person twice to start with: ostensibly this'd demonstrate a cache-miss then a cache-hit;
  • getting a different person from the first (back to a cache miss);
  • getting a person who is neither in the cache nor the DB.

Results:

C:\src\php\php.local\src\chainOfResponsibility\public>php manualTest.php
Cache miss for 1
Database hit for 1
object(me\adamcameron\cor\model\Person)#9 (2) {
  ["firstName"]=>
  string(3) "Tui"
  ["lastName"]=>
  string(4) "Tahi"
}
Cache miss for 1
Database hit for 1
object(me\adamcameron\cor\model\Person)#10 (2) {
  ["firstName"]=>
  string(3) "Tui"
  ["lastName"]=>
  string(4) "Tahi"
}
Cache miss for 4
Database hit for 4
object(me\adamcameron\cor\model\Person)#9 (2) {
  ["firstName"]=>
  string(5) "Whina"
  ["lastName"]=>
  string(3) "Wha"
}
Cache miss for 5
Database miss for 5
object(me\adamcameron\cor\model\Person)#10 (2) {
  ["firstName"]=>  NULL
  ["lastName"]=>  NULL
}

C:\src\php\php.local\src\chainOfResponsibility\public>

That's all good:
  • the cache never finds anything (although it does look),
  • so passes the job on to the DB. The DB will return the data if found,
  • otherwise it'll fall back to the default agent which just returns null.


So far: so good. But why did I omit the "putting it into the cache" step. Well... using this model, it simply won't work. The rule is that if an agent can resolve the work, then it returns it. Otherwise it defers to the next agent. I figured I could implement a "pass-thru" agent which just caches the result found by the DB agent: job done. But if the DB agent finds the record - read: "it can fulfil the task" - then it doesn't pass the task on to the next agent. It considers the task fulfilled, so just returns its result. So the only time the "put it in cache" agent will be called is if the DB agent doesn't find anything. Precisely the wrong time to cache something. That aside, the agent only passes on the inputs (the $id) to the next agent, so there's no mechanism here for an agent to receive the result from the previous agent even if the result was there to pass.

What we'd need to do here is just use the chain of responsibility to get the data, and perhaps leave it to the PersonService to cache the result it receives. However this is a bit unbalanced in my view, as caching considerations of the same data are being handled at not only two different places in the code, but two different "levels" of the task at hand. That's a bit clumsy in my book.

Next I thought I could adjust the DB handler to unconditionally pass its result on to the next handler, rather than break out of the chain once done. But that's a bit crap as it makes the DB handler behave differently than the other handlers in the chain, which also implies it knows more about what's going on than it should: ie that there's a reason to always pass the result on, rather than return it if it's done the work. Not cool.

Update:

My colleague (a different one) suggested a third open: roll the cache-put into the CachedPersonHandler:

class CachedPersonHandler extends PersonRetrievalHandler {

    private $cacheService;

    public function __construct(CacheService $cacheService){
        parent::__construct();
        $this->cacheService = $cacheService;
    }

    public function getById(int $id) : array {
        if ($this->cacheService->exists($id)) {
            return $cachedPerson = $this->cacheService->get($id);
        }

        $person = $this->nextHandler->getById($id);
        $this->cacheService->put($id, $person);
        return $person;
    }
}

Well: indeed. Now it's basically a decorator: you've decorated a DB call with some caching. Contrast it with this from my earlier article on the Decorator Pattern:

class CachedUserRepository implements UserRepositoryInterface {

    private $repository;
    private $cacheService;

    public function __construct(UserRepositoryInterface $repository, CacheServiceInterface $cacheService) {
        $this->repository = $repository;
        $this->cacheService = $cacheService;
    }

    public function getById($id) {
        if ($this->cacheService->isCached($id)) {
            return $this->cacheService->get($id);
        }
        $object = $this->repository->getById($id);

        $this->cacheService->put($id, $object);
        return $object;
    }

}

You've arrived at the same conclusion I started with ;-)

I did try one other tactic. Instead of passing just the inputs to the agents, I had a look at passing the inputs and the outputs through the chain, and also always passing on to the next agent, irrespective of whether a given handler arrived at the result. This was very proof-of-concept-y. It's possibly a legit approach if the Chain of Responsibility was one in that a given agent was only supposed to fulfil part of the solution, relying on all links in the chain to arrive at the complete result. This is not what I'm doing here, but decided to leverage this approach to get the caching working "properly".


Intermission

Go have a pee or get some more popcorn or something.


Now I have an interface for a Handler:

interface Handler {

    public function perform($request, $response);

}


This just reflects a more generic solution than getById. More importantly note that unlike getById, perform takes a request and a response. For our examples the "request" would be the ID. And the response would be any result a given agent was able to come up with.

This is implemented in an abstract way by PersonRetrievalHandler:

abstract class PersonRetrievalHandler implements Handler {

    protected $nextHandler;
    protected $logger;

    public function __construct() {
        $this->nextHandler = $this->getPassThroughHandler();
    }

    public function setNextHandler(PersonRetrievalHandler $nextHandler) {
        $this->nextHandler = $nextHandler;
    }

    private function getPassThroughHandler(){
        return new class  {
            function perform($_, $response){
                return $response;
            }
        };
    }
}

It's still down to the individual handlers to implement the perform method though.

I've also refactored that class literal out into a helper method; and its perform method now returns whatever response it was passed, rather than returning null.

The perform method in CachedPersonHandler is thus:

public function perform($request, $response=null) {
    $id = $request;
    if ($this->cacheService->exists($id)) {
        $response = $this->cacheService->get($id);
    }

    return $this->nextHandler->perform($request, $response);
}


Note that it doesn't return the result itself, it just passes it on to the next agent. Otherwise it's doing the same thing.

And the DatabasePersonHandler has also been slightly refactored:

public function perform($request, $response=null) {
    if (is_null($response)){
        $id = $request;
        $response = $this->repository->getById($id);
    }

    return $this->nextHandler->perform($request, $response);
}


It infers that the cache didn't find anything by it not receiving anything in the response, and only if so does it hit the DB. Otherwise it just passes everything through to the next agent.

Now we have a third handler, CacheablePersonHandler:

class CacheablePersonHandler extends PersonRetrievalHandler {

    private $cacheService;

    public function __construct(CacheService $cacheService){
        parent::__construct();
        $this->cacheService = $cacheService;
    }

    public function perform($request, $response=null) {
        if (!is_null($response)) {
            $id = $request;
            $this->cacheService->put($id, $response);
        }

        return $this->nextHandler->perform($request, $response);
    }
}


This one makes no pretence about finding data, it just takes any received response and caches it, keyed on the request. And passes the response on. At this point remember the default handler will just return whatever response it receives.

We wire all this together in the factory again:

public static function getPersonService()
{
    $loggingService = new LoggingService();
    $cacheService = new CacheService($loggingService);
    $personRepository = new PersonRepository($loggingService);

    $cachedHandler = new CachedPersonHandler($cacheService);
    $databaseHandler = new DatabasePersonHandler($personRepository);
    $cacheableHandler = new CacheablePersonHandler($cacheService);

    $databaseHandler->setNextHandler($cacheableHandler);
    $cachedHandler->setNextHandler($databaseHandler);

    $personService = new PersonService($cachedHandler);

    return $personService;
}


This differs only in that it also creates the CacheablePersonHandler too, and stick it at the end of the chain.

Here's our revised test:

$personService = PersonServiceFactory::getPersonService();

foreach ([1,1,4,5,5] as $id){
    $person = $personService->getById($id);
    var_dump($person);
}


(it's the same, just without the tautological repeated duplication of the same stuff again).

And the result:

C:\src\php\php.local\src\chainOfResponsibility\public>php manualTest.php
Cache miss for 1
Database hit for 1
Cache put for 1
object(me\adamcameron\cor\model\Person)#8 (2) {
  ["firstName"]=>
  string(3) "Tui"
  ["lastName"]=>
  string(4) "Tahi"
}
Cache hit for 1
Cache put for 1
object(me\adamcameron\cor\model\Person)#11 (2) {
  ["firstName"]=>
  string(3) "Tui"
  ["lastName"]=>
  string(4) "Tahi"
}
Cache miss for 4
Database hit for 4
Cache put for 4
object(me\adamcameron\cor\model\Person)#8 (2) {
  ["firstName"]=>
  string(5) "Whina"
  ["lastName"]=>
  string(3) "Wha"
}
Cache miss for 5
Database miss for 5
object(me\adamcameron\cor\model\Person)#11 (2) {
  ["firstName"]=>
  NULL
  ["lastName"]=>
  NULL
}
Cache miss for 5
Database miss for 5
object(me\adamcameron\cor\model\Person)#8 (2) {
  ["firstName"]=>
  NULL
  ["lastName"]=>
  NULL
}

C:\src\php\php.local\src\chainOfResponsibility\public>

So it "works", in that DB hits are now going into the cache.

But it also re-caches cache hits too :-(

Sigh. I could work around this by doing an exists call in the latter handler too:

public function perform($request, $response=null) {
    if (!is_null($response)) {
        $id = $request;
        if (!$this->cacheService->exists($id)) {
            $this->cacheService->put($id, $response);
        }
    }

    return $this->nextHandler->perform($request, $response);
}


But it all seems a bit clumsy to me. And when something seems clumsy to me, I start wondering if this square peg is supposed to be going in this round hole. And my conclusion is generally: "no".

The problem here goes back to the "cache the data" part of this chain doesn't belong in the chain. It's not a congruent task compared to the data-fetch tasks. The "responsibility" in the "Chain of Responsibility" is that the responsibility is "fulfilling the task". The caching agent makes no attempt to fulfil the requirement of the chain, so it doesn't belong in the chain to me.

So where does it belong? Well it belongs as near as possible to when we know the database was hit, and we have the result. So we could put it in the database agent. But then we have this imbalance that the cache-look-up is part of the chain, and the cache-put is within part of the chain. I don't like that asymmetry. Also it means the DB agent is doing two things: getting data and caching it, which seems like it's doing too much to me: it needs to know how to do two different things. We could go down the route of having like a CachedPersonRepository, and that at least removes those two responsibilities from the agent, but it does just move them: now the repo needs to know about the DB and the caching.

What I'd end up doing is using a caching decorator around a DB-only repository. That's symmetrical, and all concerns are separated.

Hmmm.

Second Intermission

Go find a suitable blade so you can open a vein and end it all instead of having to read on… (and on…)


Oh, as a footnote to this another requirement I wanted to prove is that the chain links behaved "sensibly" if called without their adjacent agents. IE: the DatabasePersonAgent agent didn't need the CachedPersonAgent, nor did it need the CacheablePersonAgent.

Here's some examples:

Just the database handler:

$loggingService = new LoggingService();
$personRepository = new PersonRepository($loggingService);

$databaseHandler = new DatabasePersonHandler($personRepository);

$personService = new PersonService($databaseHandler);

foreach ([1,5] as $id){
    $person = $personService->getById($id);
    var_dump($person);
}


Result:

C:\src\php\php.local\src\chainOfResponsibility\public>php manualTestWithJustDatabaseHandler.php
Database hit for 1
object(me\adamcameron\cor\model\Person)#7 (2) {
  ["firstName"]=>
  string(3) "Tui"
  ["lastName"]=>
  string(4) "Tahi"
}
Database miss for 5
object(me\adamcameron\cor\model\Person)#8 (2) {
  ["firstName"]=>
  NULL
  ["lastName"]=>
  NULL
}

C:\src\php\php.local\src\chainOfResponsibility\public>

Yup: it's getting data from the DB, and doesn't require the other agents.

Now one with just the CachedPersonHandler:

$loggingService = new LoggingService();
$cachingService = new CacheService($loggingService);

$cachedHandler = new CachedPersonHandler($cachingService);

$personService = new PersonService($cachedHandler);

$cachingService->put(5, ['firstName'=>'Ria', 'lastName'=>'Rima']);

foreach ([1,5] as $id){
    $person = $personService->getById($id);
    var_dump($person);
}

Result:

C:\src\php\php.local\src\chainOfResponsibility\public>php manualTestWithJustCachedHandler.php
Cache put for 5
Cache miss for 1
object(me\adamcameron\cor\model\Person)#7 (2) {
  ["firstName"]=>
  NULL
  ["lastName"]=>
  NULL
}
Cache hit for 5
object(me\adamcameron\cor\model\Person)#8 (2) {
  ["firstName"]=>
  string(3) "Ria"
  ["lastName"]=>
  string(4) "Rima"
}

C:\src\php\php.local\src\chainOfResponsibility\public>

Here I'm priming the cache manually, just to make sure it'll find the cached item OK. And it does.

For the sake of completeness I also test with just the cache-put agent. It won't do anything useless (a warning flag in itself), but it should "work":

$loggingService = new LoggingService();
$cachingService = new CacheService($loggingService);

$cacheableHandler = new CacheablePersonHandler($cachingService);

$personService = new PersonService($cacheableHandler);

foreach ([1,5] as $id){
    $person = $personService->getById($id);
    var_dump($person);
}


Result:

C:\src\php\php.local\src\chainOfResponsibility\public>php manualTestWithJustCacheableHandler.php
object(me\adamcameron\cor\model\Person)#7 (2) {
  ["firstName"]=>
  NULL
  ["lastName"]=>
  NULL
}
object(me\adamcameron\cor\model\Person)#8 (2) {
  ["firstName"]=>
  NULL
  ["lastName"]=>
  NULL
}

C:\src\php\php.local\src\chainOfResponsibility\public>

This is all unhelpful to the stated goal, but it's doing what it's told and is self-contained. "Win".

In conclusion I think this has helped me get a handle on the Chain of Responsibility pattern, which is good. I still want to have a look at chains wherein the agents achieve partial fulfilment, and possibly some recursion as well. That'd be cool.

But I really don't think this is the right solution to the job at hand at work.

I'll leave you be now. Oh... as per usual: all the code is on Github, including the working application. The first iteration of the work is tagged separately.

Righto.

--
Adam

Sunday, 1 January 2017

20162017

G'day:

Update, 2017-01-01:

I've tweaked this slightly today to... erm... strengthen some of the language, and add back in a coupla passages that for some reason got scuppered in the self-imposed "editorial rush" last night.

And - yes - take note ye shrinking violets: I use coarser language even than usual in this article.


Well, yes... 2016 was a disappointing year, yeah? Like many other people will be doing / have already done, I could rattle off a list of celebrities that have died, and offer that as "proof" that 2016 was a bit of a poor showing. Because, you know, that's how we measure how good or bad a year is these days, right? Celebrity death.

FFS, the real reason why 2016 has been such a bloody disappointment is because our "civilisation" has got to the point that that is how we measure things. Via celebrity head count. Seriously... I feel a sense of empathy with the families of all these noted people who have died this year, cos having a family member die really fucking sux. But I don't really know why we're all carrying on like the death of [insert name of celebrity here] in any way changes our own blimin' lives. Yeah, sure, those dead musicians are not going to produce any more albums for us to listen to. But plenty of other people will produce albums to listen to. We can listen to those ones. And movie stars? Do people really measure impact on their lives by whether they get to see another movie starring [name of some actor here]? Really? Are we so detached from reality that we think these people are parts of our lives?

What's happened to our society that we're all wailing an gnashing our teeth over this sort of thing?

There has been a bunch of actual shitty things that happened this year.

The UK demonstrated it couldn't be trusted to vote in a referendum about the continued viability of itself as an international consideration: instead decided to consign itself to ultimately be marginalised archipelago off the coast of Europe. The Europe we used to belong to. The Europe we were better for being part of, and that was better for us to be part of. Nah. We were all a bit easily swayed by shit-eating-grin-wearing nazi-wannabes like Nigel Farage, who managed to convince us that there's too many immigrants here already, and this is a problem. I realise it's now seen as a bit trite and simplistic to suggest the exit-voters were all about stopped those that "come over here, and take our jobs", but I've yet to see any evidence that that's not why the vote went the way it does. What's worse is that when speaking to exit-voters about these foreigners "coming over here... etc", I point out to them that I'm a foreigner who has specifically come over here to take a job. Without a pause for breath I'm reassured that immigrants like me are OK. And it's not cos I'm a Kiwi. It's cos I'm fuckin' white and I speak English (kinda ;-). I'm so embarrassed for this country - my home - that there are so many people who think this way that they have an impact on national policy.

One of the actual bad things about 2016 is that the UK fucked itself. It voted to fuck itself. We've become Country McCuntFace.

Oh... Jesus... then there's that freak that the States elected to be president. The one good thing about that is that it stopped making Poms look collectively stupid, and reminded us that USA will always go one better than anyone else. Even if it's in the "how to fuck up a vote and ruin your country" stakes. Even if Trump doesn't do anything stupid (and there's no chance of that happening), you've made yerselves an international laughing stock for the next four years. Oh, fuck it, knowing you lot it'll be eight years.

The jury is still out as to whether you've inadvertently elected a Russian goon as president too. Inadvertently in a two-fold way: the electorate certainly didn't mean to; and - less certainly - Trump doesn't realise he's being played. That one could pan out either way, I think.

At the same time the Four Horsemen are present and accounted for with the same list of war, famine, pestilence (oh, and the other one is "conquest", apparently... had to look that one up) that goes on all year every year. Because we don't bloody learn. Because we care about celebrities but not actual people. Especially fucking foreigners (ask yerself this quickly: how many marketplace bombs were detonated in various countries around the world today? How many can you name? How many innocent people just doing their shopping died? I read of a couple, but I too cannot name where they were, and the body count didn't seem memorable).

Second update:

To be clear, I posted this before the well-publicised attack in Istanbul. This news broke about 15min after I posted the article :-(


If the media is to be believed, we still can't even collectively own-up to screwing the planet's climate, ultimately at our expense. I can't believe that debate is still going on. Well it's not a debate. There is no indecision. Except for the media's agenda of perpetuating an idea that there is a debate.

Another real problem is encapsulated in that previous para: "if the media is to be believed". Who the fuck knows if the media can be believed any more? I used to think I could at least trust the Beeb and the Guardian to deliver my slightly-left-of-centre truth each day. But it's becoming more and more clear that the editorial slant of all news organs is now more important to them than actually telling us what's going on. I can't believe people read the Daily Mail or watch / read Fox News as a mechanism for becoming informed. Well I should believe it, because... well... refer back to the UK EU Membership Referendum and the US general election. That said: really a lot of people didn't vote in the UK referendum, and a cadre of morons in the States decided they couldn't bring themselves to vote for Clinton (because they wanted Bernie instead or some other fucking stupid excuse)... if only to stop Trump.

At the same time fascist freaks like Le Pen and Wilders in their respective countries seem to be gaining traction via a similar way of public thinking. Earlier in the year people were "celebrating" that the far right was defeated in Austria, whilst glossing over the fact that they were only just defeated. 46% of the vote went to the nazi-lite bloke.

There's 14min of 2016 left.

Back to the celebrity thing. if that's yer yardstick of how to measure "good years" vs "bad years", then yer in for a shock. Statistically the baby boomers are gonna start dying now. We're seeing the periphery of this happening now (statistical outliers). It's only going to get "worse". It's also going to get worse because the bulk of the people wringing their hands about such things grew up in a fairly golden time without much war or conflict or challenge in their lives, so "my favourite musician just died" really does seem to them to be something significant in their lives. Plus we've also grown-up in an age of increased "communications intrusion" into our lives. So we're just exposed to more celebrity than earlier generations used to. So we'll be finding a sea of dead celebrities to get upset about in 2017 too. And perhaps 2018, if Trump and Putin allow us to have one.

And all the while we will focus on this, and soak up the bread and circuses that our media will feed us whilst the truly shitty things in this world continue unabated.

So that's uplifting.

For me, 2016 was as follows:

  • my sister did not die of cancer. That was on the cards at the beginning of the year, but she's OK.
  • My brother seems none the worse for wear after a heart-attack in 2015. That's cool.
  • My dad died. I have concluded I am simply never going to move on from that.
  • My mum is in a secure ward at her care home, as she's a bit too loopy these days to be allowed out in public.
  • My son is 5, and he's a dude! I don't get to see him as often as I'd like. More often than his mother would like though, I think ;-)
  • I have blood pressure "issues", but they seem mostly OK at the moment.
  • My job is still pretty good. Perhaps not as good as it was a coupla years ago, but there's opportunities to be taken advantage of still. It's cool.
  • I've got excellent friends and rellies(**) and - on the whole - I don't have much to gripe about.
  • Oh! I have a mate who's Nan was 99 throughout most of 2016, and she's still around now it's 2017. I've never met her, but this pleases me.

As for 2017 (and, bugger it, typing this in a second time as it didn't save the first time):

  • I'm about ready to give up on the UK. I might shift to Ireland if I can. I have a job there if I want it (yes, boss, I do!), and Dublin is a lot closer to Galway where my boy is than London is.
  • I'm gonna go to NZ one last time to watch cricket with me mates (South Africa will be handing NZ it's arse on a plate, but hey: it's still cricket). After that I intend to only return for family funerals.
  • More likely than not I will simply watch my health than do anything about improving it. :-/
  • At work I've had a rough patch for the last month or so in which I've been asking "remind me again why I subject myself to this each day? Oh, a visa. That's right", but I hope to get back into my rhythm of really enjoying going to work each day and hanging out with me team.
  • I do also need to do something about keeping myself "technologically fresh". I need to study more and experiment more.
  • First things first though: I need another beer (*).

Oops... I meant to press "send" bang on 00:00:00. Missed it by a few sec.

Righto.

--
Adam


(*) just in case you wondered... no, not really drunk when writing this: I've had three beers over the course of as many hours. The poor quality of the writing is down to being in a rush, as I only decided to write this at 11:30pm, and wanted it done by midnight.

(**) I have two entire other families (long story) I did not mention in this article. But only cos they're all trucking along fine.