Laravel 5 Step by Step Guide to Vagrant Homestead in Mac

Well, if you are looking for a step by step guide but just found the older one, you might be lucky because i have just setup mine with Vagrant Homestead which is most likely what you are looking for as well. In short, the one explain on their official website isn't very clear that i have to get out and read other article just to set this up. So for anyone convenient and for mine as well, i'll just quickly write down what i did and what makes it magically work in this step by step guide.

Install required scripts

Before you even decide to setup anything you will need to do install a number of scripts to help in setting up Homestead Vagrant.

Install Vagrant

First, go ahead and visit Vagrant official website and download the mac version of Vagrant and get it install on your machine
Screen Shot 2015-10-19 at 4.32.05 AM
Just click on the pkg and continue till the end and we are all good with Vagrant.

Install VirtualBox

Without VirtualBox don't think about getting Vagrant to work. Therefore, head over to VirtualBox official website.
Screen Shot 2015-10-19 at 5.20.46 AM

Similarly, open up the .dmg file of virtualbox and install it via .pkg file and continue till the end and we are all good with VirtualBox.

Install Composer

Now to install Composer we will need to do this. to get composer available globally.

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer

However, make sure your 'php' is available as shown here,

Clays-MacBook-Air:public clay$ whereis php
/usr/bin/php

If not, please try to install php first before doing this.

Install Laravel

Now i wanted to use the script 'laravel new xxx' so i have to install Laravel as well by firing the following composer script.

composer global require "laravel/installer=~1.1"

Once this is done you should be able to create a new laravel website by generating the base files of laravel

laravel new blog

but be patient, don't do this first since we really want to set this up with Vagrant and Homestead.

Install Vagrant Homestead Box

Now we need to install Homestead Vagrant box. We can first download this upfront so we could use it later.

vagrant box add laravel/homestead

Once you've done that you can see it via

Clays-MacBook-Air:public clay$ vagrant box list
laravel/homestead (virtualbox, 0.3.0)

which should list out what vagrant boxes you have.

Install Homestead CLI

Now before anything else, we will need to install Homestead CLI tool.

composer global require "laravel/homestead=~2.0"

and make sure the path ~/.composer/vendor/bin is available after installation. And also add them into $PATH via .bashrc or .profile whichever you but might is located at ~/.profile

export PATH=~/.composer/vendor/bin:$PATH

and add this line so that we can use whatever is in the vendor bin folder.

Setup Homestead YAML file

Now we have finish installed all required script for Laravel and Vagrant. We need to setup Homestead YAML file but first, we will need to create the configure file via

homestead init
Creating Homestead.yaml file... ok
Homestead.yaml file created at: /Users/claylua/.homestead/Homestead.yaml

Now we know where this file is located, we just need to open up and starts configuring some important parts.

vi ~/.homestead/Homestead.yaml

i use vim, you can use nano and etc or whatever you like. And the most important parts are shown below,

// this is the public key
authorize: ~/.ssh/id_rsa.pub

// this is my private key that i normally use
keys:
- ~/.ssh/id_rsa

// in my macbook, i am playing all laravel files on ~/Vagrant/Code (i created it myself)
// and on Vagrant VM iw ill place it on  /Users/claylua/Vagrant/Code
folders:
- map: ~/Vagrant/Code
  to: /Users/claylua/Vagrant/Code

// i am mapping the Laravel files to claylua.com and on vagrant of vm, it will look for the 
// directory located at /Users/claylua/Vagrant/Code/blog/public
sites:
- map: claylua.com
  to: /Users/claylua/Vagrant/Code/blog/public

// i change the name of the database
databases:
- claylua

so edit your configuration file accordingly and to your liking and we can start creating Laravel base files.

Setup Laravel Base Files

As you can see on my Homestead.yaml file, i have tell the configuration to go to the directory ~/Vagrant/Code where i will setup Laravel there.

mkdir -p ~/Vagrant/Code

the above will create the folder needed.

cd ~/Vagrant/Code
laravel new blog

The above will create a new laravel blog folder.

Setup Vagrant

Now we have finish setup both laravel files and homestead configuration. It's time to start Vagrant and access our website via

homestead up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'laravel/homestead'...
==> default: Matching MAC address for NAT networking...
==> default: Checking if box 'laravel/homestead' is up to date...
...
==> default: Forwarding ports...
default: 80 => 8000 (adapter 1)
default: 443 => 44300 (adapter 1)
default: 3306 => 33060 (adapter 1)
default: 5432 => 54320 (adapter 1)
default: 22 => 2222 (adapter 1)

Once this is done, do remember that i have setup 'claylua.com' to point to my laravel installation folder. Hence, i will need to setup this domain to point it to my local machine by doing this

sudo vi /etc/hosts

and add this line at the end

192.168.10.10 claylua.com

If you are wondering why its 192.168.10.10 its because on my homestead.yaml i have place it as this ip address. Once you've done that, all you need to do is to open up the url 'claylua.com' and you'll see the following display
Screen Shot 2015-10-19 at 4.57.36 AM
and if you don't like what you see. Just destory it via command line

homestead destroy

and if you find it too troublesome to hit one line then open up VirtualBox and 'stop and remove' it.
Screen Shot 2015-10-19 at 4.58.51 AM
And you can redo the `homestead up` again.

SSH into Vagrant VM

Since this is all VM, which is really another small machine on your physical machine, you can actually ssh into it and install any thing you want. By default you can ssh via

ssh [email protected] -p 2222
Welcome to Ubuntu 14.04.3 LTS (GNU/Linux 3.19.0-25-generic x86_64)

 * Documentation:  https://help.ubuntu.com/
Last login: Sun Oct 18 20:14:08 2015 from 10.0.2.2

and you should be in as shown above.

Connect to Vagrant Database

Now we need to see whether we can login to our mysql on Vagrant homestead machine. I'm using Sequel Pro and the default value are

Host: 127.0.0.1
User: homestead
Password: secret
Port: 33060

And if you click on 'test connection' you should see this,
Screen Shot 2015-10-19 at 5.02.32 AM
Pretty neat ya.

Environment Variable

In the case you want to send environment variable to your vagrant homestead setup, all you need to do is to open up homestead.yaml and place these new variables in

variables:
- key: APP_ENV
value: dev
- key: API_KEY
value: nudeinbath
- key: API_SECRET
value: dontlookatmelikethatyouasshole

And in your Laravel application you can use this variable like this

$app_env = getenv('APP_ENV'); // returns "dev"
$api_key = getenv('API_KEY'); // returns "nudeinbath"
$api_secret = getenv('API_SECRET'); // returns "dontlookatmelikethatyouasshole"

Adding New Sites

So you have nothing to do and wanted to add a new website to create new laravel project. Simply head over to the Homestead.yaml file and add it below,

// i am mapping the Laravel files to claylua.com and on vagrant of vm, it will look for the 
// directory located at /Users/claylua/Vagrant/Code/blog/public
sites:
- map: claylua.com
  to: /Users/claylua/Vagrant/Code/blog/public
// now i am adding a new website call shit
- map: shit.com
  to: /Users/claylua/Vagrant/Code/shit/public

Similarly, i will need to add a new laravel folder into it

cd ~/Vagrant/Code
laravel new shit

Now i have to provision my server again and map the new locations from your Homestead directory.

vagrant provision

and this doesn't work you'll do this

vagrant reload --provision

And you will see something like this

Clays-MacBook-Air:.homestead clay$ vagrant reload --provision
==> default: Attempting graceful shutdown of VM...
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
==> default: Forwarding ports...
    default: 22 => 2222 (adapter 1)
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
    default: Warning: Remote connection disconnect. Retrying...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 5.0.4
    default: VirtualBox Version: 4.3
==> default: Mounting shared folders...
    default: /vagrant => /Users/clay/.homestead

And if the provision still doesn't work, do this

Clays-MacBook-Air:.homestead clay$ homestead halt
==> default: Attempting graceful shutdown of VM...
Clays-MacBook-Air:.homestead clay$ homestead up --provision
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Checking if box 'laravel/homestead' is up to date...
==> default: Clearing any previously set forwarded ports...
==> default: Clearing any previously set network interfaces...
==> default: Preparing network interfaces based on configuration...
    default: Adapter 1: nat
    default: Adapter 2: hostonly
==> default: Forwarding ports...
    default: 80 => 8000 (adapter 1)
    default: 443 => 44300 (adapter 1)
    default: 3306 => 33060 (adapter 1)
    default: 5432 => 54320 (adapter 1)
    default: 22 => 2222 (adapter 1)
==> default: Running 'pre-boot' VM customizations...
==> default: Booting VM...
==> default: Waiting for machine to boot. This may take a few minutes...
    default: SSH address: 127.0.0.1:2222
    default: SSH username: vagrant
    default: SSH auth method: private key
    default: Warning: Connection timeout. Retrying...
==> default: Machine booted and ready!
==> default: Checking for guest additions in VM...
    default: The guest additions on this VM do not match the installed version of
    default: VirtualBox! In most cases this is fine, but in rare cases it can
    default: prevent things such as shared folders from working properly. If you see
    default: shared folder errors, please make sure the guest additions within the
    default: virtual machine match the version of VirtualBox you have installed on
    default: your host and reload your VM.
    default:
    default: Guest Additions Version: 5.0.4
    default: VirtualBox Version: 4.3
==> default: Setting hostname...
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
    default: /vagrant => /Users/clay/.composer/vendor/laravel/homestead
    default: /Users/clay/Vagrant/Code => /Users/clay/Vagrant/Code
==> default: Running provisioner: file...
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-lxzddo.sh
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-1xy8ywc.sh
==> default: nginx stop/waiting
==> default: nginx start/running, process 1659
==> default: php5-fpm stop/waiting
==> default: php5-fpm start/running, process 1674
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-12bufkv.sh
==> default: nginx stop/waiting
==> default: nginx start/running, process 1712
==> default: php5-fpm stop/waiting
==> default: php5-fpm start/running, process 1727
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-qs9lku.sh
==> default: Warning: Using a password on the command line interface can be insecure.
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-13bnnt7.sh
==> default: createdb: database creation failed: ERROR:  database "temprole" already exists
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-xzs2bs.sh
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: php5-fpm stop/waiting
==> default: php5-fpm start/running, process 1816
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: Updating to version 5a5088eb342e4876cb28472ba1fc3f1da7a14852.
==> default:     Downloading: Connecting...
==> default:
==> default:     Downloading: 100%
==> default:
==> default:
==> default: Use composer self-update --rollback to return to version a54f84f05f915c6d42bed94de0cdcb4406a4707b
==> default: Running provisioner: shell...
    default: Running: /var/folders/7h/k2cjyqms7rxfnv3mvl_b0pq00000gn/T/vagrant-shell20151102-10803-88fx5m.sh

but remember to save the new shit.com to your hosts file

sudo vi /etc/hosts

now let's add this shit

192.168.10.10 shit.com

And we are done!

Conclusion on Laravel 5

Pretty much sweet and easy installation with a large community backed up with it. I'm sure there will be more fun ahead! Enjoy your Laravel 5!

Share

Woocommerce After Checkout Hook

Here is another Woocommerce hook that i used recently that is directly call after checkout but before an order is made, it is a Woocommerce after checkout hook. In this case, you can add more validation into it to prevent the order from being creating it. To do this, all you need to do is use the hook call 'woocommerce_after_checkout_validation' which gets call after checkout validation is made.

  add_action( 'woocommerce_after_checkout_validation', 'remove_item_cart_session_expired' );
  function remove_item_cart_session_expired(){
    global $woocommerce;
    $data = WC()->session->get('mypersonalsession');
    if(!$data){
      $woocommerce->cart->empty_cart();
      wc_add_notice( __("<strong>ERROR:</strong> Code 1010 - Your session expired. Please reorder again", "test"), "error" );
    }

In this case, the validation is already completed but my session has expired. So i added an error notice so that my team can look into the error code and figure out what has gone wrong with this particular order. Of course, i emepty the cart so that the order cannot be create!

Share

Woocommerce After an Order Before Payment Hook

There are times when you want to do some action like sending the order info to another database or third party integration before payment took place. of course, after payment took place, you might still initial another type of hook which is not explain here, there are tons of them if you google around. However, before a payment is made and after a checkout is place, an order is created. This hook is often ignored and not mention around. And this is the Woocommerce hook i am going to demonstrate here.

2 Woocommerce action hook after an order is made

There are actually two action hook that you can use here which are

woocommerce_checkout_order_processed
woocommerce_new_order

Both this hook, allows you to initial your custom function immediately after an order is made such as the one below,

  add_action( 'woocommerce_checkout_order_processed', 'my_status_pending',  1, 1  );

or

  add_action( 'woocommerce_new_order', 'my_status_pending',  1, 1  );

Do bear in mind that the priority here is placed at the highest as compare to the default 10. If you just use the default priority, the chances of your payment getting directly marked as paid rather than going through the normal process of Woocommerce is high. It happens to me as i couldn't figure out how come all order are being marked as paid without going through any payment selected. It is all due to the action hook used above which you have to take note of the priority to prevent yourself losing money due to unpaid invoice marked as paid automatically.

New order hook

The argument parameter is marked as 1 which is the default since we only really need the order id of the hook as shown below,

add_action( 'woocommerce_new_order', 'my_status_pending',  1, 1  );
function my_status_pending($order_id){
// do your magic here
}

Do remember to NOT place any $woocommerce->cart->empty_cart() sentence within these methods as it will remove the item in the cart and leave other checkout method unable to proceed further. Leave the empty_cart instruction to the payment gateway to handle it.

Share

Woocommerce hook custom button on admin order page

Another Woocommerce hook that i search high and low for it but in the end dig it out from the source code itself. What happen if you would like to create a custom button on Woocommerce admin order page? Something like the image below?

Screen Shot 2015-09-16 at 7.42.19 PM

Looks like something you need? It's pretty easy by using a hook call 'woocommerce_order_item_add_action_buttons'.

woocommerce_order_item_add_action_buttons woocommerce button hook

Basically this hook allows you to add custom button similar to the one woocommerce has and ensuring that you don't hack or edit the core code to get what you want. Here is a snippet of what i did.

// add new button for woocommerce
add_action( 'woocommerce_order_item_add_action_buttons', 'action_woocommerce_order_item_add_action_buttons', 10, 1);
// define the woocommerce_order_item_add_action_buttons callback
function action_woocommerce_order_item_add_action_buttons( $order )
{
    echo '<button type="button" onclick="document.post.submit();" class="button generate-items">' . __( 'Magic Button Appear!', 'hungred' ) . '</button>';
    // indicate its taopix order generator button
    echo '<input type="hidden" value="1" name="renew_order" />';
};
// resubmit renew order handler
add_action('save_post', 'renew_save_again', 10, 3);
function renew_save_again($post_id, $post, $update){
    $slug = 'shop_order';
    if(is_admin()){
            // If this isn't a 'woocommercer order' post, don't update it.
            if ( $slug != $post->post_type ) {
                    return;
            }
            if(isset($_POST['renew_order']) && $_POST['renew_order']){
                    // do your stuff here after you hit submit
                }
    }
}

dump the above snippet to your function.php file and you should see the "magic button appear!" button on your woocommerce admin order page.

A little bit of explanation here, what the above code does it create a button which the woocommerce_order_item_add_action_buttons hook does the trick

// add new button for woocommerce
add_action( 'woocommerce_order_item_add_action_buttons', 'action_woocommerce_order_item_add_action_buttons', 10, 1);
// define the woocommerce_order_item_add_action_buttons callback
function action_woocommerce_order_item_add_action_buttons( $order )
{
    echo '<button type="button" onclick="document.post.submit();" class="button generate-items">' . __( 'Magic Button Appear!', 'hungred' ) . '</button>';
    // indicate its taopix order generator button
    echo '<input type="hidden" value="1" name="renew_order" />';
};

I've place a hidden button to identify the submit is hit from this button. Next, since woocommerce admin order page is also a post page, all i have to do is to attached it with a post handler hook.

// resubmit renew order handler
add_action('save_post', 'renew_save_again', 10, 3);
function renew_save_again($post_id, $post, $update){
    $slug = 'shop_order';
    if(is_admin()){
            // If this isn't a 'woocommercer order' post, don't update it.
            if ( $slug != $post->post_type ) {
                    return;
            }
            if(isset($_POST['renew_order']) && $_POST['renew_order']){
                    // do your stuff here after you hit submit
                }
    }
}

and make sure that this handle only runs if its a shop_order type and we are good to go! That's all! Enjoy!

Share

How to find next date removing weekend and public holiday starting from today in PHP

Ok, so here is a snippets of finding out the next business or working day excluding public holiday that i used in a project. Its pretty neat, but holidays date changes every year and each country holiday is different from each other. Therefore, the holidays are being placed in an array instead. Without further ado, here's the snippets.

// get today date
$tmpDate = date('Y-m-d');
// all the holidays
$holidays = array('2015-01-01', '2015-01-02', '2015-02-04', '2015-02-18', '2015-02-19', '2015-02-20', '2015-02-21', '2015-02-22', '2015-02-23', '2015-02-27',
    '2015-02-28', '2015-03-05', '2015-03-08', '2015-03-12', '2015-03-20', '2015-03-21', '2015-03-29', '2015-04-03', '2015-04-04', '2015-04-05',
    '2015-04-06', '2015-04-07', '2015-05-01', '2015-05-03', '2015-05-04', '2015-05-10', '2015-05-11', '2015-05-25', '2015-06-03', '2015-06-19',
    '2015-06-20', '2015-06-21', '2015-06-28', '2015-08-08', '2015-08-20', '2015-08-28', '2015-09-03', '2015-09-23', '2015-09-26', '2015-09-27',
    '2015-09-28', '2015-10-09', '2015-10-10', '2015-10-21', '2015-10-25', '2015-11-12', '2015-11-26', '2015-12-22', '2015-12-25');
// 1 day
$i = 1;
// for the next business day from today
$nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
    ' +'.$i.
    ' Weekday'));
// check whether the date of the business day is in public holiday
while (in_array($nextBusinessDay, $holidays)) {
    $i++;
    // move the business date forward since its a public holiday
    $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
        ' +'.$i.
        ' Weekday'));
}

Now, the code above will find the next working day. How about telling me how to find 10 days after the working day? Pretty much the same just repeat the same logic and place it below it,

    // get today date
    $tmpDate = date('Y-m-d');
    // all the holidays
    $holidays = array('2015-01-01', '2015-01-02', '2015-02-04', '2015-02-18', '2015-02-19', '2015-02-20', '2015-02-21', '2015-02-22', '2015-02-23',
        '2015-02-27', '2015-02-28', '2015-03-05', '2015-03-08', '2015-03-12', '2015-03-20', '2015-03-21', '2015-03-29', '2015-04-03', '2015-04-04',
        '2015-04-05', '2015-04-06', '2015-04-07', '2015-05-01', '2015-05-03', '2015-05-04', '2015-05-10', '2015-05-11', '2015-05-25', '2015-06-03',
        '2015-06-19', '2015-06-20', '2015-06-21', '2015-06-28', '2015-08-08', '2015-08-20', '2015-08-28', '2015-09-03', '2015-09-23', '2015-09-26',
        '2015-09-27', '2015-09-28', '2015-10-09', '2015-10-10', '2015-10-21', '2015-10-25', '2015-11-12', '2015-11-26', '2015-12-22', '2015-12-25');
    // 1 day
    $i = 1;
    // for the next business day from today
    $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
        ' +'.$i.
        ' Weekday'));
    // check whether the date of the business day is in public holiday
    while (in_array($nextBusinessDay, $holidays)) {
        $i++;
        // move the business date forward since its a public holiday
        $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
            ' +'.$i.
            ' Weekday'));
    }
    // working day + 10 working day excluding holidays
    $tmpDate = $nextBusinessDay;
    // 1 day
    $i = 1;
    // next business day excluding weekdays after 10 days
    $nextBusinessDay = date('Y-m-d', strtotime($tmpDate.
        ' +10 weekdays'));
    // this is the format we want it to look like
    $date = date("m月d日", strtotime($tmpDate.
        " +10 weekdays"));
    // is the next business day after 10 day a public holiday?
    while (in_array($nextBusinessDay, $holidays)) {
        // if it is, move the day by 1 
        $nextBusinessDay = date('Y-m-d', strtotime($nextBusinessDay.
            ' +'.$i.
            ' weekdays'));
        // get the format we want, actually this can be thrown outside of the loop
        $date = date('m月d日', strtotime($nextBusinessDay));
        $i++;
    }

And as you can see, i DID NOT OPTIMIZE the code. i'm sorry. don't hate me, but i do share it, so, forgive me. Anyway, the code above can be reduce by half if you dump it into a function and just reuse the logic. i kinda lazy without doing that. IM SO SORRY! but if you did optimize it and dump it into a neat function, do share it out in the comment and i'll dump it right here!

*** UPDATE ****

Ok basically the above code doesn't remove the issue when there are days when the holidays fall between 2 dates. Anyhow, the more appropriate code that takes care of ALL holidays are

// find latest working day
$tmpDate = date('Y-m-d');
$holidays = array('2016-02-06','2016-02-07','2016-02-08','2016-02-09','2016-02-10','2016-02-11','2016-02-12','2016-02-13','2016-02-14','2016-02-27','2016-02-28','2016-02-29','2016-04-02','2016-04-03','2016-04-04','2016-04-05','2016-06-09','2016-06-10','2016-06-11','2016-06-12','2016-09-15','2016-09-16','2016-09-17','2016-09-18','2016-10-09','2016-10-10','2016-10-11');
$i = 1;
$nextBusinessDay = date('Y-m-d', strtotime($tmpDate . ' +' . $i . ' Weekday'));

while (in_array($nextBusinessDay, $holidays)) {
  $i++;
  $nextBusinessDay = date('Y-m-d', strtotime($tmpDate . ' +' . $i . ' Weekday'));
}
// working day + 10 working day excluding holidays
$tmpDate = $nextBusinessDay;
$startDate = new DateTime( $tmpDate );    //intialize start date
$endDate = new DateTime( '2016-12-31' );    //initialize end date
$holiday = array('2016-02-06','2016-02-07','2016-02-08','2016-02-09','2016-02-10','2016-02-11','2016-02-12','2016-02-13','2016-02-14','2016-02-27','2016-02-28','2016-02-29','2016-04-02','2016-04-03','2016-04-04','2016-04-05','2016-06-09','2016-06-10','2016-06-11','2016-06-12','2016-09-15','2016-09-16','2016-09-17','2016-09-18','2016-10-09','2016-10-10','2016-10-11');
$interval = new DateInterval('P1D');    // set the interval as 1 day
$daterange = new DatePeriod($startDate, $interval ,$endDate);
// find all the dates that are working days
foreach($daterange as $date){
        if($date->format("N") <6 AND !in_array($date->format("Y-m-d"),$holiday))
                $result[] = $date->format("Y-m-d");
}
// now we want 10 working days starting from 0 (hence the 9 )
echo $result[9];

Basically, we first find the next business day as the 'starting' date and also initial the 'end' date which is the end of 2016 in this case, the 'results' will gives me a list of working dates. From the list of work date, i need 10 working days, and since its an array, something like 10th working day can be simply print out like the one show in the code

Share