How much does it cost to be a web developer?

With Software Development topping 2014′s top jobs list, I thought I would share how much it cost me to become a web developer, and what my monthly expenses look like nowadays.

Start-Up Costs

If you’re new to the industry, you’ll need this stuff. My philosophy is that it is OK to spend money to make money, especially when it comes to getting proper tools. You have to have fast, reliable equipment or your productivity will be adversely impacted.


* Mine was a gift
** Cost with a two-year contract
*** Free for evaluation

What about training?

My advise is to skip the college thing. Don’t waste your money, and more importantly, your life.

A few books and online resources are all you really need. Google is your best friend. Read up, then go build a real, live project. That is the best way to learn.

As you struggle through, you’ll be learning to think for yourself and figure problems out on your own. This will by far be your most valuable skill.

Recurring Costs


* Verizon Wireless More Everything plan with 4GB bandwidth and mobile hotspot
** Charter 30Gbps cable connection

Free stuff

The best part about software development today is that many of the tools you need are free! These are tools I use every day. Your list may vary some, but these have been very solid in my experience.


Never before has it been so easy to get such a well-paying and rewarding job. Anyone can do it, if they are interested, but it will take drive and commitment. For about $3,000 in up-front costs, you can become a web developer. My monthly expenses run less than many student loan payments.

It will take about 4-5 years and 10,000 hours of practice and resume-building to command a 6-figure salary. Think about it: would you rather have a 4-5 year college degree and no job, or a 6-figure salary?

While many people are struggling to find employment, employers are struggling to find good software developers (emphasis on good, since mediocre devs are a dime-a-dozen).

It is a great year to be a software developer!

Deep linking into an iframe, cross-domain

Why would you want to do this? My use case was a gallery application where we needed to deep-link to a specific gallery entry. Alas, the gallery would be iframed. Yes, iframes should be avoided, but sometimes in real-life you have to just deal with it.

Imagine my surprise when I discovered that passing the parent window.location object into an iframe across domains is not only possible, but is easy and works in most browsers, down to IE8. Continue reading

Video: Estimation Protips

Video from my Estimation Protips talk to the Atlanta PHP User Group in September. Slides and links here.

Software Estimation Pro Tips from Atlanta PHP User Group on Vimeo.

You’re an expert developer, peacefully composing code into a profoundly elegant masterpiece, when suddenly your boss rushes in with the Next Big Idea that will Revolutionize The Way People Use The Internet. He’s on his way to pitch to a VC, and stops by to describe the Idea in excited terms. After a 30 second elevator pitch, he pops the question: “So, Ricky, how long do you think it will take to build this thing-a-ma-bob?”

What do you say?

In this September 5, 2013 presentation, Jonathon Hill (@compwright) presents ten Pro Tips that will cover your back, save your job, and keep your boss’s shirt. Jonathon is the Director of Development at Brandmovers (, and a steering committee member of the Atlanta PHP User Group.

Installing the CodeIgniter plugin for PHP_CodeSniffer

Since reading Phil Sturgeon’s post on PHP Static Analysis in the Sublime text editor, I have been experimenting with using phpcs and Sublime in general. Since I am currently used to the CodeIgniter coding standard, the time finally came today to try and configure my setup for that standard instead of PSR-2.

(A downgrade? Perhaps, but it’s what we use at work and it fits better into my workflow at the moment. Besides, the PSR-2 bracket rules annoy me to no end.)

After installing phpcs on top of my homebrewed php54 installation, adding the CodeIgniter standard to PHP_CodeSniffer was a little more tricky than expected. The catch is to specify the correct path to the PEAR module in the ant build script, which is NOT the same thing as the path to the phpcs command:

sudo ant -Dphpcs.dir=/usr/local/Cellar/php54/5.4.19/lib/php/PHP/CodeSniffer/

After that, we’re off to the races. Verifying that the install works:

$ /usr/local/Cellar/php54/5.4.19/bin/phpcs -iThe installed coding standards are CodeIgniter, MySource, PEAR, PHPCS, PSR1, PSR2, Squiz and Zend

Great! Now just change the phpcs settings in the Sublime PHP Code Sniffer config file to CodeIgniter, and we’re done.

"phpcs_additional_args": {
 "--standard": "CodeIgniter",
 "-n": ""

Presentation: Estimation Protips

Estimation is a topic which is extremely important to running a successful software development company, whether you are a digital agency (like Brandmovers) or a freelancer working solo.

I was honored to be the featured speaker for the September 2013 meeting of the Atlanta PHP User Group. The slides are below in case you missed it. If you are interested in the topic, there is supporting documentation below for reference.


You’re an expert developer, peacefully composing code into a profoundly elegant masterpiece, when suddenly your boss rushes in with the Next Big Idea that will Revolutionize The Way People Use The Internet. He’s on his way to pitch to a VC, and stops by to describe the Idea in excited terms. After a 30 second elevator pitch, he pops the question: “So, Peter, how long do you think it will take to build this thing-a-ma-bob?”

What do you say?

These eight Protips will cover your back, save your job, and keep your boss’s shirt.

Supporting Documentation

I drew heavily from these resources for this presentation, and highly recommend them for anyone wishing to get better at estimating projects:


Blog posts:

Tilde expansion in PHP

If you have ever written a PHP command line script and tried to pass a file to it using the POSIX tilde (~) shortcut to reference your home directory, you may have been surprised to learn that the operating system does not automatically expand tildes in paths.

You’ll get an error like this:

Warning: fopen(~/file.csv): failed to open stream: No such file or directory

Fortunately, it is an easy matter to perform tilde expansion, once you know you need to.

function expand_tilde($path)
    if (function_exists('posix_getuid') && strpos($path, '~') !== false) {
        $info = posix_getpwuid(posix_getuid());
        $path = str_replace('~', $info['dir'], $path);

    return $path;

H/T to Ernest Friedman-Hill for cluing me into the solution.

Disqus guest posting via API

While evaluating the Disqus API for things like posting and flagging as a guest, I was baffled by this non-descript error:

{"code":12,"response":"This application cannot create posts on the chosen forum"}

After checking the obvious things (like enabling guest posting and checking my domain settings for my forum and application), I was finally able to solve this using the disqus-php library:

require __DIR__ . '/disqus-php-master/disqusapi/disqusapi.php';

$disqus = new DisqusAPI($secret_key);

    'thread' => $thread_id,
    'message' => $message,
    'author_name' => $author_name,
    'author_email' => $author_email,
    'api_key' => $api_key,

The catch is that the api_key is NOT the same thing as the public key shown in your Disqus application settings. I actually had to inspect one of the AJAX calls from the Disqus Javascript widget to get the correct api_key:

Disqus AJAX call headers showing the api_key

Send mail() using PHP on Mac OS X

Thanks to Benjamin Rojas, Andy Stratton, and a tip from Jasper, I was able to successfully send email from my home-brewed MAMP environment. Here’s the summary.

  1. Add the following to your /etc/postfix/sasl_passwd file:

    (Of course, you don’t have to use GMail or port 587, but you get the idea.)

  2. Configure postfix:
    sudo postmap /etc/postfix/sasl_passwd
  3. Backup and edit your postfix configuration:
    sudo cp /etc/postfix/ /etc/postfix/
    sudo vim /etc/postfix/

    If you use TLS, then you will need to add the TLS settings but the other settings should already be there as a result of running the postmap command. You should have these options set in /etc/postfix/

    mydomain_fallback = localhost
    mail_owner = _postfix
    setgid_group = _postdrop
  4. Start postfix:
    sudo postfix start

    If there are errors, you may need to edit your /etc/postfix/ and restart postfix:

    sudo postfix reload
  5. Send a test message:
    date | mail -s test
  6. Make postfix start automatically on boot by opening your /System/Library/LaunchDaemons/org.postfix.master.plist file and adding:

    Add this at the bottom just before the closing </dict> tag.

  7. Edit your /etc/php.ini file and configure the sendmail_path option:
    sendmail_path = "sendmail -t -i"

You should now be able to send email using PHP’s mail() function. If you continue to have issues, watch the contents of your postfix mail log:

tail -f /var/log/mail.log

Write tests, you might learn something

Writing unit tests for your code is widely regarded as a best practice. There are many excuses for not writing tests: time, cost, and the fun factor. Excuses aside, there are some Good Reasons to write tests for your code. I just discovered one today.

Write tests, you might learn something…faster

I’m working on an API integration that requires a bit of AES-256 encryption. Getting that worked out in PHP took some surprising turns, so just to be sure that I was getting all the details right (such as initialization vector size) I decided to write a unit test for my mcrypt wrapper library.

Since this library had worked well in other projects, imagine my surprise when the first AES-256 encrypt-decrypt test failed:

$ phpunit *
PHPUnit 3.7.8 by Sebastian Bergmann.


Time: 0 seconds, Memory: 2.50Mb

There was 1 failure:

1) EncryptionAes256Test::testEncryptDecrypt with data set #0 ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefhijklmnopqrstuvwxyz')
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
+Binary String: 0x4142434445464748494a4b4c4d4e4f505152535455565758595a61626364656668696a6b6c6d6e6f707172737475767778797a00000000000000000000000000


Tests: 4, Assertions: 7, Failures: 1.

Echoing out the decrypted string proved just as bizarre:

string(51) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefhijklmnopqrstuvwxyz"
string(64) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefhijklmnopqrstuvwxyz"

It seems that mcrypt pads the string with enough null characters (\0) to match the block size, and unlike strings in C, PHP strings do not necessarily end with the first null character.

The fix was obvious:

$decrypted = rtrim($decrypted, "\0");

Guess what? By investing time in a simple unit test, I unexpectedly learned something new about PHP, AND saved myself quite a bit of time debugging an API call that would not have worked.

I’d say it was worth it.