Posted: March 25th, 2011 | Author: Alex Korn | Filed under: Tutorials | Tags: design patterns, PHP | No Comments »
The observer pattern is a handy design pattern often used in UI-focused languages like JavaScript, but not often used in PHP. Prior to PHP 5.3, the observer pattern was cumbersome to use and required a lot of objects – one for every possible observer. However, anonymous functions (Closures
) in PHP 5.3 can replace the previously used Observer objects. This makes the observer pattern much more practical by reducing the number of classes that you need to make, often for one-time use.
Let’s write up our basic Observable object. You may want to make this class abstract since it’s probably not useful on its own.
class Observable
{
/**
* @var array Map<string eventName, List<Closure observer>> $_observers
*/
protected $_observers = array();
/**
* @param string $eventName
* @param array $data
*/
protected final function _fireEvent($eventName, array $data = null)
{
if (isset($this->_observers[$eventName]))
{
foreach ($this->_observers[$eventName] as $observer)
{
$observer($data);
}
}
}
/**
* @param string $eventName
* @param Closure $observer With parameter (array $data)
*/
public final function addObserver($eventName, Closure $observer)
{
if (!isset($this->_observers[$eventName]))
{
$this->_observers[$eventName] = array();
}
$this->_observers[$eventName][] = $observer;
}
/**
* @param string $eventName
* @param Closure $observer The observer to remove
*/
public final function removeObserver($eventName, Closure $observer)
{
if (isset($this->_observers[$eventName]))
{
foreach ($this->_observers[$eventName] as $key => $existingObserver)
{
if ($existingObserver === $observer)
{
unset($this->_observers[$eventName][$key]);
}
}
}
}
} |
class Observable
{
/**
* @var array Map<string eventName, List<Closure observer>> $_observers
*/
protected $_observers = array();
/**
* @param string $eventName
* @param array $data
*/
protected final function _fireEvent($eventName, array $data = null)
{
if (isset($this->_observers[$eventName]))
{
foreach ($this->_observers[$eventName] as $observer)
{
$observer($data);
}
}
}
/**
* @param string $eventName
* @param Closure $observer With parameter (array $data)
*/
public final function addObserver($eventName, Closure $observer)
{
if (!isset($this->_observers[$eventName]))
{
$this->_observers[$eventName] = array();
}
$this->_observers[$eventName][] = $observer;
}
/**
* @param string $eventName
* @param Closure $observer The observer to remove
*/
public final function removeObserver($eventName, Closure $observer)
{
if (isset($this->_observers[$eventName]))
{
foreach ($this->_observers[$eventName] as $key => $existingObserver)
{
if ($existingObserver === $observer)
{
unset($this->_observers[$eventName][$key]);
}
}
}
}
}
Now that we have the basic observable class, let’s look at a simple example:
class Person extends Observable
{
protected $_name;
protected $_friends = array();
public function __construct($name)
{
$this->_name = $name;
}
public function getName()
{
return $this->_name;
}
public function getIntroducedTo(Person $person)
{
$this->_friends[] = $person;
$this->_fireEvent('introduced', array('other' => $person, 'me' => $this));
}
}
$sally = new Person('Sally');
$sally->addObserver('introduced', function(array $data)
{
echo 'Hi, ' . $data['other']->getName() . ', my name is ' .
$data['me']->getName() . '.';
});
$sally->getIntroducedTo(new Person('Harry')); |
class Person extends Observable
{
protected $_name;
protected $_friends = array();
public function __construct($name)
{
$this->_name = $name;
}
public function getName()
{
return $this->_name;
}
public function getIntroducedTo(Person $person)
{
$this->_friends[] = $person;
$this->_fireEvent('introduced', array('other' => $person, 'me' => $this));
}
}
$sally = new Person('Sally');
$sally->addObserver('introduced', function(array $data)
{
echo 'Hi, ' . $data['other']->getName() . ', my name is ' .
$data['me']->getName() . '.';
});
$sally->getIntroducedTo(new Person('Harry'));
The output will be "Hi, Harry, my name is Sally."
. Note that you can easy modify the greeting Sally uses from outside that Person
object without changing the functionality of adding the friend.
And there you have it – the observer pattern and an example, in less than 75 lines of code!
Posted: March 17th, 2011 | Author: Alex Korn | Filed under: AWS, Hosting, Tutorials | Tags: amazon, apache, ec2, linux, mysql, PHP | 29 Comments »
Do you know nothing about Amazon Web Services (AWS) or Linux server administration, but want to get a PHP/MySQL server set up on AWS? I was once like you, relying upon my web host to have PHP and MySQL installed and configured, so it was a bit daunting initially to work with AWS, but it’s actually rather simple. Read on and I’ll show you how to set up PHP and MySQL on one of Amazon’s free servers step by step. You can have a functioning site up and running within half an hour.
Amazon Web Services
First things first: Amazon Web Services has a ton of different products, but the one you want is Amazon Elastic Compute Cloud (EC2). Go there, and click “Sign Up for Amazon EC2”.
Once you’ve gotten that set up, go to the AWS Management Console, and click on “Instances” in the Navigation panel. An Instance is just a virtual server – so let’s create one! Click “Launch Instance” under “My Instances”, and select “Basic 64-bit Amazon Linux AMI”. On the Instance Details phase, select “Micro” (which is Free tier eligible). Continue until you need to enter the “Name” – if you don’t know what else to call it, just use “Web/DB server”.
Next you create a Key Pair – this will be the credentials you’ll use to SSH into the box. The instructions here should be fairly straightforward. Next is the Security Group, which will be used to specify the firewall used for your instance. Feel free to use the default Group for now. Continue to the Review phase and launch it!
You should now be able to SSH into your instance using your .pem file with ssh -i [FILE NAME].pem ec2-user@ec2-[IP ADDRESS].compute-1.amazonaws.com
. Alright, we’ve got a server up and running! However, you may notice that this server has very little installed on it. which php
? Nothing. which mysql
? The same. Let’s install some software.
Configuring the Linux Server
Below I’ll show you how to set up PHP and MySQL on the server. I’ve separated PHP and MySQL so that it’s easier to adapt this to having two instances.
PHP
First, the basics for PHP:
sudo yum install php-mysql php php-xml php-mcrypt php-mbstring php-cli mysql httpd
Press ‘y’ for each of the prompts that shows up. Note that you’re logged in as ec2-user, so you need to sudo all of these commands.
You should now be able to create and run a PHP test file. Next, let’s get MySQL up and running.
MySQL server
First, install and begin running the server:
sudo yum install mysql-server
sudo /etc/init.d/mysqld start
Next, set the root password. I’ve found this password generator to be just dandy.
mysqladmin -u root password '[PASSWORD]'
Now we set up two users for MySQL: the administrator, which you’ll use to create and modify tables; and the app user, which the app will use to query the DB (with more limited privileges). Log into MySQL as root
(mysql -u root -p
) and enter each of the following lines:
CREATE DATABASE [DB NAME];
CREATE USER '[DB NAME]_admin'@'localhost' IDENTIFIED BY '[ADMIN PASSWORD]';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER ON [DB NAME].* TO '[DB NAME]_admin'@'localhost';
CREATE USER '[DB NAME]_user'@'localhost' IDENTIFIED BY '[USER PASSWORD]';
GRANT SELECT, INSERT, UPDATE, DELETE ON [DB NAME].* TO '[DB NAME]_user'@'localhost';
You may want to fine-tune your database settings further than this, but this is a good start.
Make it web-accessible
We now have PHP and MySQL running on the box, but cannot access it through a browser. We need to configure the web server and set up an Elastic IP.
Web Server
First, let’s create a test PHP file that will be accessed by the browser. Create directories so that you can put your file in /opt/app/current
1. Make an index.php
file that contains whatever you want.
If you want to FTP transfer files to your server, you’ll want to give the ec2-user permissions to modify files in your web directory:
sudo chown ec2-user /opt/app/current
To set up the web server, httpd, we need to first modify its configuration file, located at /etc/httpd/conf/httpd.conf
. Open it up with vim
, emacs
, or your favorite text editor, and go to the bottom of the file. Here you’ll see a small section on the VirtualHost (between <VirtualHost *:80>
and </VirtualHost>
). Uncomment it out and set DocumentRoot to /opt/app/current
. Restart (or start) httpd
:
sudo /etc/init.d/httpd restart
Elastic IP and Security Groups
In the AWS Management Console, click on “Elastic IPs”, then “Allocate New Address” under “Addresses”. Once the address is created, click on it, then “Associate Address”. Select the instance and associate it.
Now click on “Security Groups” in the Navigation panel. Select the Security Group that you used for the instance (probably the default one). Under the “Inbound” tab, add an HTTP rule (port 80). Click “Apply Rule Changes”, and you should now be able to access your website! In your browser, go to http://ec2-[IP ADDRESS].compute-1.amazonaws.com/
, where the IP address is the Elastic IP you made with periods replaced with hyphens.
Hello World! or Putting it all together
We now have all the pieces we need to access MySQL from PHP and serve that to the browser-accessible website. So let’s log into mysql
and create a sample table:
mysql -u [DB NAME]_admin -p
[type password]
mysql> use [DB NAME];
mysql> CREATE TABLE test (message VARCHAR(255));
mysql> INSERT INTO test (message) VALUES ('Hello world!');
Now modify your index.php file (/opt/app/current/index.php
) to be the following:
<?php
$conn = new mysqli('localhost', '[DB NAME]_user', '[USER PASSWORD]', '[DB NAME]');
$result = $conn->query("SELECT message FROM test;");
$row = $result->fetch_assoc();
echo $row['message'];
?> |
<?php
$conn = new mysqli('localhost', '[DB NAME]_user', '[USER PASSWORD]', '[DB NAME]');
$result = $conn->query("SELECT message FROM test;");
$row = $result->fetch_assoc();
echo $row['message'];
?>
We now have a fully functioning PHP and MySQL server!
Taking it further
That’s it for the basics, but there’s so much more that you can do now.
sudo pear upgrade
sudo yum install php-pear
sudo pear channel-discover pear.phpunit.de
sudo pear channel-discover pear.symfony-project.com
sudo pear channel-discover components.ez.no
sudo pear install phpunit/PHPUnit
I’ve found it handy to set up an administration area for my sites using a different port on the same URL. Note that port 80 is the default for web traffic, but 8080 is also commonly used.
Create /opt/app/admin
. Then, in httpd.conf, add the line Listen 8080
after Listen 80
and add another VirtualHost entry, using <VirtualHost *:8080>
and pointing to the /opt/app/admin
directory. Update your Security Group to allow traffic over port 8080. Make sure to restart Apache and you should now be able to access your admin folder through your browser at yourdomain.com:8080
.
You can then download phpMyAdmin into /opt/app/admin/pma
and unzip it. Using the [DB NAME]_admin
user, you can now manage your databases there through your browser.
Using two Instances
It can be very beneficial to performance to separate the web server and the DB server. To do this, you’ll need to set up two Instances, one of which has the web server httpd
running and an Elastic IP, and the other of which has the MySQL server mysqld
running.
They can use the same Security Group, but you’ll have to add the MySQL rule (port 3066) for Inbound traffic to allow the servers to talk to each other.
On the web box, instead of using “localhost” as the MySQL host, use the Internal IP address of the MySQL box. On the DB box, set up your grant permissions to allow from anywhere in '%.ec2.internal'
(or just from your IPs).
Notes
-
/opt/app/current
is a Rails convention that I enjoy. What you should do is put your releases in /opt/app/releases/[release #]
, then have /opt/app/current
be a symlink
to the current release.
Another (much more) common standard is to put web-accessible code in /var/www/html
. Feel free to put your HTML code wherever you want; just make sure to update httpd.conf
appropriately (and restart Apache).
^ Back up
Thanks to Ryan Ausanka-Crues at Palomino Labs for help with this.