PHP - - By Wern Ancheta

Automate PHP with Phake – Real World Examples

Automating PHP with Phake

In part one, we covered the basics of Phake and demonstrated ways of executing tasks with it, covering groups, dependencies, and arguments. In this part, we’ll look at some sample real world applications of Phake. Note that the following examples are largely based on things that I usually do manually that need some sort of automation.

Uploading Files to Server with a Phake task

Let’s start by writing a task that will upload files to an FTP server.

task('up', function($args){

  $host = $args['host'];
  $user = $args['user'];
  $pass = $args['pass'];
  $port = 21; //default ftp port
  $timeout = 60; //timeout for uploading individual files
  
  //connect to server
  $ftp = ftp_connect($host, $port, $timeout);
  ftp_login($ftp, $user, $pass); //login to server

  $root_local_dir = $args['local_path'];
  $root_ftp_dir = $args['remote_path'];
  $dir_to_upload = $args['local_path'];

  $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir_to_upload), RecursiveIteratorIterator::SELF_FIRST);

  //iterate through all the files and folders in the specified directory
  foreach($iterator as $path){
    //if the current path refers to a file
    if($path->isFile()){	

      $current_file = $path->__toString();       	
      $dest_file = str_replace($root_local_dir, $root_ftp_dir, $current_file);   	
      $result = ftp_nb_put($ftp, $dest_file, $current_file, FTP_BINARY);
      
      //while the end of the file is not yet reached keep uploading
      while($result == FTP_MOREDATA){
        $result = ftp_nb_continue($ftp);
      }
      
      //once the whole file has finished uploading
      if($result == FTP_FINISHED){
        echo "uploaded: " . $current_file . "\n";
      }
			
     }else{
       //if the current path refers to a directory
       $current_dir = $path->__toString();
   
       //if the name of the directory doesn't begin with dot
       if(substr($current_dir, -1) != '.'){ 
         //remove the path to the directory from the current path
         $current_dir = str_replace($root_local_dir, '', $current_dir);
         //remove the beginning slash from current path
         $current_dir = substr($current_dir, 1);
          
         //create the directory in the server
         ftp_mksubdirs($ftp, $root_ftp_dir, $current_dir);
         echo "created dir: " . $current_dir . "\n";
       }
     } 
   }
});

As you can see from the code above, the task accepts five arguments:

  • host – the domain name or the ip address of the server where you want to upload the files
  • user
  • pass
  • local_path – the path that you want to upload
  • remote_path – the path in the server

We then pass on these arguments to the ftp_connect method. The ftp_connect method returns an ftp stream which we then supply as a first argument for the ftp_login method along with the username and password.

After that, we can iterate through all the files in the local path using the recursive iterator iterator and recursive directory iterator. The recursive iterator iterator takes up a recursive directory iterator object as its first argument and the second argument is a constant from the RecursiveIteratorIterator class. In our task, we have supplied RecursiveIteratorIterator::SELF_FIRST as the second argument. This instructs the iterator to include the full path in every iteration. We then loop through all the files and directories in that path using a foreach loop.

We can check whether the current path is a file using the isFile method. If it’s a file, we just convert it to a string using the __toString magic method, and once converted we assign it to the $current_file variable. Next, we declare another variable ($dest_file) that will hold the path to the current file without the path to the root directory. This will be used as the name of the path for the file that will be created on the server. Once those variables have values assigned to them, we can supply them as the arguments for the ftp_nb_put method. This takes the ftp stream which was returned by the ftp_connect method earlier as the first argument. The second argument is the path to the file on the server and the third one is the path to the file in your computer. The fourth argument is the mode of transfer; in this case we used FTP_BINARY. We can then upload the file by using the ftp_nb_continue method which takes the ftp stream as its argument. As long as this method returns the FTP_MOREDATA constant it means the end of the file still hasn’t been reached, so we’re using a while loop to check for it. Once it’s done it returns FTP_FINISHED.

If the current path refers to a directory, we also convert it to a string, then check if it begins with ., because as you might already know we need to exclude directories that begin with .. A popular example is the .git directory that git uses to store history. We need to exclude these directories as they’re often not needed for the app or website to run and contain meta data or versioning info.

If the current directory isn’t a hidden directory, we remove the root path from it because we don’t need to create that path in the server. That is why we’re using str_replace to replace the part that we don’t need with an empty string.

Once that’s done we can just call the ftp_mksubdirs method to create the path in the server.

The ftp_mksubdirs method takes the ftp stream, the root directory in the server and the path to the current directory. Note that this is not a method that’s built into PHP; we first need to declare it before we can use it. Here’s the function:

function ftp_mksubdirs($ftpcon, $ftpbasedir, $ftpath){
   @ftp_chdir($ftpcon, $ftpbasedir);
   $parts = explode('/',$ftpath); 
   foreach($parts as $part){
      if(!@ftp_chdir($ftpcon, $part)){
	ftp_mkdir($ftpcon, $part);
	ftp_chdir($ftpcon, $part);
      }
   }
}

What this function does is create the directories if they do not exist yet. So if you pass in the following path:

app/views/users

…and the the app, views and users directories don’t exist on the server yet, it creates all of them.

You can run the task above by executing a command like this one in the terminal:

phake up host=someserver.com user=me pass=secret local_path=/home/wern/www/someproject remote_path=/public_html

Note that this task applies to my own personal use case mostly – you might be doing your deploys another way, or you might not be needing the str_replace part at all. What matters is that this is a live task that I actually use, and which makes my life easier.

Seeding the Database

With Phake we can also write a task that will seed the database for us. For that, we’re going to need to use a library called Faker. To use Faker include the following in your project’s composer.json file:

{
  "require": {
    "fzaninotto/faker": "1.5.*@dev" 
  }
}

Next create a task and call it seed_users. It will take one argument called rows. This allows you to set the number of rows you want to insert into your tables.

task('seed_users', function($args){
    $rows = 1000;
    if(!empty($args['rows']){
        $count = $args['rows'];
    }
});

Next, we connect to the database using Mysqli:

$host = 'localhost';
$user = 'user';
$pass = 'secret';
$database = 'test';

$db = new Mysqli($host, $user, $pass, $database);

Then, we initialize a new Faker generator and assign it to the $faker variable. With a for loop, we repeatedly execute the code which generates and saves the user data into the database. We set it to execute until it reaches the number of rows that we have as an argument. By default it will execute 100 times which generates 100 random users, but all with the same password, so we can easily log in as one in case we need to.

$faker = Faker\Factory::create();

for($x = 0; $x <= $rows; $x++){

    $email = $faker->email;
    $password = password_hash('secret', PASSWORD_DEFAULT);
    $first_name = $faker->firstName;
    $last_name = $faker->lastName;
    $address = $faker->address;

    $db->query("INSERT INTO users SET email = '$email', password = '$password'");

    $db->query("INSERT INTO user_info SET first_name = '$first_name', last_name = '$last_name', address = '$address'");
}

echo "Database was seeded!\n";

Putting everything together we end up with the following task:

task('seed_users', function($args){
        
  $rows = 1000;
  if(!empty($args['rows']){
    $rows = $args['rows'];
  }

  $host = 'localhost';
  $user = 'user';
  $pass = 'secret';
  $database = 'test';

  $db = new Mysqli($host, $user, $pass, $database); 
  $faker = Faker\Factory::create();

  for($x = 0; $x <= $rows; $x++){

    $email = $faker->email;
    $password = password_hash('secret', PASSWORD_DEFAULT);
    $first_name = $faker->firstName;
    $last_name = $faker->lastName;
    $address = $faker->address;

    $db->query("INSERT INTO users SET email = '$email', password = '$password'");

    $db->query("INSERT INTO user_info SET first_name = '$first_name', last_name = '$last_name', address = '$address'");
  }

  echo "Database was seeded!\n";
});

Once that’s done you can call the task by executing the following command:

phake seed_users rows=1000

Syncing Data

Another task that I usually find myself doing repetitively is syncing the database changes from my local machine to a server. With Phake, this can be easily automated by using mysqldump, a database backup program that comes with MySQL. From the task, all you have to do is execute the mysqldump command using the exec method in PHP. The mysqldump program accepts the name of the database, the user and the password as its arguments. You can then pipe it to the ssh command. Once ssh is executed, it will ask you for the password, and once you’ve entered the password it will execute the mysql $remote_db command. What this does is get the query that was dumped by the mysqldump command earlier and execute it on the server. Note that this overwrites whatever it is that’s currently on the server so this is only recommended when you’re still in the development phase of your app.

task('sync_db', function($args){
  $db = $args['db']; //name of the local database
  $user = $args['db_user']; //database user
  $pass = $args['db_pass']; //the user's password
  $host = $args['host']; //domain name of the server you want to connect to
  $ssh_user = $args['ssh_user']; //the user used for logging in
  $remote_db = $args['remote_db']; //the name of the database in the server
  exec("mysqldump $db -u $user -p$pass | ssh $ssh_user@$host mysql $remote_db");
});

You can call the task above by using the following command. Just substitute the credentials for your own:

phake sync_db db=my_db db_user=me db_pass=secret ssh_user=me host=somewebsite.com remote_db=my_db

Note that if this doesn’t work you might need to set the following values in your sshd_config file located at /etc/ssh/sshd_config:

X11Forwarding yes
X11UseLocalhost no

Once that’s done reload sshd for the changes to take effect:

sudo /etc/init.d/sshd reload

Conclusion

Phake is a nice way to automate mundane tasks and save time by making the computer do the work for you. With Phake, you can automate pretty much anything that you can think of. It provides you with with all the necessary tools for task automation. If you find yourself doing something repetitive, think of how you can automate it with Phake.

Got any interesting tasks and use cases you’d like to share with us? Let us know in the comments.