Matthew Weier O'PhinneyDeployment with Zend Server (Part 1 of 8) (16.9.2014, 13:30 UTC)

I manage a number of websites running on Zend Server, Zend's PHP application platform. I've started accumulating a number of patterns and tricks that make the deployments more successful, and which also allow me to do more advanced things such as setting up recurring jobs for the application, clearing page caches, and more.

Yes, YOU can afford Zend Server

"But, wait, Zend Server is uber-expensive!" I hear some folks saying.

Well, yes and no.

With the release of Zend Server 7, Zend now offers a "Development Edition" that contains all the features I've covered here, and which runs $195. This makes it affordable for small shops and freelancers, but potentially out of the reach of individuals.

But there's another option, which I'm using, which is even more intriguing: Zend Server on the Amazon Web Services (AWS) Marketplace. On AWS, you can try out Zend Server free for 30 days. After that, you get charged a fee on top of your normal AWS EC2 usage. Depending on the EC2 instance you choose, this can run as low as ~$24/month (this is on the t1.micro, and that's the total per month for both AWS and Zend Server usage). That's cheaper than most VPS hosting or PaaS providers, and gives you a full license for Zend Server.

Considering Zend Server is available on almost every PaaS and IaaS offering available, this is a great way to try it out, as well as to setup staging and testing servers cheaply; you can then choose the provider you want based on its other features. For those of you running low traffic or small, personal or hobbyist sites, it's an inexpensive alternative to VPS hosting.

So... onwards with my first tip.

Tip 1: zf-deploy

My first trick is to use zf-deploy. This is a tool Enrico and I wrote when prepping Apigility for its initial stable release. It allows you to create deployment packages from your application, including zip, tarball, and ZPKs (Zend Server deployment packages). We designed it to simplify packaging Zend Framework 2 and Apigility applications, but with a small amount of work, it could likely be used for a greater variety of PHP applications.

zf-deploy takes the current state of your working directory, and clones it to a working path. It then runs Composer (though you can disable this), and strips out anything configured in your .gitignore file (again, you can disable this). From there, it creates your package.

One optional piece is that, when creating a ZPK, you can tell it which deployment.xml you want to use and/or specify a directory containing the deployment.xml and any install scripts you want to include in the package. This latter is incredibly useful, as you can use this to shape your deployment.

As an example, on my own website, I have a CLI job that will fetch my latest GitHub activity. I can invoke that in my post_stage.php script:


if (! chdir(getenv('ZS_APPLICATION_BASE_DIR'))) {
  throw new Exception('Unable to change to application directory');
}

$php = '/usr/local/zend/bin/php';

$command = $php . ' public/index.php githubfeed fetch';
echo "\nExecuting `$command`\n";
system($command);

One task I always do is make sure my application data directory is writable by the web server. This next line builds on the above, in that it assumes you've changed to your application directory first:


$command = 'chmod -R a+rwX ./data';
echo "\nExecuting `$command`\n";
system($command);

Yes, PHP has a built-in for chmod, but it doesn't act recursively.

For ZF2 and Apigility applications, zf-deploy also allows you to specify a directory that contains the *local.php config scripts for your config/autoload/ directory, allowing you to merge in configuration specific for the deployment environment. This is a fantastic capability, as I can keep any private configuration separate from my main repository.

Deployment now becomes:


$ vendor/bin/zfdeploy.php mwop.net.zpk --configs=../mwop.net-config --zpk=zpk

and I now have a ZPK ready to push to Zend Server.

In sum: zf-deploy simplifies ZPK creation, and allows

Truncated by Planet PHP, read more at the original (another 1095 bytes)

Link
Matthew Weier O'PhinneyDeployment with Zend Server (Part 2 of 8) (16.9.2014, 13:30 UTC)

This is the second in a series of eight posts detailing tips on deploying to Zend Server. The previous post in the series detailed getting started with Zend Server on the AWS marketplace and using zf-deploy to create ZPK packages to deploy to Zend Server.

Today, I'm looking at how to created scheduled/recurring jobs using Zend Server's Job Queue; think of this as application-level cronjobs.

Tip 2: Recurring Jobs

I needed to define a few recurring jobs on the server. In the past, I've used cron for this, but I've recently had a slight change of mind on this: if I use cron, I have to assume I'm running on a unix-like system, and have some sort of system access to the server. If I have multiple servers running, that means ensuring they're setup on each server. It seems better to be able to define these jobs at the applicaton level.

Since Zend Server comes with Job Queue, I decided to try it out for scheduling recurring jobs. This is not terribly intuitive, however. The UI allows you to define scheduled jobs... but only gives options for every minute, hour, day, week, and month, without allowing you to specify the exact interval (e.g., every day at 20:00).

The PHP API, however, makes this easy. I can create a job as follows:


$queue = new ZendJobQueue();
$queue->createHttpJob('/jobs/github-feed.php', [], [
  'name'       => 'github-feed',
  'persistent' => false,
  'schedule'   => '5,20,35,40 * * * *',
]);

Essentially, you provide a URL to the script to execute (Job Queue "runs" a job by accessing a URL on the server), and provide a schedule in crontab format. I like to give my jobs names as well, as it allows me to search for them in the UI, and also enables linking between the rules and the logs in the UI. Marking them as not persistent ensures that if the job is successful, it will be removed from the events list.

The question is, where do you define this? I decided to do this in my post_activate.php deployment script. However, this raises two new problems:

  • Rules need not just a path to the script, but also the scheme and host. You _can_ omit those, but only if the script can resolve them via $_SERVER... which it cannot due during deployment.
  • Each deployment adds the jobs you define... but this does not overwrite or remove the jobs you added in previous deployments.

I solved these as follows:


$server = 'http://mwop.net';

// Remove previously scheduled jobs:
$queue = new ZendJobQueue();
foreach ($queue->getSchedulingRules() as $job) {
    if (0 !== strpos($job['script'], $server)) {
        // not one we're interested in
        continue;
    }

    // Remove previously scheduled job
    $queue->deleteSchedulingRule($job['id']);
}

$queue->createHttpJob($server . '/jobs/github-feed.php', [], [
  'name'       => 'github-feed',
  'persistent' => false,
  'schedule'   => '5,20,35,40 * * * *',
]);

So, in summary:

  • Define your rules with names.
  • Define recurring rules using the schedule option.
  • Define recurring rules in your deployment script, during post_activate.
  • Remove previously defined rules in your deployment script, prior to defining them.

Next time...

The next tip in the series is a short one, perfect for following the US Labor Day weekend, and details something I learned the hard way from Tip 1 when setting up deployment tasks.

Other articles in the series

I will update this post to link to each article as it releases.
Link
Matthew Weier O'PhinneyDeployment with Zend Server (Part 3 of 8) (16.9.2014, 13:30 UTC)

This is the third in a series of eight posts detailing tips on deploying to Zend Server. The previous post in the series detailed creating recurring jobs via Zend Job Queue, à la cronjobs.

Today, I'm sharing a very short deployment script tip learned by experience.

Tip 3: chmod

In the first tip, I detailed writing deployment scripts. One of the snippets I shared was a chmod routine:


$command = 'chmod -R a+rwX ./data';
echo "\nExecuting `$command`\n";
system($command);

The code is fine; what I did not share is where in the deployment script you should invoke it. As I discovered from experience, this is key.

Zend Server's deployment scripts run as the zend user. If they are writing any data to the data directory, that data is owned by the zend user and group -- and often will not be writable by the web server user. If you have scheduled jobs that need to write to the same files, they will fail... unless you have done the chmod after your deployment tasks are done.

So, that's today's tip: if you need any directory in your application to be writable by scheduled jobs, which will run as the web server user, make sure you do your chmod as the last step of your deployment script.

Next time...

The next tip in the series is another short one, and will detail how to secure your Job Queue job scripts.

Other articles in the series

I will update this post to link to each article as it releases.
Link
Matthew Weier O'PhinneyDeployment with Zend Server (Part 5 of 8) (16.9.2014, 13:30 UTC)

This is the fifth in a series of eight posts detailing tips on deploying to Zend Server. The previous post in the series detailed how to secure your Job Queue job scripts.

Today, I'm sharing some best practices around writing job scripts, particularly around how to indicate execution status.

Tip 5: Set your job status

You should always set your job script status, and exit with an appropriate return status. This ensures that Job Queue knows for sure if the job completed successfully, which can help you better identify failed jobs in the UI. I use the following:


// for failure:
ZendJobQueue::setCurrentJobStatus(ZendJobQueue::FAILED);
exit(1);

// for success:
ZendJobQueue::setCurrentJobStatus(ZendJobQueue::OK);
exit(0);

I also have started returning relevant messages. Since Job Queue aggregates these in the UI panel, that allows you to examine the output, which often helps in debugging.


exec($command, $output, $return);
header('Content-Type: text/plain');
if ($return != 0) {
    ZendJobQueue::setCurrentJobStatus(ZendJobQueue::FAILED);
    echo implode("\n", $output);
    exit(1);
}

ZendJobQueue::setCurrentJobStatus(ZendJobQueue::OK);
echo implode("\n", $output);
exit(0);

Here's sample output:

(The [0;34m]-style codes are colorization codes; terminals capable of color would display the output in color, but Zend Server, of course, is seeing plain text.)

In sum: return appropriate job status via the ZendJobQueue::setCurrentJobStatus() static method and the exit() code, and send output to help diagnose issues later.

Next time...

The next tip in the series discusses setting up page caching in Zend Server, as well as creating jobs to clear page caches.

Other articles in the series

I will update this post to link to each article as it releases.
Link
Matthew Weier O'PhinneyDeployment with Zend Server (Part 4 of 8) (16.9.2014, 13:30 UTC)

This is the fourth in a series of eight posts detailing tips on deploying to Zend Server. The previous post in the series detailed a trick I learned about when to execute a chmod statement during deployment.

Today, I'm sharing a tip about securing your Job Queue job scripts.

Tip 4: Secure your job scripts

In the second tip, I detailed when to register job scripts, but not how to write them. As it turns out, there's one very important facet to consider when writing job scripts: security.

One issue with Job Queue is that jobs are triggered... via the web. This means that they are exposed via the web, which makes them potential attack vectors. However, there's a simple trick to prevent access other than from Job Queue; add this at the top of your job scripts:


if (! ZendJobQueue::getCurrentJobId()) {
    header('HTTP/1.1 403 Forbidden');
    exit(1);
}

While the jobs are invoked via HTTP, Zend Server has ways of tracking whether or not they are being executed in the context of Job Queue, and for which job. If the ZendJobQueue::getCurrentJobId() returns a falsy value, then it was not invoked via Job Queue, and you can exit immediately. I like to set a 403 status in these situations as well, but that's just a personal preference.

Next time...

The next tip in the series is builds on this one, and gives some best practices to follow when writing your job scripts.

Other articles in the series

I will update this post to link to each article as it releases.
Link
Matthew Weier O'PhinneyDeployment with Zend Server (Part 6 of 8) (16.9.2014, 13:30 UTC)

This is the sixth in a series of eight posts detailing tips on deploying to Zend Server. The previous post in the series detailed setting job script status codes.

Today, I'm sharing some tips around setting up page caching, and jobs for clearing the Zend Server page cache.

Tip 6: Page caching

Zend Server offers page caching. This can be defined per-application or globally. I typically use global rules, as I most often define server aliases; application-specific rules are based on the primary server name only, which makes it impossible to cache per-hostname.

I define my rules first by setting up my rules using regular expressions. For instance, for my current site, I have this for the host:


(www\.)?mwop.net

This allows me to match with or without the www. prefix.

After that, I define regular expressions for the paths, and ensure that matches take into account the REQUEST_URI (failure to do this will cache the same page for any page matching the regex!).

When I deploy, or when I run specific jobs, I typically want to clear my cache. To do that, I have a Job Queue job, and in that script, I use the page_cache_remove_cached_contents() function defined by the page cache extension in Zend Server.

This function accepts one argument. The documentation says it's a URL, but in actuality you need to provide the pattern from the rule you want to match; it will then clear caches for any pages that match that rule. That means you have to provide the full match -- which will include the scheme, host, port, and path. Note the port -- that absolutely must be present for the match to work, even if it's the default port for the given scheme.

What that means is that in my example above, the argument to page_cache_remove_cached_contents() becomes http://(www\.)?mwop\.net:80/resume. If I allow both HTTP and HTTPS access, then I also will need to explicitly clear https://(www\.)?mwop\.net:443/resume. Note that the regexp escape characters are present, as are any conditional patterns.

My current cache clearing script looks like this:


chdir(__DIR__ . '/../../');

if (! ZendJobQueue::getCurrentJobId()) {
    header('HTTP/1.1 403 Forbidden');
    exit(1);
}

$paths = [
    '/',
    '/resume',
];

foreach ($paths as $path) {
    page_cache_remove_cached_contents(
        'http://(www\.)?mwop\.net:80' . $path
    );
}

ZendJobQueue::setCurrentJobStatus(ZendJobQueue::OK);
exit(0);

If I wanted to get more granular, I could alter the script to accept rules and URLs to clear via arguments provided by Job Queue; see the Job Queue documentation for information on passing arguments.

I queue this script in my post_activate.php deployment script, but without a schedule:


$queue->createHttpJob($server . '/jobs/clear-cache.php', [], [
    'name' => 'clear-cache',
    'persistent' => false,
]);

This will schedule it to run immediately once activation is complete. I will also queue it from other jobs if what they do should result in flushing the page cache; I use the exact same code when I do so.

Note on cache clearing

The Zend Server PHP API offers another function that would appear to be more relevant and specific: page_cache_remove_cached_contents_by_uri(). This particular function accepts a rule name, and the URI you wish to clear, and, as documented, seems like a nice way to clear the cache for a specific URI as a subset of a rule, without clearing caches for all pages matching the rule. However, as of version 7.0, this functionality does not work properly (in fact, I was unable to find any combination of rule and url that resulted in a cache clear). I recommend using page_cache_remove_cached_contents() only for now, or using full page caching within your framework.

Next time...

The next tip in the series discusses using the Zend Server SDK for deploying your application from the command line.

Truncated by Planet PHP, read more at the original (another 724 bytes)

Link
Matthew Weier O'PhinneyDeployment with Zend Server (Part 7 of 8) (16.9.2014, 13:30 UTC)

This is the seventh in a series of eight posts detailing tips on deploying to Zend Server. The previous post in the series detailed setting up and clearing page caching.

Today, I'm sharing how to use the Zend Server SDK to deploy your Zend Server deployment packages (ZPKs) from the command line.

Tip 7: zs-client

Zend Server has an API, which allows you to interact with many admin tasks without needing access to the UI, and in an automated fashion. The API is extensive, and has a very complex argument signing process, which makes it difficult to consume. However, this is largely solved via zs-client, the Zend Server SDK.

The first thing you need to do after downloading it is to create an application target. This simplifies usage of the client for subsequent requests, allowing you to specify --target={target name} instead of having to provide the Zend Server URL, API username, and API token for each call.

This is done using the addTarget command:


$ zs-client.phar addTarget \
> --target={unique target name} \
> --zsurl={URL to your Zend Server instance} \
> --zskey={API username} \
> --zssecret={API token} \
> --http="sslverifypeer=0"

The zsurl is the scheme, host, and port only; don't include the path. You can find keys and tokens on the "Administration > Web API" page of your Zend Server UI, and can even generate new ones there.

Note the last line; Zend Server uses self-signed SSL certificates, which can raise issues with cURL in particular -- which the SDK uses under the hood. Passing --http="sslverifypeer=0" fixes that situation.

Once you've created your target, you need to determine your application identifier. Use the applicationGetStatus command to find it:


$ zs-client.phar applicationGetStatus --target={unique target name}

Look through the list of deployed applications, and find the of the application.

From here, you can now deploy packages using the applicationUpdate command:


$ zs-client.phar applicationUpdate \
> --appId={id} \
> --appPackage={your ZPK} \
> --target={unique target name}

In sum: the Zend Server SDK gives us the tools to automate our deployment.

Next time...

The next tip in the series details automating deployments using zf-deploy and zs-client.

Other articles in the series

I will update this post to link to each article as it releases.
Link
Qafoo - PHPTesting Micro Services (16.9.2014, 05:21 UTC)
I recently had a short exchange with Ole Michaelis on Twitter about how to end-to-end test micro services. Since I didn't have time to make my whole case, Ole suggested that I blog about this, which I'm happily doing now.The idea behind micro service architecture was discussed by Martin Fowler in a nice to read blog post, so I won't jump on that topic in detail here.
Link
Cal EvansInterview with Sara Golemon (16.9.2014, 05:00 UTC)

Twitter: @saramg

Show Notes

Link
Evert PotAccessing protected properties from objects that share the same ancestry. (15.9.2014, 17:55 UTC)

I realized something odd about accessing protected properties the other day. It's possible in PHP to access protected properties from other objects, as long as they are from the same class, as illustrated here:

<?php

class MyClass {

    protected $val;

    function __construct($newVal = 'default') {

        $this->val = $newVal;

    }

    function output(MyClass $subject) {

        echo $subject->val, "\n";

    }
}


$obj1 = new MyClass();
$obj2 = new MyClass("hello world");

$obj1->output($obj2);
// Output: hello world

?>

I always thought that protected strictly allows objects to access things from the current inheritence tree, but didn't realize that this also extends to other instances of the same object.

This behavior works for properties and methods, and also when they are defined as private.

The other day, I realized though that this even works for objects of other classes, as long as you are accessing members that are defined in a class that also appears in the accessing class' ancestry.

Sounds a bit complicated, so here's the example:

<?php

class Ancestor {

    protected $val = 'ancestor';

}


class Child1 extends Ancestor {

    function __construct() {

        $this->val = 'child1';

    }

}

class Child2 extends Ancestor {

    function output(Ancestor $subject) {

        echo $subject->val, "\n";

    }

}

$child1 = new Child1();
$child2 = new Child2();

$child2->output($child1);
// Output: child1

?>

Interestingly, if the last example is modified so that the properties are not set in the constructors, but instead by overriding the property, this will

Truncated by Planet PHP, read more at the original (another 3607 bytes)

Link
LinksRSS 0.92   RDF 1.
Atom Feed   100% Popoon
PHP5 powered   PEAR
ButtonsPlanet PHP   Planet PHP
Planet PHP