loading...

re: What's the deal with downing PHP development? VIEW POST

FULL DISCUSSION
 

You can write good, well-designed code in any language. Working in a good,
well-designed, PHP codebase is probably as pleasant an experience as working
in a good codebase in Javascript, Ruby, or Python. I would rather write in
a good PHP codebase than in a bad Javascript, Ruby, or Python codebase.

However I believe:

  1. Unhealthy PHP codebases are more painful than unhealthy codebases in other languages
  2. You have to fight harder against PHP to keep your codebase healthy than against other languages.

For these reasons, I would strongly recommend against PHP for new programmers and new projects.

I'll provide a couple concrete examples of how I have experienced this:

a) Testing with mocks is difficult because you can't monkey-patch in PHP.

I've had a lot of difficulty writing unit tests for PHP code that wasn't designed
with unit testing in mind. In order to write a good unit test, you need to mock
out your database calls, but that can be really tricky if you are calling methods
that were not designed to injecting mocks them easy.

In Javascript, this is not as much of a problem because you can literally monkey-patch
anything. Here's an example:

const db = require('./db')
async function banUser (id) {
    const results = await db.query(
        'SELECT ip_address ' +
        'FROM users ' +
        'WHERE id = ?',
        [ id ]
    )

    const ip = results[0].ip_address
    return await db.query(
        'INSERT INTO banned_ips VALUES (?)',
        [ip]
    )
}

async function testBanUser () {

    // Store the original definition of `query`
    const $query = db.query
    let calls = []
    // Monkey patch `query`
    db.query = async function (statement, values) {
        calls.push({ statement, values })
        db.query = async function (statement, values) {
            calls.push({ statement, values })
        }
        return [{ ip_address : '100.100.100.100' }]
    }
    await banUser(2)
    // Restore the original definition
    db.query = $query
    expect(calls[0]).to.deep.equal({
        statement: 'SELECT ip_address FROM users WHERE id = ?',
        values: [2]
    })
    expect(calls[1]).to.deep.equal({
        statement: 'INSERT INTO banned_ips VALUES (?)',
        values: ['100.100.100.100']
    })
}

The "monkey patching it with a mock that when called replaces itself with
a different mock" is kind of ugly, but it works and you can do it much more
cleanly with a library like "sinon.js" -- an incredible library.

In PHP, trying to monkey patch like this just fails. If you do something like

function testBanUser() {
  $database->query = function ($statements, $values) {
     echo "Joke's on you! You can't monkey-patch in PHP!";
  };
}

and then call $database->query, it will still just end up invoking the
original definition.

There's a PHP library called "mockery" that can get you a little further than
this, and gives you tools to inject your mocks in certain cases. But it's complicated.
Mocking static methods gets especially hairy, and mocking final methods is
nigh impossible. Overall you just really, really have to fight for it.

b) Another example of how PHP actively leads the user into writing bad
code
is superglobals. If you want information about the HTTP request you
are currently serving, PHP's default way of exposing this information to you
is through the $_REQUEST superglobal. As experienced programmers know,
global variables are a code smell, and they rapidly lead to unmaintainable
code. In a healthy codebase, you should encapsulate the superglobals, access
them only in one place, define some sort of wrapper object around them to
control access to HTTP request data, and pass such objects as arguments to
the functions/methods that need to access/operate on http request data. It's
possible to do this sort of thing in PHP, but the language makes it
difficult. The language itself is basically actively encouraging you to do
the wrong thing and use these superglobals everywhere.

Node.js does the right thing here. You instantiate the HTTP server yourself
and you create handlers for HTTP requests that take HTTP requests object
in as parameters. No global variables necessary.

These are just two examples -- I could go on and on and write more, but I
believe they speak to a general theme. It's possible to write good PHP --
if you know what you're doing and have a good design sense. But PHP the
language and PHP the ecosystem is going to be fighting you every
step of the way.

Code of Conduct Report abuse