June 18, 2024
Breaking News

Is It Possible to Write and Run PHP Code on an iPad? – SitePoint

This article was peer reviewed by Claudio Ribeiro. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

I love the iPad. It’s a fantastic form factor for media consumption and gaming; and it also works well as an e-reader. The trouble is I don’t use it nearly as much as I could. Most of the time I’m consuming media (Netflix, Twitch, YouTube), I’m coding in parallel.

I can do that on my MacBook, but I’ve never been able to do that until now. Two things have made it possible:

  1. iOS recently added support for picture in picture playback. That means I can keep a video app playing, while working on something else.

  2. Fantastic apps (like DraftCode and Working Copy) make it possible to code and run PHP apps, and push changes to Github, directly from the iPad.

Today I’m going to show you how I code on an iPad. I won’t pretend it’s a perfect workflow (what workflow is?), but this is as exciting for me as the first time I used a laptop instead of a desktop.

I started writing this post a while ago. To tell you the truth, I don’t think the apps I’ve going to talk about were ready then. They’ve both received updates since. It’s as though the developers could hear inside my brain, and wanted to make me happy. I also apologize for the size of this page; there are many animated gifs..


One of the most important parts of this whole experiment, is finding the right keyboard. When it comes to mobile devices, there’s no shortage of poor-quality hardware.

I searched for quite some time before I settled on a keyboard I liked the look of. I can safely say I made the right choice.

This keyboard is about twice the length of the iPad Mini 2, and weighs about the same. The keys feel amazing, with plenty of space between them. And it’s quiet!

This keyboard also has a trackpad. iOS doesn’t support mouse or trackpad (as far as I’m aware), so you don’t have to go with a keyboard that has a trackpad. I like this because it can double as an input device for my Raspberry Pi (using a single USB port for keyboard and trackpad).

Unless you’re using a Bluetooth keyboard, you’re going to need a USB port. Fortunately, Apple produce an adapter (originally made for digital camera compatibility), that works wonderfully with every USB keyboard I’ve ever tried.

The Windows key doubles as a command key, and Windows + z / Windows + x / Windows + c / Windows + v all work as you’re used to. You can also use Windows + → and Windows + ← to move to the beginning and end of lines.

The All-in-one Media keyboard is listed as USD 39.95 on the Microsoft website, but you can pick it up for USD 29.99 on Amazon. The Lightning USB adapter is listed as USD 29.99 on the Apple website, and actually costs a couple dollars more from Amazon.


I’ve already mentioned a couple of apps (DraftCode and Working Copy), and the truth is these are the only two you need.

What tools do you use to code? You probably use an IDE (or at the very least a text editor, like Atom). You probably use something like Git, to organize and version your source code.

DraftCode is a text editor with syntax highlighting. It doesn’t yet support IDE-like features like code completion and static analysis, but it does come with the ability to run the PHP scripts you write in it.

What’s more, it includes a file tree through which you can manage files and folders. It’s possible (though somewhat laborious) to structure an entire PHP application through this interface.

We’ll take a look at how to run PHP apps in a bit…

Working Copy is a Git client. The free version supports Github, Bitbucket, and any other Git URL source. It supports cloning, fetching, and merging. I haven’t tried anything else with it, except for pushing.

If you want to be able to push the changes you’ve coded on an iPad, you’re going to have to fork out for the enterprise version. The only notable difference (given how little I’ve used it) is that you can only push code to a remote on the enterprise version.

DraftCode costs USD 10.99, and Working Copy (enterprise) costs USD USD 14.99. While I was recording (for the gifs you see before you), I noticed a strange export bug. When the free version was installed, I could export to it. When the enterprise version was installed, I couldn’t. At this stage, I recommend using the free version’s in-app enterprise purchase (for the same price) instead of buying the enterprise version directly.

Writing Your First Script(s)

Let’s take a look at the process for writing PHP scripts in DraftCode. After loading (and deleting all manner of example files), the interface is remarkably clean:

At the bottom of the file tree (the left panel), there are a few buttons. The Cog (settings button) shows a bit of information about the app, and provides buttons to install WordPress and phpLiteAdmin (a SQLite phpMyAdmin clone).

Then, there are buttons for creating files, folders, importing media files, and batch moving files (in that order). The media importer draws from the iPad’s media library, so it supports any audio/video/image file you could otherwise play on the iPad.

Let’s create a script. It’s useful to know which version of PHP and which modules are available through the app. You can create a file named just about anything (but a common name for this kind of script would be index.php or phpinfo.php):

Trying SQLite

You may have noticed a few mentions of MySQL (in phpinfo). That doesn’t mean that you can start using a MySQL database on your iPad, just that you could connect to a publicly accessible MySQL server.

DraftCode does have the SQLite extension though. Let’s use that, and PDO, to test whether we can use SQLite databases:


$handle = new PDO("sqlite::memory:");


        message TEXT

$query = "
    INSERT INTO messages (message) VALUES (:message)

$statement = $handle->prepare($query);
$statement->bindParam(":message", $message);

$message = "hello world";


Most of this is just standard PDO example code, though I’ll forgive you for being unfamiliar with it. We live in the age of database abstractions layers and object-relational mappers.

We open a handle to an in-memory SQLite database, and set the error mode to throw exceptions. Then we create a messages table, with a single message text field.

We follow this up by preparing a SQL query, and binding a :message parameter to this prepared statement. Finally, we execute the prepared statement, and fully expect the messages table to contain a single row.

Let’s try fetching this row, using the following code:

$messages = $handle->query("
    SELECT * FROM messages

foreach($messages as $message) {
    print "message: " . $message["message"] . "<br>" . PHP_EOL;

SQLite isn’t a replacement for other, more robust databases, but we can use it to develop apps that will only ever serve a single user.

Pulling Code With Working Copy

More often than not, I find myself working on an existing codebase. It would be awful if I had to recreate the whole working directory by hand. Luckily we can just use Working Copy to pull the source code from a Git server.

If we plan to push code back up to Github (assuming that’s the remote we’re using), it’s a good idea to set up an identity and connect with Github:

It’s not strictly necessary, but at least it’s out the way (for when we want to push code we’ve added/changed). The next step is to clone a repository. We can send code to DraftCode using the share dialog. Working Copy archives the repository, and stores some PHP code to extract it to the iOS clipboard. To extract the repository (inside DraftCode), we need to create a new file, paste the clipboard code, and run it.

Installing Laravel

The latest version of Laravel (5.3 as I write this) supports PHP 5.6 and SQLite databases. That’s great news for us, but there’s still a bit of work we need to do to get it working.

Committing Dependencies

As we’ve seen, any code we want to run has to be run through DraftCode’s server. That means we can’t easily use Composer to install dependencies. It’s theoretically possible to make a script for this, but that’s a topic for another time.

The quickest way to give Laravel all the dependencies it needs, is to store them in Git. That way, we can pull an entire Git repository down, and deploy it to DraftCode’s working directory.

vendor is ignored in Laravel apps by default. You’ll have to remove it from .gitignore to be able to commit it to the repository.

Customizing The Environment

.env is another ignored file. When you pull the Git repository down, you’re probably not going to have a version of the Laravel app will use (unless you intentionally committed .env in the first place).

It’s not currently possible to create (or edit) .env or .env.example without first giving them a .txt extension. You’ll need .env, with a valid APP_KEY value before Laravel can create sessions.


The version of PHP DraftCode provides doesn’t have the iconv_* function set. We can use the patchwork/utf8 library to shim these functions.

Laravel 4.* used to have patchwork/utf8 as a dependency. This was removed in 5.0

To add it, we can use:

composer require patchwork/utf8

As with most PHP shims, the right time to add them is directly following the Composer autoloader. The very first time this happens in Laravel, is in public/index.php.

In fact, the default state of public/index.php is:


require __DIR__.'/../bootstrap/autoload.php';

$app = require_once __DIR__.'/../bootstrap/app.php';

$kernel = $app->make(IlluminateContractsHttpKernel::class);

$response = $kernel->handle(
    $request = IlluminateHttpRequest::capture()


$kernel->terminate($request, $response);

This is from https://github.com/laravel/laravel/blob/5dc817bf55598fda017e3b7f39957447bda94846/public/index.php

So, to add the shim for iconv_* functions, we need to add the following line:

require __DIR__.'/../bootstrap/autoload.php';


Random Bytes

Laravel also depends on paragonie/random_compat:~1.4|~2.0. This library provides various shims for the newer cryptography improvements of the language. Chief among them is the ability to generate random data in a cryptographically secure way.

What makes this generation cryptographically secure is the source of entropy. The shim uses various sources, which can be seen here.

Unfortunately, the only source supported by the DraftCode version of PHP is the openssl_random_pseudo_bytes function. And though the comment says that the library will fall back to openssl_random_pseudo_bytes as a last resort, this functionality was removed in version 1.3, to be replaced by a function that throws an exception.

Laravel requires ~1.4, which means we can’t require ^1.2 in our project without getting a composer error. The only alternative is to get the implementation from the 1.2 version of paragonie/random_compat, and including it before Composer’s autoloader.

If you’re wondering why we would need to include it before, it’s because paragonie/random_compat registers that exception-throwing random_bytes function globally (and as soon as it is loaded by Composer). This creates a situation where one shim library prevents all other shim libraries (of the same type) from working. A curious position to take…

Please don’t let my criticism of this single aspect of paragonie/random_compat make you think I don’t like the library. It’s fantastic, and you should probably be using it in favor of your own shim implementation.

The openssl_random_pseudo_bytes implementation source looks like this:

function random_bytes($bytes)
    try {
        $bytes = RandomCompat_intval($bytes);
    } catch (TypeError $ex) {
        throw new TypeError(
            'random_bytes(): $bytes must be an integer'

    if ($bytes < 1) {
        throw new Error(
            'Length must be greater than 0'

    $secure = true;
    $buf = openssl_random_pseudo_bytes($bytes, $secure);
    if (
        $buf !== false
        RandomCompat_strlen($buf) === $bytes
    ) {
        return $buf;

    throw new Exception(
        'Could not gather sufficient random data'

This is from here

You can save that anywhere you like, but I’ve saved it to path/to/project/compat-random.php. We need to add it above the Composer autoloader:

if (!function_exists('random_bytes')) {
    require_once __DIR__.'/../compat-random.php';

require __DIR__.'/../bootstrap/autoload.php';


This way, the openssl_random_pseudo_bytes implementation will be used (if random_bytes doesn’t already exist), and paragonie/random_compat won’t register an exception-throwing random_bytes.

This is obviously not as secure as the alternative shim functions inside paragonie/random_compat. You shouldn’t run this hack in production.

Custom Routes

Before we talk about custom routes, let’s run the Laravel app as it is:

So far, so good! But, if we’re running public/index.php then how do we ever change routes to somewhere else in our app? To enable this, we need to modify public/index.php again:

$_SERVER["REQUEST_URI"] = $_GET["url"];

$response = $kernel->handle(
    $request = IlluminateHttpRequest::capture()

Before each request, we override the REQUEST_URI with whatever we provide to the url query string parameter. This works because Request::capture() proxies to the underlying HTTPRequest::createFromGlobals Symfony implementation. The same implementation builds various URI states based on REQUEST_URI.

Now, we can create new routes, like:

Route::get("/then", function() {
    return "hello world";

…And navigate to them with the URL

Pushing Code With Working Copy

Once you’ve made changes to your repository, you’ll probably want to commit and push them. Navigate up to the root repository folder, and tap the folder name. You’ll see an option to zip the folder. This will take some time, depending on the size of the codebase. When complete, you can select and share the archive with Working Copy.

Working Copy can then identify which files were changed, and allows you to commit them back to the repository.


In this tutorial, we looked at the hardware, software, and configuration requirements for developing PHP apps on the iPad. We looked at some of the supported features (and limitations) of DraftCode’s PHP.

Before we part ways, I think it’s a good idea to talk about why we’d want to code on the iPad. This has been a fun experiment for me, but I don’t imagine an iPad could become my coding machine of choice. The apps have improved (dramatically) over the past few weeks, but they’re still slow to use (owing to the limitations of sandboxing and not being able to run CLI scripts).

There is a certain appeal to using the iPad, if you find yourself flying often. If you can get a neat case for them, the iPad, keyboard and adapter are much neater than a MacBook and charger. The iPad can charge on a plane, and the touch screen makes the occasional break-for-a-game viable.

That said, any Surface + keyboard combo is probably more comfortable to use. With a Surface, you don’t need to install these specialist apps – you can install a normal IDE and a usable PHP 7.0 environment.

So, am I saying you should go for a Surface? No.

Let’s look at the costs involved, for the workflow I’ve described in this tutorial:

  1. You need an iPad, a keyboard, and a USB connector. If you already own an iPad, that’s USD 29.99 for the connector and the cost of the keyboard. Or you could get a Bluetooth keyboard.
  2. You need DraftCode, which is USD 10.99. You can get away with the free version of Working Copy (if all you’re not interested in pushing back to a remote, or you’re happy emailing your project around).

Let’s say you had none of these things. You’d have to pay:

  1. USD 269 → iPad Mini 2 (Apple)
  2. USD 29.99 → Camera kit connector (Apple)
  3. USD 29.99 → All-in-one Media keyboard (Amazon)
  4. USD 10.99 → DraftCode
  5. USD 14.99 → Working Copy

That comes to USD 381.96 (or USD 85.96 if you already own the iPad). The cheapest Surface 3 you can buy (from Microsoft) is USD 499, and you don’t even get the keyboard with it. Aside from coding, I’d far rather have the iPad.

It’s not going to replace your current workflow, but it’ll do in a pinch, and it’s fun to try. Have you tried something similar yet? How do you code when traveling? Let us know what worked for you in the comments below.