using Programming;

A Blog about some of the intrinsics related to programming and how one can get the best out of various languages.

Getting Started with PHP: Part 2

Getting started with PHP: Part 2

In our last discussion we got our PHP environment setup, and we talked about some basic parts of PHP:

  • Statements end in semi-colons (;);
  • We use echo to output things to the caller;
  • There are several comparison operators (==, ===, !=, !==, >, <, >=, <=, <=>);
  • We can use if to change what a program does;
  • Variables ($var);

This got our adventure started, so I want to start moving more into PHP as a language. We're going to start building example products, and talking about the features as we do. We will talk about standard functions, special variables and more.

Let's build a form to show the user's name

For this, we're going to build a web-form that we use to ask for a user's name, and then use the logic we used previously to say hello.

First we'll create a PHP file as we did before. The name is only important in-so-far as you will need to remember it for your browser.

Next, we need a form to show ask the user for their name. For this, we're going to talk about PHP strings for a moment.

PHP: Strings

PHP has 2 different types of strings, and two different ways to create each one. The types are:

Delimited double-quoted strings:

$name = "Elliott";

Delimited single-quoted strings:

$name = 'Elliott';

HEREDOC string (double-quoted):

$name = <<<NAME
Elliott
NAME;

NOWDOC string (single-quoted):

$name = <<<'NAME'
Elliott
NAME;

So, at this point you are likely wondering why PHP has 4 different strings. The logic behind why is actually quite sound, so we'll go over it at a rough-level.

First, single-quoted and double-quoted strings behave differently. Likewise, NOWDOC and HEREDOC behave differently, matching their single-quoted or double-quoted counterpart. A double-quoted string allows you to embed variables within it, such as our previous "Hello $my_name". However, a single-quoted string does not allow this. If we changed our string to 'Hello $my_name', we would get Hello $my_name instead of Hello Elliott as our output.

Next, in delimited strings we have to escape the delimiter if we want to embed it within our string. This means if we want to put the delimiter itself in the string (which we often do with HTML/CSS/JavaScript), we need to escape it, such as:

$html = "<form method=\"POST\"></form>";

Notice how the quotes around "POST" are preceeded by a slash (\). This means that the literal double-quote (") will be put there instead of the escape \".

Enter HEREDOC and NOWDOC: because they don't use delimiters you can include the delimiters in the string freely:

$html = <<<HTML
<form method="POST"></form>
HTML;

The rules around HEREDOC and NOWDOC are as follows:

  1. The string begins with three less-than signs (<<<) and then a name, then the line ends;
  2. The string can contain any characters, and most importantly, the delimiters (" / ') do not need to be escaped;
  3. The string ends when a line that begins with the original name and then a semi-colon is encountered (there may be no whitespace at the beginning of the line);
  4. If the opening name is wrapped with single-quotes, it is a 'NOWDOC' (which acts like a single-quoted string), otherwise it is a HEREDOC (which acts like a double-quoted string);

By "acts like a -quoted string", I mean it either replaces variables like a double-quoted string, or it does not like a single-quoted string.

One key note about HEREDOC and NOWDOC is that you cannot use concatenation in them directly, you must create intermediary strings for that:

$html = <<<HTML
<form method="POST">
HTML;

$html = $html . <<<HTML
</form>
HTML;

That said, both HEREDOC and NOWDOC handle line-continuations / CR;LF; properly, so you could simply do:

$html = <<<HTML
<form method="POST">
</form>
HTML;

Back to our Form

Now that we know about strings, we need to start getting our form ready. In PHP, we can do this either by echo-ing the form, or by ending the PHP tags and including the form as raw data.

For our form, we only need two items within it: a text-box for the name, and a button to submit it. Our HTML might look something like the following:

<form method="POST">
    <input type="text" name="name" />
    <input type="submit" name="submit" value="Say Hello" />
</form>

The name on each of these is important as that is how PHP will access the values.

To see this in action, let's create a PHP page with only that in it. No <?php tags or code at all. If you did it properly, you'll see a webpage with a single text-box and button that says "Say Hello".

This demonstrates that we don't need to echo everything, we can just exit the PHP context and write it out, PHP will output it for us.

Obviously we don't always want to show the form, so it's time we talk about how PHP can access the data that an HTML form sends back.

PHP: Posting

When an HTML form posts data back to a server, it sends a request with the Method: POST header, and a body with each name/value pair encoded as name=encodeValue(value), concatenated with ampersands (&). Thus, if we typed Elliott into our text box and clicked "Say Hello", it would post name=Elliott&submit=Say Hello to our server.

PHP has several "superglobals", which are built-in variables that do special things. The two we'll talk about right now are $_GET and $_POST. You can access both of these from any PHP code.

PHP: Arrays

Before we go deeper into superglobals, let's go over arrays very quickly.

In PHP, an array is a key => value mapping. That is: it has any number of unique keys that have exactly one value. Keys can be anything, most commonly they are integers and strings.

There are two ways in PHP 7.4.6 to construct arrays:

$some_array = array (
    "Key 1" => "Value 1",
    "Key 2" => "Value 2"
);
$some_array = [
    "Key 1" => "Value 1",
    "Key 2" => "Value 2"
];

Both of these methods create the same array, and allow us to use $some_array["Key 1"] and $some_array["Key 2"] to access the values in it. We can also use that to set the values, so we could say $some_array["Key 1"] = "New Value";. Additionally, we can add new keys to the array by doing $some_array["Key 3"] = "Value 3";. Additionally, PHP has a built-in function named print_r that will output the contents of an array for us to troubleshoot.

If we change our PHP page to the following:

<?php
$some_array = array (
    "Key 1" => "Value 1",
    "Key 2" => "Value 2"
);
$some_array = [
    "Key 1" => "Value 1",
    "Key 2" => "Value 2"
];
$some_array["Key 1"] = "New Value";
$some_array["Key 3"] = "Value 3";
print_r($some_array);
?>

We will get the following output:

Array ( [Key 1] => New Value [Key 2] => Value 2 [Key 3] => Value 3 )

This shows us the Key 1 did change, and that Key 3 was added.

Additionally, one other thing we need to discuss on arrays is how to determine if a key exists. Specifically, the isset(...) function.

If we have an array, and we don't know if our key is in it, we use isset( $array[$key] ) to test for it. If we try to use $array[$key] directly, we will get an error:

Notice: Undefined index: $key in $file on line $line

This will put ugly output on our page, and while we can suppress it that doesn't help us: the best way to avoid it is test for it.

To do that, we might do the following:

if (isset($_POST["name"])) {
    // do something with our name
}

Back to Superglobals

The $_GET and $_POST superglobals are arrays where the key comes from a component of the request, and the value comes from that value. As an example, our $_POST will have two keys: $_POST["name"] and $_POST["submit"]. These will have the values set to "Elliott" and "Say Hello" respectively.

Additionally, $_GET derives it's keys and values from the URL parameters. If you've used many websites, you've probably noticed at some point you get wonky URL's that have a question-mark (?) and then names, equal-signs (=) and values, maybe even an ampersand (&) between them. This data is encoded the same way as our POST data, but is accessed via $_GET instead of $_POST. If we did index.php?name=Elliott, we could access our name value with $_GET["name"].

Basically:

  • $_GET will give you an array of the URL parameter keys and values;
  • $_POST will give you an array of the POST body keys and values;

With all this in mind, we can actually build our form now.

For our form, we will need an if statement to determine if the form was "submitted" (test if "name" is in the $_POST array), an else to output the form if it wasn't submitted, and in the if we need to test if it's "Jon", because he owes me a lot of money.

One method of doing this boils down to the following:

<?php
if (isset($_POST["name"])) {
    $name = $_POST["name"];
    if ($name === "Jon") {
        echo "You owe me a lot of money, Jon.";
    } else {
        echo "Hello $name";
    }
} else {
?>
<form method="POST">
    <input type="text" name="name" />
    <input type="submit" name="submit" value="Say Hello" />
</form>
<?php
}
?>

Now, you'll notice we ended the PHP processing before the HTML, then started it again afterwards. This is one technique of printing HTML on a page, but it's not my favourite. I prefer to use an echo with a HEREDOC because it seems to read more clearly, and we do less "context switching" between what is PHP code and what is not.

To use the HEREDOC, we change our code to:

<?php
if (isset($_POST["name"])) {
    $name = $_POST["name"];
    if ($name === "Jon") {
        echo "You owe me a lot of money, Jon.";
    } else {
        echo "Hello $name";
    }
} else {
    echo <<<HTML
<form method="POST">
    <input type="text" name="name" />
    <input type="submit" name="submit" value="Say Hello" />
</form>
HTML;
}

To me, this is a cleaner way to output our HTML. This also allows us to embed variables in it if we need to, whereas the other version would require us to do <?php echo $variable; ?>.

More form Elements

I want to go over some more HTML form elements, such as textarea, checkbox, radio and select.

Of these, three play just as well as text did with our PHP: $_POST[$name] will have the value they are. The one that doesn't play as well is checkbox, and the behaviour it has can vary depending on whether you are using Apache or IIS.

With Apache, if isset($_POST[$checkbox_name]) evaluates to TRUE, then the checkbox was selected, regardless of value. With IIS, the checkbox value will be off if it was not submitted, and either on or the specified value if it was submitted.

We'll go over basic elements, and basic attributes that are important on those elements, but please know that this is not an exhaustive list of the elements nor of the attributes.

HTML: Textarea

An HTML textarea is a large text box that supports multiline input. It is included in a form with the following (basic) markup:

<textarea
    [name="$name"]
    [rows="$row_count"]
    [cols="$col_chars"]>
</textarea>

PHP will see $_POST[$name] as the text-value entered.

HTML: Text

An HTML text is a small text box that supports soft-limits on text input. It is included in a form with the following (basic) markup:

<input type="text"
    [name="$name"]
    [value="$default_value"]
    [maxlength="$max_length_chars"] />

PHP will see $_POST[$name] as the text-value entered.

HTML: Radio

An HTML radio is a group of mutually-exclusive options (that is, only one option may be selected). It is included in a form with the following (basic) markup:

<input type="radio"
    [name="$name"]
    [value="$value"]
    [checked="checked"] />

If multiple radio buttons have the same name, they are part of a single "group" and only one of them may be selected. Additionally, you may default which one is selected with the checked="checked" attribute.

PHP will see $_POST[$name] as the $value of the selected radio button.

HTML: Select

An HTML select is a group of mutually-exclusive options (that is, only one option may be selected) that presents as a drop-down list. It is included in a form with the following (basic) markup:

<select [name="$name"]>
    <option [value="$value"] [selected="selected"]>$display_text</option>
</select>

Any number of <option> elements may be included in it. Additionally, you may default which one is selected with the selected="selected" attribute.

PHP will see $_POST[$name] as the $value of the selected drop-down option.

HTML: Checkbox

An HTML checkbox is a single on/off toggle. It is included in a form with the following (basic) markup:

<input type="checkbox"
    [name="$name"]
    [value="$value"]
    [checked="checked"] />

You may default it to the "checked" state with the checked="checked" attribute.

On Apache:

PHP will see $_POST[$name] as $value (or "on" if there is no value) if it is checked, otherwise $_POST[$name] will not be set.

On IIS:

PHP will see $_POST[$name] as $value (or "on" if there is no value) if it is checked, otherwise it will see $_POST[$name] as "off".

Experimenting with Form Elements

To see all of this in action, you can print_r($_POST) and create a basic form with all of these elements:

<?php
print_r($_POST);
echo <<<HTML
<form method="POST">
    <textarea name="textarea">Textarea Text</textarea><br />
    <input type="text" name="text" value="Text Text" /><br />
    <input type="radio" name="radio" value="Option 1" checked="checked" /><br />
    <input type="radio" name="radio" value="Option 2" /><br />
    <input type="checkbox" name="checkbox_on" checked="checked" /><br />
    <input type="checkbox" name="checkbox_on_value" value="Checkbox On" checked="checked" /><br />
    <input type="checkbox" name="checkbox_off" /><br />
    <input type="checkbox" name="checkbox_off_value" value="Checkbox Off" /><br />
    <select name="select">
        <option value="Option 1" selected="selected">Option 1</option>
        <option value="Option 2">Option 2</option>
        <option value="Option 3">Option 3</option>
        <option value="Option 4">Option 4</option>
    </select>
    <input type="submit" name="submit" value="Submit" /><br />
</form>
HTML;

If you submit the default form on your Apache + PHP environment, you will get the following at the beginning of the page:

Array
(
    [textarea] => Textarea Text
    [text] => Text Text
    [radio] => Option 1
    [checkbox_on] => on
    [checkbox_on_value] => Checkbox On
    [select] => Option 1
    [submit] => Submit
)

Cleaning up Form Elements

One of the many things I have my own standard "PHP library" for is cleaning up these form elements, specifically checkboxes.

I typically do this by creating a PHP class with static functions, but we haven't gotten to that point and there's a lot of ground to cover before we get there, so I'm going to start with regular functions to clean this up.

PHP: Functions

PHP has the concept of "functions", or reusable blocks of code that do a certain, specific task. We have used something that looks like a function (isset), but we haven't used a true function yet.

A function is just code, often with inputs and outputs that do a thing. Functions have different "definitions", such as "pure" vs. "impure", where a "pure" function will produce the exact same output for any given input, and an "impure" function will not.

PHP defines functions quite simply: the keyword function followed by the name, a pair of parenthesis (()), then braces ({}) to denote the code in the function. Additionally, in the parenthesis, you may specify parameters to be passed to your function.

One of the relatively new features of PHP is the ability to specify types for the parameters passed to your function, and the type of data the function will return. We're going to use these, as it's a healthy habit to get into.

If we wanted to define a function to get text from our form, we might do the following:

function text(string $name): ?string {
    if (isset($_POST[$name])) {
        return $_POST[$name];
    }
    return NULL;
}

Some of this is new, so let's start from the beginning and hit all the new pieces:

  • function: as discussed above, this tells PHP we're about to define our own function;
  • text: this is the name, and this can be any string that you would name a variable;
  • string: this is the type for the first input parameter, here we say it has to be a string value;
  • $name: this is the name of the first parameter, in our case we're naming it name;
  • : ?string: this specifies the return type of the function, here it says it is a ?string, where the ? means it is a nullable string;
  • return: this tells PHP to end the function execution and give the caller whatever follows the return keyword;
  • NULL: this is a special value that means "there is no value";

The string for the parameter, and : ?string for the return type are optional, and if you do not specify them PHP will default to allowing any input and will not tell the caller what the output is (because it does not know).

We can call this function by specifying the name and then parameters: text("textarea") will be replaced with the value of $_POST["textarea"] per our function at runtime.

My personal PHP library defines a bunch of these functions, but the one I want to mention is the checkbox function, which will take the IIS oddities into account:

function checkbox(string $name): ?string {
    if (isset($_POST[$name]) && $_POST[$name] !== "off") {
        return $_POST[$name];
    }
    return NULL;
}

Here, we notice we have something new: &&. Let's talk about that for a moment.

PHP: Logical Combination Operators

PHP has the concept of combining logical comparison operators together via several chain operators. This allows you to put multiple conditions in your if statements (and other locations, but that's a discussion for later).

  • &&: if the left and right side of this are both TRUE the result is TRUE, otherwise the result is FALSE, additionally if the left side is FALSE the right side is not evaluated;
  • ||: if the left and right side of this are both FALSE the result is FALSE, otherwise the result is TRUE, additionally if the left side is TRUE the right side is not evaluated;
  • !: flips the right side from TRUE to FALSE, or FALSE to TRUE;
  • xor: if the left and right side are not the same boolean the result is TRUE, otherwise the result is FALSE;

As a result, we can use isset($_POST[$name]) && $_POST[$name] !== "off" to say "if $_POST[$name] has a value, and that value is not "off", then continue into our body." Additionally, PHP will not attempt to test $_POST[$name] !== "off" if isset($_POST[$name]) failed.


In Summation

With logical operators covered, I think now is a good time to wrap up. We'll get further into our adventure next time, but we're building a solid foundation on which we'll continue to develop.

Getting started with PHP

Getting started with PHP

I started my programming career over 15 years ago...which makes me look really old now that I think about it. When I started my career I started with HTML, then I folded some CSS in, and that's where it sat for a while. Don't get me wrong -- I was a child at the time. Eventually, I started picking up PHP, when I did I think it was version 4.2 or 4.3. That's mostly unimportant, I only learned the basic syntax and programming concepts back then. Eventually I went on to PHP 5, and now even PHP 7.

Through that time I've used a variety of languages and paradigms, but I always return to PHP as a language because it's where I started.

My career started on the web, and it then took a detour into desktop, console, and service-based applications. Now-a-days I do minimal web development, most of my time is spent building large-scale API's and platforms. So I think it's fitting that I write some information on PHP: how to get started, get your environment setup, and build great things.

Setting an Environment Up

Back when I started setting a PHP environment up felt overwhelming. Granted I was brand-new to programming at the time, but it always felt like a daunting task. Over the years it has gotten easier. Partly because I know more now than I did then, partly because of the tooling. We're not going to use any fancy tooling...we're going to do it the old-fashioned way because it's important that you understand what PHP is, and what PHP does.

PHP is just a program

When I started with PHP I thought it was this magical thing, this amazing thing that could never be understood -- and, to be fair, at the time, it was. But as I grew, I realized that PHP is just a program. Yeah, it's a complicated, intelligent, dynamic program, but it's just a program.

Like all other pieces of software on a Windows computer, PHP is just a .exe file that has some .dll files it works with. That's it. Nothing more. That said, when you look at how much is in those files, it starts to become apparent that PHP is not a simple, trivial thing, but a massive, complex system that can do so much.

To get started, we're going to download PHP and play with it before anything else. To do that, go to windows.php.net/download, and select the latest "Thread Safe x64" version. At the time of this writing, it is 7.4.6 VC15 x64 Thread Safe (2020-May-12 15:28:43). You will want the "Zip" package which should be the first option. Once you download it, extract the Zip file somewhere, and open the folder in PowerShell.

PHP REPL / Interactive Shell

Once you have a PowerShell window open, launch the "PHP interactive shell" with the command:

./php -a

It should print "Interactive Shell", then prompt with php >. At this point, we can type some valid PHP in. For example, we could type echo "Hello World"; and then press enter, after which it should print Hello World, then take us back to the php > prompt.

At this point we are in something called a "REPL", or "Read, Evaluate, Print Loop." Different languages call it different things, in PHP it's the "Interactive Shell", in F# we call it "F# Interactive" or fsi, in Python it's the default action of the command python. The overall idea here is that a REPL, or interactive shell, is a common feature of many programming languages.

Anyway, we're going to play with PHP in the REPL briefly. We want to familiarize ourselves with the idea that PHP is nothing special, and go over some very basic PHP syntax. And I mean very basic.

PHP: Statements end with a semi-colon

The first rule of PHP: all "statements" end with a semi-colon (;). What a "statement" is doesn't matter at the moment, but understand that most lines should have a semi-colon at the end. If you get a PHP error about "unexpected , expected ';'", that's what happened.

PHP: echo

To start with, the echo we used above is just a little bit of magic. What echo does is print a string to the "output" tied to the PHP processor. In a web scenario that's the HTTP session, in our REPL it is our console (which is why we just see it as another line after we press enter).

Essentially, echo says "send this to whoever is running our code."

You can give echo any value, but it will try to print the "string" version of the value. For a number, that's the base-10 equivalent (i.e. 15), for a string it would be just the string, for an object you might get some odd results because the object may not have a "string" equivalent defined.

PHP: Variables

Next, let's briefly go over variables.

A "variable" is a name we use that contains a value. In PHP variables are not declared prior to use (that means that we can make a new variable whenever we want), and they can change types throughout usage (which means we can change a variable from a number to some text to a group of other values).

Declaring a variable in PHP is extremely simple: use the dollar-sign ($) and then any name you like so long as it starts with a letter or underscore and does not contain spaces: $some_variable for example. Once you've named it, you use a single equal-sign and then any value to set it: $my_name = "Elliott";. At this point, we can now reference $my_name later in the program and it will be set to "Elliott":

$my_name = "Elliott";
echo "Hello $my_name";

If you run this in the REPL, it should print Hello Elliott.

We didn't talk about this before, but you can embed variables directly in a string: "Hello $my_name" causes PHP to replace $my_name with the value stored in that variable, in this case, "Elliott".

Another way to do this is "string concatenation", which is necessary sometimes.

PHP: String Concatenation

String concatenation is pretty simple. As the name implies, it is taking two things and constructing a string out of them. (Note: the two things don't need to be strings, they will get converted to strings if they are not already.)

String concatenation is achieved simply by putting a "dot" (.) between two values. Typically, when we do this we include a "space" on either side of the dot, such as: echo "Hello " . $my_name;. This will print the exact same thing as our echo "Hello $my_name";, but there are situations where you will need to use string concatenation.

Also, notice how our "Hello " includes the space between the word Hello and $my_name: this is important because the string concatenation / dot operation will directly combine the two strings, there will be no whitespace added.

PHP: Flow Control

Whether you've programmed before or not, you may not immediately understand what 'flow control' means, but it's typically a pretty simple thing.

Flow control is how a program decides between multiple options of what to do next. It's a way of changing the path the program will take. Imagine a train approaching a track-split. Obviously the train has to go left or right, it can't go both, so we use "flow control" to push it one direction or the other: PHP has similar constructs.

The simplest form of flow control in PHP is the if / elseif / else statement, where elseif and else are both optional. An if statement basically says "evaluate the condition in this statement, and execute the code in the body of the statement when the evaluation turns out to be true." The question is: what is true?

PHP: True and False

Most programming languages have the idea of a "boolean". That is, a value which is only allowed to be one of two options: TRUE, or FALSE. There are no other possible options.

PHP has a pretty large set of methods for deciding if something is TRUE or FALSE, but while it's a lot of decisions, it's rather straightforward. I'll be pulling from php.net, but the following values are considered FALSE, all others are TRUE:

  • the literal boolean constant FALSE;
  • the integers 0 and -0;
  • the floating-point / decimal values 0.0 and -0.0;
  • an empty string ("");
  • a string of "0";
  • an array that has zero elements ([] or array());
  • the type NULL;
  • unset variables;
  • a SimpleXML object created from an empty tag;

Every other value is considered TRUE at the time of this writing. The rules may change, so it's recommended to visit the PHP.net documentation on the subject.

Back to Flow Control: Testing Conditions

So now that we understand TRUE and FALSE to a limited degree, we can go back to flow control. In order for an if statement to make a decision, it has to evaluate if the condition is TRUE or FALSE.

To evaluate conditions, we need a way to test things, and PHP supplies that via various operators:

  • ==: test if two items are "mostly the same" (we'll go deeper into this later);
  • ===: test if two items are identical (we'll go deeper into this later);
  • != and <>: test if two items are not "mostly the same" (opposite of ==);
  • !==: test if two items are not identical (opposite of ===);
  • >: test if one item is greater than another item;
  • <: test if one item is less than another item;
  • >=: test if one item is greater than or equal to another item;
  • <=: test if one item is less than or equal to another item;
  • <=>: indicate whether one item is less than, equal to, or greater than another item (we'll go deeper into this later);

For now, we're going to ignore ==, !=, and <=>, then we'll talk about the three of them at a later time.

So, to test a condition we will use === to determine if our name is equal to a comparison: $my_name === "Jon". If $my_name was set to "Jon" before this line, then the result will be TRUE, if it was not, then the result is FALSE.

After we test our condition, we create a new "scope" within the if that has the code we want to run. This is achieved by putting an opening brace ({) after the if, and a closing brace (}) after the code we want to run.

Afterwards, we can use an else in the same way to do something different if our condition was not true.

In PHP, we do this simply by putting it all together with parenthesis around our condition as follows:

if ($my_name === "Jon") {
    echo "You owe me a lot of money, Jon.";
} else {
    echo "Hello $my_name";
}

You'll notice, if you type the first line into the PHP REPL, it gives you php { for the next line instead of php >, this is because the REPL recognizes that you opened a new scope and did not close it yet, so it is going to wait for more code.

Setting up a web environment

Ok, long-winded intro to PHP complete, it's time we setup a web server to process PHP files. This is actually a relatively straightforward process. In many cases you'll find people refer to these as a stack with one of the following names: LAMP, LAPP, LAP, WAMP, WAPP or WAP. What does this all mean?

  • LAMP: Linux, Apache, MySQL, PHP
  • LAPP: Linux, Apache, PostgreSQL, PHP
  • LAP: Linux, Apache, PHP
  • WAMP: Windows, Apache, MySQL, PHP
  • WAPP: Windows, Apache, PostgreSQL, PHP
  • WAP: Windows, Apache, PHP

We're going to setup a WAP server, someday we might make it a WAMP or WAPP server, but for now we don't need any "SQL" component.

If you're running Windows, congratulations! You have the first thing setup. If you're not...I have some bad news: my examples will all be for Windows.

Next, we need to setup Apache and PHP. We'll setup Apache, then we'll tag PHP in.

To setup Apache we need to download a Windows version. The Apache Software Foundation keeps a list of Windows third-parties on their website. We're going to use the Apache Lounge version, which is Apache 2.4 VS16 / VC15 x64 / Win64: https://www.apachelounge.com/download/VS16/binaries/httpd-2.4.43-win64-VS16.zip.

You will also need the Visual C++ Redistributable for Visual Studio 2015-2019 if you do not have it already. Fortunately, Apache Lounge provides a link to a version of it: https://aka.ms/vs/16/release/VC_redist.x64.exe.

Install the redistributable first, then we can setup Apache.

Setting up Apache

Once the redistributables are installed, we can setup Apache. Fortunately, Apache Lounge makes it easy to get things installed. We will install Apache as a service, which means it will start on system boot and logs will be managed by Windows.

We want to put Apache somewhere memorable. For you, I don't care where you put it, but I'm going to put mine under C:\Web\ so that it's somewhere memorable. We want to copy the contents of the Apache24 folder of our download to wherever we decided. With the default setup structure, our web root will be <apache root>\htdocs, so mine will be C:\Web\htdocs.

Once we have Apache copied to our root folder, we need to grab that path (C:\Web in my case) and open the conf\httpd.conf file. Near the top (in my case, line 37) should be a Define SRVROOT ... line, which we will need to modify to the path we are using. Again, we'll use my case:

Define SRVROOT "C:/Web"

Once we've done that, save the file. We're ready to install and run Apache.

To install Apache, open an Admin PowerShell session in the bin folder under your Apache install (C:\Web\bin in my case) and run the following:

.\httpd.exe -k install -n "Apache HTTP Server"

If you get the following error you can ignore it:

AH00558: httpd.exe: Could not reliably determine the server's fully qualified domain name, using <some IPv4 or IPv6 address>. Set the 'ServerName' directive globally to suppress this message

This just means that Apache could not identify a DNS name (such as "www.example.com") to use. We could specify one, but we don't have one.

Once that is complete, run Get-Service ApacheHttpServer in your PowerShell window, and you should see the following:

Status   Name               DisplayName
------   ----               -----------
Stopped  ApacheHttpServer   Apache HTTP Server

This means that the service has been installed successfully.

Next, run Start-Service ApacheHttpServer, then run Get-Service ApacheHttpServer when that completes. You should see:

Status   Name               DisplayName
------   ----               -----------
Running  ApacheHttpServer   Apache HTTP Server

If all went well, Apache is installed and, more importantly, running. To verify, open a web browser and navigate to http://localhost. You should see a very basic page with the text "It works!"

Adding PHP to Apache

Now that Apache is installed, we need to get back into the PHP folder we downloaded and played with earlier.

First, turn Apache off. You can do this with Stop-Service ApacheHttpServer and then running Get-Service ApacheHttpServer to verify.

Create a folder named php inside your Apache root, for me this is C:\Web\php, copy the files from the PHP 7.4.6 x64 Type Safe download into this folder.

Copy the php.ini-development in your new php directory and rename it to php.ini, mine is C:\Web\php\php.ini.

We will need to edit this new php.ini, so go ahead and open it.

Locate the extension_dir settings (on my version, this is line 755) and uncomment the Windows line (757 on my version) by removing the semi-colon (;) from the beginning, then add your PHP root before the ext, mine looks like:

extension_dir = "C:/Web/php/ext"

Next, we need to edit our httpd.conf.

We want to find the DirectoryIndex line (on my version this is line 285). We want to add index.php before index.html, so the line should read:

DirectoryIndex index.php index.html

Next, we need to add the PHP configuration to Apache. To do this, go to the end of the httpd.conf file and add the following, replacing C:/Web with your Apache root directory:

LoadModule php7_module "C:/Web/php/php7apache2_4.dll"
AddType application/x-httpd-php .php
PHPIniDir "C:/Web/php"

Once that is complete, run Start-Service ApacheHttpServer and then Get-Service ApacheHttpServer to verify.

If Apache restarted properly, load http://localhost again to verify that Apache is running. Notice that you still see "It works!" because we haven't added any PHP code yet.

Add an index.php file in your htdocs location (mine is C:\Web\htdocs), and let's add some PHP:

<?php
phpinfo();

All PHP pages must use <?php to indicate the start of PHP code, and ?> to indicate the end of PHP code if the file does not end in PHP code.

Once you do this, load http://localhost, and we should finally see a very nice-looking page that has our PHP version listed at the top ("PHP Version 7.4.6" at the time of this writing) and talks about our PHP installation.

Adding some PHP and Apache features

Once your PHP and Apache install is verified, let's add some more features we may or may not use in future tutorials.

To start with, update httpd.conf and do the following:

  1. Locate rewrite_module in the file (mine is line 162);
  2. Uncomment the pound-sign / number-sign (#) at the beginning;

Next, open the php.ini file and do the following:

  1. Locate extension=curl in the file (mine is line 910) and uncomment the line by removing the semi-colon (;) at the beginning;
  2. Locate extension=gd2 in the file (mine is line 914) and uncomment the line by removing the semi-colon (;) at the beginning;
  3. Locate extension=mbstring in the file (mine is line 920) and uncomment the line by removing the semi-colon (;) at the beginning;
  4. Locate extension=mysqli in the file (mine is line 922) and uncomment the line by removing the semi-colon (;) at the beginning;
  5. Locate extension=pdo_mysql in the file (mine is line 927) and uncomment the line by removing the semi-colon (;) at the beginning;
  6. Locate extension=pdo_pgsql in the file (mine is line 930) and uncomment the line by removing the semi-colon (;) at the beginning;
  7. Locate extension=pgsql in the file (mine is line 932) and uncomment the line by removing the semi-colon (;) at the beginning;

Save both of these files and restart Apache. If you don't want to go through stopping and starting the service, you can edit these files while it is running (though none of your changes will take effect) then use Restart-Service ApacheHttpServer and then Get-Service ApacheHttpServer.

Once you have done this, reload your http://localhost page to verify.

  • Find the apache2handler section, and then the "Loaded Modules" cell, and verify that mod_rewrite is in the list;
  • Find the gd section and verify the "GD Support" cell says enabled;
  • Find the mysqlnd section and verify the "API Extensions" cell says mysqli,pdo_mysql;

You may not find all the extensions we enabled in your index.php, and that is OK: some extensions have prerequisites we did not enable. The pgsql and pdo_pgsql require a PostgreSQL driver that is not provided, for example.


All Set

Now that all this is done, we have completed our WAP installation: Windows + Apache + PHP. The next post will start getting into writing more PHP, specifically getting into more topics like looping, functions, and passing data around. As we get into these topics, I will be describing some of the "good practices" around them. (Note: some folks, including myself and past-me, call these "best practices", but I don't know if they are or not. I call them "good practices" because they're things I like to see when looking at someone else's code.)

F# Advent 2019: Dawn of the F# Domain Types

If you use .NET, build your objects in F#

So towards the end of October, I tweeted (as one does) about building domain objects and models in F#. I mentioned a few nice things about it, so today I intend to do a deeper dive into some of those nice things, and talk about why they are concerns you should keep in the back of your head when you make a decision about how to build your domain models.

Now it's no secret that I like F#. I talk about it a lot (among many other things), largely because when I am doing .NET work, there is almost always some F# involved. (It might be a small portion, or it might be a large portion, depends on the project.) As a result, if you've ever spoke with me in person about programming, I've probably talked about it.

Today, I'm going to show you why F# is a very cool tool in the .NET ecosystem, and how F# fixes a lot of class-oriented things that, in my opinion, C# does wrong.


F# does a lot of the heavy-lifting for object-oriented work

One of the most impressive things I feel F# has to offer is the level of generation it offers for object-oriented work. How it can give you a lot of things you usually spend a good chunk of time on for free. Three lines of F# can do something that would take, genuinely, dozens (or even hundreds) of lines of C#.

If you've ever done OOP work, chances are you've had to write a .ToString(), or a .Equals(object), or a .GetHashCode(). Spoiler alert: with F# you don't need to write any of those most of the time. (You can, but it's ill-advised.)

Let's take a common game-development scenario in C#: vectors. In physics, a vector is a distance and direction. Start at the origin (or point (0,0)), then adjust position from there based on the direction given, and the distance, move to the new location.

Commonly, we represent them in one of two ways:

  • r, Θ
  • x, y

You are likely familiar with the (x, y) format: this is standard point representation for the cartesian coordinate system. The (r, Θ) format is common in polar coordinate systems.

Long-winded math aside, the two values can be translated between. We'll build a sizable Vector2 feature-set into our program, and see how the F# and C# of the implementation vary.

A standard C# starting point might be something like the following:

public class Vector2 
{
    public int X { get; set; }
    public int Y { get; set; }
}

In F#, we would do something like the following:

type Vector2 = {
    X : int
    Y : int
}

Alright, so far F# hasn't really lived up to the promise. It's the same length as the C#, and we don't really see any advantages.

Fair. We haven't done anything to really take advantage of F# yet, but one of the things we already have is an implementation of .ToString(), .Equals(object), and .GetHashCode(). It also implements IEquatable<Vector2>, IStructuralEquatable, IComparable<Vector2>, IStructuralComparable, and IComparable.

I could go into a large digression on the intermediate language generated and how it works, why it's important, etc., instead, I'm going to just use a quick screenshot of the generated F# class (left) to the generated C# class (right).

F# and C# comparison

Wow are those different. F# implemented 9 additional methods and even setup a constructor for us. Not so bad. The C# version, on the other hand, is just the two properties. So really, our 4 lines of F# is equivalent to a lot, lot more in C#. In fact, the F# version is even immutable. Sure, we could omit the set in the C# to achieve the same, but then we have to define our own constructor. My point with this first example is to demonstrate how effective F# is at generation. We'll dig deeper into what it means in a few moments.

Now, I could stop here, and I suppose if I were publishing this right now I might, but today I'm going to go a little further.


Why is all this F# generation important?

This is probably the more important part to discuss: why is the generation F# provides so important? Why do I push it as such a major reason to build objects in F#?

It all boils down to a couple basic points:

  1. Structural equality and comparison is important for doing effective matching and evaluation of objects against one-another.
  2. These few functions are extremely important if the object itself is to be used in a HashMap scenario. (I.e. a dictionary key, etc.)

Basically, it boils down to performance and developer ease. A lot of times we want to be able to do something like if a = b then ..., but we find out that there is no = operator between a and b, even when a and b are the same type. So you go ahead and define your own operator, just to need to define a .Equals(object) method and then .GetHashCode(). You will also want both of these implemented if you are using LINQ, as some of the LINQ methods / query syntax require them.

Unfortunately, in C# we don't get those features. Instead we only have a base implementation (the default) for both Equals(object) and GetHashCode(). The base implementations don't do structural equality at all, so two objects that are exactly the same might not match via .Equals(object). (Spoiler: unless they are the exact same instance, they won't match via the default.)

This also comes in to heavy play with struct types, as it's expected that two structures (which are expected to be value types) that are instanced via the same values would be the same. But alas, by default, they are not such in .NET.

So, let's look at some situations we would have to write a lot of manual code for in C#, that F# just gives us.

Scenario 1: you want to determine if an element is in an array.

Let's assume you have an array of elements, and you want to determine if you have a certain element in the array. With the new LINQ syntax in C#, it should be rather simple, no?

var find = new Vector2() { X = 1, Y = 2 };
var results = elements.Where(x => x.Equals(find));

Seems reasonable, should work fine. Except it wont. The problem here is that we don't override .Equals(object), and we don't have a .Equals(Vector2), so our default C# version won't work. Our F# version will work fine though, as we have both of those options in the F# version. One note: for the F# version we must initialize find via the constructor, because it's immutable (which lends well to a later point).

To get the C# version to work, we really just need to pick one of the two to implement, though for fairness we'll actually implement both.

public override bool Equals(object other) => Equals(other as Vector2);
public bool Equals(Vector2 other) => other != null && X == other.X && Y == other.Y;    

So we added two lines (at a minimum) to our C# to make sure this works, but we end up with working code, so we are done here.

First note: your IDE might state that you didn't override GetHashCode(), and it's entirely correct! It's always a best practice to override both if you are doing one of them. Second note: you might find that the two lines printed by the C# version are different than the F# version, and they are. The F# version has a custom .ToString() implementation, which means when we do a print line for it, we get a nicer string.

Scenario 2: you have two collections, and you want to create the collection of the elements present in both.

This scenario is your basic Intersect scenario: you have some elements1 in one collection, and some more elements2 in another collection. You want to create a single collection that encompasses only those elements that are present in both collection.

You might write some code like the following:

var results = elementsA.Intersect(elementsB).ToArray();
Console.WriteLine(results.Length);

In the C# version, you'll find that no elements match no matter how you call the Intersect function, and that the F# version gives you some.

Again, we're missing a key piece, I alluded to it earlier: GetHashCode(). We need it for Intersect to work properly.

So, we'll create a poor implementation:

public override int GetHashCode() => X ^ Y;

Now the C# version works fine.

Note here: the F# GetHashCode() is a good implementation, ours was a poor attempt to make sure that we got something in place for .NET to use. In .NET, it's OK for two different elements to return the same code, because Equals(object) is the final decision maker, but two identical elements may NOT return different hash codes.


At this point, I wanted to go into some performance implications of the C# classes vs. the F# generated objects, but because this post is already long, and also because it's Christmas, I'm going to save that discussion for a later date. In fact, I'm going to target mid-January for that discussion. That said, I recommend doing some tests of the C# vs. F# objects, and especially in usage as dictionary keys. You might find some interesting results.

Additionally, this is all up on GitHub, so feel free to play with the exact code I was using to test our setups.

You're Logging Wrong: Stop It

You're taking Dependencies Wrong

Specifically, you're logging wrong.

Here's the deal, we all at some point think "Oh, I need to log some information, better write an abstraction!"

Because we programmers are too afraid (or arrogant) to take dependencies on someone else's "stuff", we always write our own abstractions. They're usually subtly different, but the 99% similarities are:

  1. An ILogger interface of some sort;
  2. A Log(Severity, message) in the ILogger;
  3. Two or three generic logger implementations;

This is basically the bulk of what most implementations call for. Always something like this.

This might look something like the following, in C#:

public enum LoggerLevel : byte
{
    Error = 0,
    Warning = 100,
    Information = 200,
    Verbose = 255
}

public interface ILogger
{
    void Log(LoggerLevel level, string msg);
    void Log<T>(LoggerLevel level, T obj);
}

Right? This probably looks familiar. This is a pretty common pattern, so common I call it the "logger pattern."

The purpose is to make it easy to say "Ok, here's an ILogger, do your thing." We have a logger sample for something like an in-memory buffer:

public class BaseLogger : ILogger
{
    private List<string> messages = new List<string>(10000);

    public void Log(LoggerLevel level, string msg)
    {
        messages.Add(msg);
    }

    public void Log<T>(LoggerLevel level, T obj)
    {
        messages.Add(obj.ToString());
    }
}

And we say "great, things are done."

Here's the deal: this is wrong.


What's wrong with ILogger?

Well, just about everything.

  1. It requires an implementation for every target logger, this means a lot of boiler-plate code, etc.;
  2. We always mess it up with trying to support "common" targets (AutoFac, for example);

A great person named David Fowler tweeted about this a few days ago:

This is what the .NET library ecosystem looks like today. Say you develop an interesting library/framework that calls into user code and also does some interesting logging (lets call this library A). A customer comes along and wants to use Autofac and log4net.

They file a feature request on your repository and you do what a good software engineer would do, you make an abstraction, A.ILogger and A.ICanActivateYourCode and you make 2 new libraries. A.Log4Net and A.Autofac. Rinse and repeat this process with libraries A to Z

What you end up with are different abstractions that all look the same but nobody wants to take a dependency because that's a big deal. What if it's the wrong one, what if that package goes away? The benefit needs to be huge in order for your core library to pull more deps.

So a customer that wants to use A, B and C with log4net and autofac install. A, B, C and A.log4Net, A.Autofact, B.log4Net, b.Autofac, C.log4net, C.Autofac.

I call this glue library hell. There are insufficient shared abstractions that exist, therefore everyone makes their own.

Not to mention you have to hope that enough of the API for the underlying libraries are appropriately exposed so that you have a consistent way to configure them.

David's point was that we have a million of the same implementations, but they're ever-so-slightly different.


So what's the 'right' way?

Ah yes, what is the 'right' way?

Well, due to the nature of logging, we are always adding a message / thing to the log, we're never actually doing anything else. As a result, the "right" way to take a logging dependency is not ILogger, but is instead an Action<string>. That is: a function that you pass a message to.

Wait, what do you mean?

Back to our original example, let's say we have a function DoWork, and DoWork takes an ILogger:

private static void DoWork(ILogger logger)
{
    for (var i = 0; i < COUNT; i++)
        logger.Log(LoggerLevel.Information, "Test");
}

This seems straightforward. "Yeah, you log the whatever with the Log method on the ILogger." Sure, makes sense.

Calling this is straightforward:

ILogger logger = new BaseLogger();
DoWork(logger);

And viola: we have logging.

But, we want to log with a function instead, so we replace DoWork:

private static void DoWork(Action<LoggerLevel, string> logger)
{
    for (var i = 0; i < COUNT; i++)
        logger(LoggerLevel.Information, "Test");
}

Now, instead of taking the ILogger directly, we'll take the Log function:

Action<LoggerLevel, string> logger = new BaseLogger().Log;
DoWork(logger);

Curiously, this makes life much easier on the consumer, as now they don't need a ILogger implementation (and that whole abstraction is just gone), instead, they pass a function.

This means that the consumer can say "here's a function that logs to AutoFac", or "here's a function that logs to log4net", etc.

Even moreso: our implementation is now compatible with any other utility using a logger (should they all use function logging). The same function can be slightly reworked for each utility, assuming they don't share a LogLevel. You just wrap it with a very quick lambda and life is good.


But wait, there's more.

There's one more thing here we should evaluate, which is the obvious: what about performance?

Ah yes, the age-old "is it fast, though?" question.

Everyone wants to make sure their code is fast, they want to make sure they don't have any performance loss. Why? I don't know, it's always something superficial.

So, is it fast? This is a good question, I guess.

Of course, me being me, I benchmarked various types of logging and threw it on GitHub, but the overall consensus was: it is as fast, or faster.

(Sidebar: the nop in the F# version is because of this bug. Looks like Don Syme has thoughts on how we can fix it.)

The Full Results

So I just ran my benchmark alet it do it's thing, I've put the full result below:

// * Summary *

BenchmarkDotNet=v0.11.4, OS=Windows 10.0.17134.407 (1803/April2018Update/Redstone4)
Intel Xeon CPU E3-1505M v6 3.00GHz, 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.0.100-preview-010184
  [Host]     : .NET Core 2.1.7 (CoreCLR 4.6.27129.04, CoreFX 4.6.27129.04), 64bit RyuJIT
  DefaultJob : .NET Core 2.1.7 (CoreCLR 4.6.27129.04, CoreFX 4.6.27129.04), 64bit RyuJIT


|                  Method |      Mean |      Error |     StdDev |    Median | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|------------------------ |----------:|-----------:|-----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:|
|               LogDirect |  33.42 us |  0.4143 us |  0.3673 us |  33.36 us |  0.62 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|             LogDirectFs |  35.46 us |  0.6995 us |  1.4755 us |  35.60 us |  0.68 |    0.04 |     18.8599 |      3.7231 |           - |            78.21 KB |
|         LogViaInjection |  53.94 us |  0.5640 us |  0.4710 us |  54.11 us |  1.00 |    0.00 |     18.8599 |      3.7231 |           - |            78.21 KB |
|       LogViaInjectionFs |  56.81 us |  0.9890 us |  0.8259 us |  56.72 us |  1.05 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|          LogViaCallback |  47.99 us |  0.5204 us |  0.4868 us |  47.91 us |  0.89 |    0.01 |     18.8599 |      3.7231 |           - |            78.27 KB |
|        LogViaCallbackFs |  50.81 us |  0.3474 us |  0.3250 us |  50.81 us |  0.94 |    0.01 |     18.8599 |      3.7231 |           - |            78.27 KB |
|            LogObjDirect | 666.71 us | 12.3811 us | 12.7145 us | 664.47 us | 12.38 |    0.32 |    185.5469 |     92.7734 |           - |          1093.84 KB |
|          LogObjDirectFs | 668.30 us |  8.1078 us |  6.7704 us | 664.00 us | 12.39 |    0.15 |    185.5469 |     92.7734 |           - |          1093.84 KB |
|      LogObjViaInjection | 709.30 us | 12.1255 us | 11.3422 us | 706.69 us | 13.14 |    0.26 |    185.5469 |     92.7734 |           - |          1093.84 KB |
|    LogObjViaInjectionFs | 705.13 us |  6.9833 us |  6.5322 us | 705.46 us | 13.07 |    0.18 |    185.5469 |     92.7734 |           - |          1093.84 KB |
|       LogObjViaCallback | 671.84 us |  6.9447 us |  5.4220 us | 673.47 us | 12.44 |    0.11 |    185.5469 |     92.7734 |           - |           1093.9 KB |
|     LogObjViaCallbackFs | 669.24 us |  4.6712 us |  3.9007 us | 670.10 us | 12.41 |    0.09 |    185.5469 |     92.7734 |           - |           1093.9 KB |
|         LogInlineDirect |  33.32 us |  0.3477 us |  0.3253 us |  33.24 us |  0.62 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|       LogInlineDirectFs |  33.43 us |  0.4313 us |  0.4035 us |  33.34 us |  0.62 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|   LogInlineViaInjection |  33.68 us |  0.6655 us |  0.7121 us |  33.39 us |  0.63 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
| LogInlineViaInjectionFs |  35.91 us |  0.8274 us |  2.4267 us |  35.15 us |  0.64 |    0.03 |     18.8599 |      3.7231 |           - |            78.21 KB |
|    LogInlineViaCallback |  49.10 us |  0.6939 us |  0.6491 us |  49.02 us |  0.91 |    0.01 |     18.8599 |      3.7231 |           - |            78.27 KB |
|  LogInlineViaCallbackFs |  51.15 us |  0.5555 us |  0.5196 us |  51.01 us |  0.95 |    0.01 |     18.8599 |      3.7231 |           - |            78.27 KB |
|  LogInlineDynamicDirect |  76.38 us |  1.4895 us |  1.9884 us |  75.43 us |  1.43 |    0.05 |     18.7988 |      3.0518 |           - |            78.21 KB |

That's a lot of benchmarking. Let's crop that down:

|                  Method |      Mean |      Error |     StdDev |    Median | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|------------------------ |----------:|-----------:|-----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:|
|               LogDirect |  33.42 us |  0.4143 us |  0.3673 us |  33.36 us |  0.62 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|         LogViaInjection |  53.94 us |  0.5640 us |  0.4710 us |  54.11 us |  1.00 |    0.00 |     18.8599 |      3.7231 |           - |            78.21 KB |
|          LogViaCallback |  47.99 us |  0.5204 us |  0.4868 us |  47.91 us |  0.89 |    0.01 |     18.8599 |      3.7231 |           - |            78.27 KB |

Those are the three lines I want to look at.

First and foremost: the fastest way is to pass the BaseLogger class in directly (i.e.: no ILogger interface usage). That was pretty obviousl.

But, the next, interesting note is that the interface version is actually about 6us slower than passing the function within the interface. This is curious, not only is passing the function a more proper way to do it, but it's also ever-so-slightly faster. (Granted, the margins are tiny and statistically meaningless, but it does prove that there is not discernable performance disadvantage, so that argument is now completely irrelevant.)

The only "bad" difference is that there was an extra 0.06KB allocated, but I assume that's overhead for Action<>. It's also such a tiny amount that if you're using that (or the time, to be completely frank) for justification, you are not making the right decisions.

The only time the interface is a better option is if you are not passing it, but are doing things inline:

|                  Method |      Mean |      Error |     StdDev |    Median | Ratio | RatioSD | Gen 0/1k Op | Gen 1/1k Op | Gen 2/1k Op | Allocated Memory/Op |
|------------------------ |----------:|-----------:|-----------:|----------:|------:|--------:|------------:|------------:|------------:|--------------------:|
|         LogInlineDirect |  33.32 us |  0.3477 us |  0.3253 us |  33.24 us |  0.62 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|   LogInlineViaInjection |  33.68 us |  0.6655 us |  0.7121 us |  33.39 us |  0.63 |    0.01 |     18.8599 |      3.7231 |           - |            78.21 KB |
|    LogInlineViaCallback |  49.10 us |  0.6939 us |  0.6491 us |  49.02 us |  0.91 |    0.01 |     18.8599 |      3.7231 |           - |            78.27 KB |

Here, you'll note that the overhead of Action<> actually puts the callback version at a significant disadvantage, and a discernable disadvantage in this case.


In Summation

To summarize our discussion:

  1. Don't make an ILogger, it's unbecoming.
  2. Use an Action or Function, it's much more dynamic and reusable.
  3. The only time this is not the case is when the logger is constructed inline with the thing being logged. Then use a regular function or class.

.NET Gotcha: System.Net.WebException: The remote server returned an error

F# / .NET: Gotcha's

The dreaded System.Net.WebException: The remote server returned an error mess...

If you've ever used the WebClient to target RESTful API's in .NET, you probably hit this error at least once:

System.Net.WebException: The remote server returned an error: {Some Error}.
   at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)
   at System.Net.WebClient.DownloadString(Uri address)
   at System.Net.WebClient.DownloadString(String address)

Scenario: you're just chugging along, working on some stuff, then an exception comes in and BAM, messes up your whole day.

I had this happen to a coworker yesterday. He was working on something and kept wondering why the endpoint was giving him a 404: Not Found in a WebException. He'd never really hit this before, because this was the first time he needed to consume a RESTful API in .NET.

What's happening here?

When the .NET WebClient sends a request, it's backed by a WebRequest, which happens to have someone's "good" idea: when we hit a non-200 status code, throw an exception. That makes sense, right? If we hit a 404, it makes sense to throw an exception, right?

Sure, in 1990. It's 2019 though, we use the 404 to mean something. Some genius out there said "hey, when building RESTful API's, just send an HTTP status code with your 'thing', we already have a list of them." The problem is that up, down, left and right people were treating 300, 400, and 500 status codes as actual errors, and things would break. Well, .NET is no exception.

So how do we fix it?

Yes, this is the easy part. We have to use a try / catch or try / with, depending on the language (OH NO!).

Effectively, the following code will throw a System.Net.WebException: The remote server returned an error: (404) Not Found. on line 7, when called on line 11. We'll fix that.

open System.Net
let urlBase = "https://httpstat.us/"

let getCode code =
    use wc = new WebClient()
    wc.Headers.Add(HttpRequestHeader.Accept, "application/json")
    code |> sprintf "%s%i" urlBase |> wc.DownloadString

200 |> getCode |> printfn "%s"
202 |> getCode |> printfn "%s"
404 |> getCode |> printfn "%s"

So, what do we do?

Easy, catch the WebException and read the response:

open System.IO
open System.Net
let urlBase = "https://httpstat.us/"

let getCode code =
    try
        use wc = new WebClient()
        wc.Headers.Add(HttpRequestHeader.Accept, "application/json")
        code |> sprintf "%s%i" urlBase |> wc.DownloadString
    with :? WebException as ex ->
        use sr = new StreamReader(ex.Response.GetResponseStream())
        sr.ReadToEnd()

200 |> getCode |> printfn "%s"
202 |> getCode |> printfn "%s"
404 |> getCode |> printfn "%s"

This isn't perfect, we'll catch all WebException issues, so we want one more "guard" clause:

open System.IO
open System.Net
let urlBase = "https://httpstat.us/"

let getCode code =
    try
        use wc = new WebClient()
        wc.Headers.Add(HttpRequestHeader.Accept, "application/json")
        code |> sprintf "%s%i" urlBase |> wc.DownloadString
    with :? WebException as ex when ex.Status = WebExceptionStatus.ProtocolError ->
        use sr = new StreamReader(ex.Response.GetResponseStream())
        sr.ReadToEnd()

200 |> getCode |> printfn "%s"
202 |> getCode |> printfn "%s"
404 |> getCode |> printfn "%s"

Bam. Now we only catch ProtocolError exceptions, like 400, 500, etc.