Showing posts with label php. Show all posts
Showing posts with label php. Show all posts

2013-03-06

MidwestPHP Review

MidwestPHP logo
It seems like smaller regional PHP conferences are popping up all over the place these days. While they don't have the budgets of the bigger conferences like ZendCon and |Tek, they do provide something those other conferences just can't: an intimate setting. The newest conference on the block is MidwestPHP. I've never been to Minnesota, but submitted two talks to them anyway. Both somehow got accepted and I had a blast at the conference, even while speaking (I'm terrified of public speaking, but it is getting easier). Here's some thoughts on the conference:


The Good

  • Organization - Many attendees that I talked to had no idea it was a first time conference run by first time organizers. Things went relatively smoothly. Kudos to Mike and Jonathan for a great job.
  • Speaker gifts - As a fan of craft brews, I appreciated a four pack from a local brewery.
  • Newer speakers - Smaller conferences with four tracks have space to take a chance on some new speakers, injecting some new blood into the speaking circuit. The PHP community seems to have the same speakers at many of its conferences. Midwest did a good job of mixing "big name" speakers (like Chris Hartjes) with relative newcomers (like me) as well as some first timers. Giving new people experience is great for the future since the larger conferences might feel safer accepting a talk from someone that has spoken before.
  • Attendees - It's great seeing so many people so eager to soak up knowledge. I made note of a few people at the beginning of the conference and mentally stalked them throughout. Unlike many conferences I've been to, they weren't always with the same group. Being willing to talk to random people at a conference is a great way to amplify your benefit from going to a conference.
  • The venue - Some intimate rooms as well as larger rooms, all with tables. Lots of wide hallways for the important between session discussions.

The Less Good

  • Speaker gifts - While I appreciate good beer, some people don't drink. And based on the four 4-packs I ended up with, some speakers weren't interested in carrying them home.
  • The venue - The rooms were very spread out and difficult to find at first even with the map. More signs directing people where to find where they're going would be very helpful. Also, as with most conference rooms, more power would be awesome. 
  • No organized social - There were a few attendees that I would have liked to socialize with after the conference finished for the day, but with no social event to go to they just went home. So it ended up being just a bunch of the speakers socializing with each other. While the other speakers are cool and all, I already knew several of them. I'm not implying that the social needs to be a huge party, but just reserving a large room at a nearby restaurant would be cool.

The Ugly

  • The cold - I hate it. My thin Texan blood still hasn't warmed back up to my normal body temperature. 
  • The snow - Delayed some flights and froze me even more. I admit it, I'm a wimp when it comes to the cold.

The Sessions

As for the sessions, with four tracks I obviously missed more sessions than I attended. Some sessions were easy for me to pick, like the ones where I was speaking. Some were easy for me to decide not to go to, like Chris's testing talk which is awesome, but I've seen it. There was a good mix of basic, intermediate, and advanced talks as well as a good mix of front-end and back-end talks. The talks I personally really enjoyed:
  • Scaling PHP with HipHop by Sara Golemon - Since the first time I heard about the HipHop project I've been fascinated by it, even though I never really had a reason to use it. Her talk was interesting from a historical and architectural perspective. Again, I'm probably still not going to have an excuse to run HipHop in production, I do plan on playing around with it.
  • Caching and Tuning fun for high scalability by Wim Godden - Great pointers for when and how to use caching, with some real world examples. Much of this would be immediately useful to sites trying to scale up.
  • Embrace Your Inner Designer by Josh Broton - This was a stretch for me, since I'm about as far from a designer as they get (I don't "do" pretty). But I actually feel like I gained something from it to make me a slightly less shitty designer. And Josh probably had the best delivery of any speaker that I saw. Just don't tell him I said that.

Overall, great conference. I learned a lot, met some great new people, and had a great time. I will probably make the trip up next year as well, though I may need a heavier ski jacket to protect me from the sub-zero temperatures.

2011-11-22

Consistency is the key

http://www.flickr.com/photos/richard-g/3549285383/
In my last post, Keeping it simple, I wrote about a few things that can make you a better coder, or at least a more valuable member of a coding team. This is the next step down the path of coding nerdvana.



Style isn't just for the stylish


Every coder has their own preferred style. Left to our own devices, we tend to write code our own way. As long as you're the only one looking at the code, this isn't a problem, but consistency should still be valued.

If you're part of a team, you hopefully have a well documented style guide that everyone follows. Hopefully it covers the gritty details so that developers don't get into fist fights with each other about style differences. The last thing that a growing code base needs is for you to be able to tell who wrote a piece of code without checking your source control's blame log.

But what if it doesn't cover a style point?


Stay consistent!


Perhaps it's my upbringing as a military brat or my overly-logical thought process, but I just can't handle disorder in code. It seems so simple to me, but I see code like this entirely too often:

function foo($bar, $mitz) {
    if ( 0 == $bar ) {
        doSomething();
    }
    if($mitz == 0){
        doSomethingElse();
    }
}
There are some very valid reasons to write your if conditions one way or the other (0 == $bar instead of $bar == 0). I'm sure there are people that will make arguments about whether to put spaces outside of the if condition parenthesis, or extra spaces inside them. But doing it two different ways in a single method is just crazy.

Mixing styles in your code makes it an order of magnitude more difficult to read. Code that is hard to read is hard to maintain. Your code will spend more time in maintenance than in development, so why wouldn't you do everything you could to make it easier to maintain?

2011-11-10

Building rock solid software in the real world

http://www.flickr.com/photos/preef/32995286/
Recently (2011-11-08) I gave a talk at the Dallas PHP meetup about building rock solid software as a team. For my first experience talking in front of a crowd since high school, I thought it went pretty well. Several people have asked for me to post my slides (which I did), but they were made in a way that doesn't really help people out if they didn't see the talk. The talk was recorded and is available on Ustream, but I thought it might be helpful to do a blog post on the topics I covered as well.

This post is mainly meant to aggregate links to the topics that I talked about.


Tools


I covered several tools. All of these should be available to the developers as build targets and run in your continuous build. Lint and your unit tests should be run as part of your submission process.

  • lint - The bare minimum, it just detects syntax errors in your scripts. Code that doesn't pass the lint test won't pass any other tests or manual QA.
  • PHPUnit - Standard unit testing framework. There is plenty of information about it elsewhere.
  • PHP Code Sniffer - Detects code smells that should be fixed. Many bad programming practices can be written as "sniffs" along with most rules from your smile guide.
  • PHP Mess Detector - Statically analyzes your code for possible bugs or coding practices that tends to hide bugs.
  • PHP Copy Paste Detector - Scans your code to find large similar blocks which can be factored out to a common method.
  • PHP Dead Code Detector - Scans your code to find code that can not be reached. For example, code after a return statement.
  • Code coverage - Adding the xdebug extension to your system allows PHPUnit to calculate how much of your code is run by unit tests.


Code reviews

I talked about two different code review packages:
And I talked about three different ways of doing reviews:
  • Pre-review - Code doesn't get submitted until a peer reviews it. Keeps bad stuff out of your code base.
  • Post-review - Code gets submitted, then gets peer reviewed. Comments made about the code may never get resolved, but code reviews don't slow down getting code into production.
  • Public shaming - Put the code up on a projector and discuss as a team. Great way to destroy programmer morale.
I mentioned a few points about what to look for in a code review:
  • anything the tools couldn't catch
    • logic errors (like ifs that don't make sense)
    • loops with off-by-one errors
    • performance problems (SQL in a loop)
    • things to refactor (large methods)
  • or things they missed
    • Style problems (not really wrong, but you know, wrong)
    • Typos (variable names, documentation)
    • Tests that don't have assertions
    • Methods without tests


Style guides

There's two ways to choose a style guide:
  1. Roll your own - Look at your existing code and build the style guide from what you're already doing.
  2. Use existing - Such as Zend or Pear.

2010-09-28

Unit Testing cURL Code in PHP

My team is writing an API that will be used from other parts of our infrastructure. We're using cURL to make connections from the other parts to the API. But sometimes our network and API server are a bit flaky, which makes code that calls the API brittle if we don't handle those transient errors.

Instead of using PHP's native curl_* functions, we've created a simple class to wrap them. This helps some of the more junior team members as well, hiding some of the nitty-gritty details of making HTTP connections:


/**
 * Class abstracting the cURL library for easier use.
 *
 * Usage:
 *     $curl = new Curl();
 *     $curl->setUrl('http://www.google.com/#')
 *         ->setData('&q=testing+curl')
 *         ->setType('GET');
 *     $curl->send();
 *     echo $curl->getStatusCode(), PHP_EOL;
 *     echo $curl->getResponse(), PHP_EOL;
 */
class Curl {
    /**
     * @var string Body returned by the last request.
     */
    protected $body;

    /**
     * @var resource Actual CURL connection handle.
     */
    protected $ch;

    /**
     * @var mixed Data to send to server.
     */
    protected  $data;

    /**
     * @var integer Response code from the last request.
     */
    protected $status;

    /**
     * @var string Request type.
     */
    protected $type;

    /**
     * @var string Url for the connection.
     */
    protected $url;


    /**
     * Constructor.
     */
    public function __construct() {
        $this->body = null;
        $this->ch = curl_init();
        $this->data = null;
        $this->status = null;
        $this->type = 'GET';
        $this->url = null;
        curl_setopt($this->ch, CURLOPT_RETURNTRANSFER,
            true);
        curl_setopt($this->ch, CURLOPT_USERAGENT,
            'Curl Client');
    }

    /**
     * Return the body returned by the last request.
     * @return string
     */
    public function getBody() {
        return $this->body;
    }

    /**
     * Return the current payload.
     * @return mixed
     */
    public function getData() {
        return $this->data;
    }

    /**
     * Set the payload for the request.
     *
     * This can either by a string, formatted like a query
     * string:
     *      foo=bar&mitz=fah
     * or a single-dimensional array:
     *      array('foo' => 'bar', 'mitz' => 'fah')
     * @param mixed $data
     * @return Curl
     */
    public function setData($data) {
        if (is_array($data)) {
            $data = http_build_query($data);
        }
        $this->data = $data;
        return $this;
    }

    /**
     * Return the status code for the last request.
     * @return integer
     */
    public function getStatusCode() {
        return $this->status;
    }

    /**
     * Return the current type of request.
     * @return string
     */
    public function getType() {
        return $this->type;
    }

    /**
     * Set the type of request to make (GET, POST, PUT,
     * DELETE, etc)
     * @param string $type Request type to send.
     * @return Curl
     */
    public function setType($type) {
        $this->type = $type;
        curl_setopt($this->ch, CURLOPT_CUSTOMREQUEST,
            $type);
        return $this;
    }

    /**
     * Return the connection's URL.
     * @return string
     */
    public function getUrl() {
        return $this->url;
    }

    /**
     * Set the URL to make an HTTP connection to.
     * @param string $url URL to connect to.
     * @return Curl
     */
    public function setUrl($url) {
        $this->url = $url;
        curl_setopt($this->ch, CURLOPT_URL, $url);
        return $this;
    }

    /**
     * Send the request.
     * @return Curl|null
     */
    public function send() {
        if (!$this->url) {
            return null;
        }
        if ('GET' == $this->type) {
            $this->url .= '?' . $this->data;
        } else {
            curl_setopt($this->ch, CURLOPT_POSTFIELDS,
                $this->data);
            if ('PUT' == $this->type) {
                $header = 'Content-Length: '
                    . strlen($this->data);
                curl_setopt($this->ch, CURLOPT_HTTPHEADER,
                    array($header));
            }
        }
        $this->body = curl_exec($this->ch);
        $this->status = curl_getinfo($this->ch,
            CURLINFO_HTTP_CODE);
        return $this;
    }
}



First, here's an example class that calls to the API to pull a customer record. Assume the API returns a JSON object when called with http://api/customer/42. We pass in a Curl object to actually make the connection, then parse the response to fill our customer object's members.


class Customer {
    /**
     * @var integer Database ID.
     */
    public $id;

    /**
     * @var string Customer's name.
     */
    public $name;

    /**
     * Load a customer from the API.
     * @param integer $id ID of the customer to load.
     * @param Curl $curl Curl object.
     */
    public function load($id, Curl $curl) {
        $curl->setUrl('http://api/customer/' . (int)$id)
            ->send();
        $customer = json_decode($curl->getResponse());
        $this->id = $customer->id;
        $this->name = $customer->name;
    }
}


In a perfect world, calling load() will always populate the id and name members of the customer. In the real world, the API server could be down, or the network could die, or random alpha particles could mess up the request, so the code should handle those error messages. But setting up a test network to create those conditions is expensive and probably a waste of time, so we'll create a stub for the Curl object that allows us to break the API calls in interesting ways.


/**
 * Stub for the Curl class.
 *
 * Allows testing code that uses cURL without actually
 * making any HTTP connections.
 */
class StubCurl
extends Curl {
    /**
     * Constructor.
     */
    public function __construct() {
        $this->body = null;
        $this->data = null;
        $this->status = null;
        $this->type = 'GET';
        $this->url = null;
    }

    /**
     * Set the response 'returned' by the server.
     * @param string $body
     * @return StubCurl
     */
    public function setBody($body) {
        $this->body = $body;
    }

    /**
     * Set the HTTP status 'returned' by the server.
     * @param integer $code
     * @return StubCurl
     */
    public function setStatusCode($status) {
        $this->status = $status;
    }

    /**
     * Set the request method (GET, POST, etc).
     * @param string $type
     * @return StubCurl
     */
    public function setType($type) {
        $this->type = $type;
        return $this;
    }

    /**
     * Set the URL to connect to.
     * @param string $url
     * @return StubCurl
     */
    public function setUrl($url) {
        $this->url = $url;
        return $this;
    }

    /**
     * 'Send' the cURL request.
     *
     * Obviously doesn't actually send any requests.
     * @return StubCurl
     */
    public function send() {
        if (!$this->url) {
            return null;
        }
        if ('GET' == $this->type) {
            $this->url .= '?' . $this->data;
        }
        return $this;
    }
}


By passing a StubCurl object in to Customer::Load(), we can make the server return any status (or no status at all):


require_once 'Curl.php';
require_once 'Customer.php';
require_once 'StubCurl.php';

class CustomerTest
extends PHPUnit_Framework_TestCase {
    private $customer;

    protected function setUp() {
        $this->customer = new Customer();
    }

    /**
     * Test loading with a good API connection.
     * @covers Customer::load
     * @test
     */
    public function testLoadFound() {
        $curl = new StubCurl();
        $curl->setStatusCode(200)
            ->setBody('{"id":42,"name":"Bob King"}');
        $this->customer->load(42, $curl);
        $this->assertEquals('Bob King',
            $this->customer->name);
    }

    /**
     * Test loading a customer that isn't found.
     * Add a test for whatever behavior you want the
     * customer object to take if the API returns a 404
     * Not Found error. In this case, we're going to want
     * the customer object to throw an exception.
     * @covers Customer::load
     * @expectedException NotFoundException
     * @test
     */
    public function testLoadNotFound() {
        $curl = new StubCurl();
        $curl->setStatusCode(404);
        $this->customer->load(42, $curl);
    }

    /**
     * Test loading a customer from bad JSON.
     * The API returns a garbled response body.
     * @covers Customer::load
     * @expectedException BadResponseException
     * @test
     */
    public function testBadResponse() {
        $curl = new StubCurl();
        $curl->setStatusCode(200)
            ->setBody('{"id":_*()*(*$#@#$');
        $this->customer->load(42, $curl);
    }
}


Then the Customer class needs to have its load() method beefed up to handle the errors:


/**
 * Load a customer from the API.
 * @param integer $id ID of the customer to load.
 * @param Curl $curl Curl object.
 * @throws BadResponseException
 * @throws NotFoundException
 */
public function load($id, Curl $curl) {
    $curl->setUrl('http://api/customer/' . (int)$id)
        ->send();
    if (404 == $curl->getStatusCode()) {
        throw new NotFoundException();
    }
    $customer = json_decode($curl->getResponse());
    if (null === $customer) {
        throw new BadResponseException();
    }
    $this->id = $customer->id;
    $this->name = $customer->name;
}


Obviously you'll want to handle more errors in a production environment, maybe logging bad responses and server errors for later analysis.

2010-09-14

Avoiding PHP syntax errors with Subversion pre-commit hooks

Since my last few posts have all been about Subversion, I figured I'd do another. Our new team member is an experienced programmer, but has not had any experience with PHP. And he's not real good about actually running his code before submitting it.

PHP allows you to run its command line executable with a -l flag to catch any syntax errors in your code. Very simple, but can really help catch some stupid errors. Yes, they should definitely be caught by a code review, but code reviewers should also be able to assume that the code actually runs.

So here's a Subversion pre-commit hook that will run PHP lint on all PHP files and reject the commit if any of them fail.

2010-08-23

Forcing PHPUnit tests on Subversion commit

All of my PHP projects use PHPUnit for unit testing. One of my coworkers just refuses to understand the point of testing. He doesn't get Test Driven Development (TDD), and rarely runs the unit tests for his classes. This means that he frequently breaks my build. I can't force him to change his behavior since he doesn't work for me, but I can change the way my systems work. And I control the Subversion server.
I wanted to force unit tests to pass before allowing submission, but couldn't find anything about running PHPUnit tests in a Subversion pre-commit hook. So I wrote my own. Hopefully this will help someone out there:
To use this, copy it to the hooks directory in your Subversion repository and name it 'pre-commit'. Make sure the paths defined in the script are correct for your system. Make it executable (chmod +x pre-commit).

2010-02-22

Thoughts on Frameworks

Every developer thinks that their way of coding is better than every other programmer out there. They think their APIs are more elegant, their style is more succinct, and their loops are more optimized.

They are wrong.

I've had the misfortune to work with several custom-built frameworks. They were all terrible in their own way. Some had a slight excuse in that they were built in aging versions of PHP, but the otherall crappiness of the framework still stands on its own. And I can't claim that I think they frameworks were utter shit because I didn't write them, since I did write one of them.

But in writing my own framework and realizing just how crappy it ended up gave me the perspective to realize that I should use one of the big frameworks. I should use a Zend Framework, or Cake, or Symfony, or whatever. The programmers that wrote those frameworks may not individually be a much better developer than me, in aggregate they tower head and shoulders above. They get the benefit of having security experts picking at the seams of their interfaces, optimization guys fine tuning little used paths, and usability experts chiming in on workflow. They get the benefit of people trying to run the frameworks on operating systems you've never heard of, trying things with them you've never thought of, and using browsers you're too good to use.

Every developer should write their own framework, throw it away, and use one of the established ones.

It's so tempting to have the control over every aspect of the framework and the complete understanding of how each moving piece fits together. It's nice being able to custom build functionality for each small piece of your application into the framework-level code. But what always happens is that you start a new project and reuse your framework since you've put so much work into it. Then you find yourself having to code around all of the special cases that you put into the original. You're better off just using a more general framework from the start and putting your business logic where it belongs.