Use PHP to query the Mimecast API

Mimecast has fairly detailed documentation and code examples for their API but nothing for PHP.

Here’s a basic PHP code example for their “Get Account” API endpoint. I used their Python sample code as a starting point. You’ll need to update the base URL and the keys to use it.

<?php

// Setup required variables
$baseUrl = 'https://xx-api.mimecast.com';
$uri = '/api/account/get-account';
$url = $baseUrl.$uri;
$accessKey = 'YOUR ACCESS KEY';
$secretKey = 'YOUR SECRET KEY';
$appId = 'YOUR APPLICATION ID';
$appKey = 'YOUR APPLICATION KEY';

// Generate request header values
$requestId = uniqid();
$hdrDate = gmdate('r');

// DataToSign is used in hmac_sha1
$dataToSign = implode(':', array($hdrDate, $requestId, $uri, $appKey));

// Create the HMAC SHA1 of the Base64 decoded secret key for the Authorization header
$hmacSha1 = hash_hmac('sha1', $dataToSign, base64_decode($secretKey), true);

// Use the HMAC SHA1 value to sign the hdrDate + ":" requestId + ":" + URI + ":" + appkey
$sig = base64_encode($hmacSha1);

// Create request headers
$headers = array(
    'Authorization: MC '.$accessKey.':'.$sig,
    'x-mc-req-id: '.$requestId,
    'x-mc-app-id: '.$appId,
    'x-mc-date: '.$hdrDate,
    'Content-Type: application/json',
    'Accept: application/json',
);

$payload = json_encode(array('data' => array()));

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);

print_r(json_decode(curl_exec($ch)));

curl_close($ch);

Using TCPDF with Symfony 2

Using TCPDF with Symfony2 is pretty simple. However there are a few problems that may arise.

namespace Acme\DemoBundle\Controller;

class PdfController extends Controller
{
    public function pdfAction()
    {
        $pdf = new \TCPDF();

        // Construct the PDF.

        $pdf->Output('filename.pdf');
    }
}

Easy enough. The PDF loads, but we get this error in the logs:

request.CRITICAL: Uncaught PHP Exception LogicException: "The controller must return a response (null given). Did you forget to add a return statement somewhere in your controller?"

We can fix that:

namespace Acme\DemoBundle\Controller;

use Symfony\Component\HttpFoundation\Response;

class PdfController extends Controller
{
    public function pdfAction()
    {
        $pdf = new \TCPDF();

        // Construct the PDF.

        $pdf->Output('filename.pdf');

        return new Response(); // To make the controller happy.
    }
}

Add some authentication and remember me tokens, close the browser, relaunch the browser, and visit the PDF page. We get a new error:

request.CRITICAL: Uncaught PHP Exception RuntimeException: "Failed to start the session because headers have already been sent by "[...]/vendor/tecnick.com/tcpdf/include/tcpdf_static.php"

Darn. TCPDF::Output() sends headers before Symfony has the chance. We can fix that too:

namespace Acme\DemoBundle\Controller;

use Symfony\Component\HttpFoundation\StreamedResponse;

class PdfController extends Controller
{
    public function pdfAction()
    {
        $pdf = new \TCPDF();

        // Construct the PDF.

        return new StreamedResponse(function () use ($pdf) {
            $pdf->Output('filename.pdf');
        });
    }
}

Perfect. Now Symfony and TCPDF::Output() can both send their headers, and everything plays nice.

Symfony 2: Using @ParamConverter with multiple Doctrine entities

Symfony 2 describes how to use parameter converters to translate slugs to entities, but their example does not enforce the relationship between the two entities. Here’s their example:

/**
 * @Route("/blog/{date}/{slug}/comments/{comment_slug}")
 * @ParamConverter("post", options={"mapping": {"date": "date", "slug": "slug"}})
 * @ParamConverter("comment", options={"mapping": {"comment_slug": "slug"}})
 */
public function showAction(Post $post, Comment $comment)
{
}

Assuming that Post has id and slug attributes and that Comment has id, post, and slug attributes, the above example does not require that the Post slug in the URL match the Comment’s Post. Here’s an example that requires that the Post and Comment are related:

/**
 * @Route("/blog/{post_date}/{post_slug}/comments/{slug}")
 * @ParamConverter("post", options={"mapping": {"post_date": "date", "post_slug": "slug"}})
 */
public function showAction(Post $post, Comment $comment)
{
}

This example works because Post is processed first and because $post is named the same as the Comment::$post relationship. When it comes time to process the Comment, it attempts to use slug and post to find the Comment instead of just slug. (No @ParamConverter annotation is necessary for the Comment because the parameters are named the same as the attributes.) If $post doesn’t match $comment->post, a 404 is returned, no additional checks required.

Vim: Indext PHP case and default statements in a switch block

By default, Vim does not indent case and default statements inside of a switch block in a PHP file:

switch ($foo) {
case 'bar':
    // do something
    break;
}

It turns out a single line in one’s vimrc can fix that:

let g:PHP_vintage_case_default_indent = 1

Now indenting is correct:

switch ($foo) {
    case 'bar':
        // do something
        break;
}