Tuesday, February 7, 2012

Maintain a DRY philosophy in Codeigniter


This is my second part in why I really like Codeigniter, it follows on from this post. Whereas the first post was a comparison of Codeigniter and Yii, this post is purely Codeigniter related.

When you first learn Codeigniter, you will build your controllers, models and views and plug them all in accordingly. Ok, now imagine a situation where you have multiple controllers, i.e. News, Content and Testimonial - all are part of the application and all the controllers need the same basic information/variables passed through to the views for output. To make life easier for yourself you could create a very 'fat' controller and keep all functionality inside said controller, but my preference is always to use multiple 'thin' controllers as it makes application much easier to manage.

Imagine you have to build a large application which has many, many different subject areas, with multiple features, i.e. Admin areas, customer areas, public areas and you need to do it in an efficient manner. This is where the DRY (Do not repeat yourself) approach comes in handy, very handy.

For example an array of dynamically generated links that are being created from a class you have written. Using a DRY philosophy you only want to call your link generating method once, not once in every controller or method/action. This is how to do it.

In a lot of Codeigniter examples found on the web you will see the following being used:

$this->load->view('view-page',$data);

...which passes the data from your controller in to your view.

However, you do have another tool that you can use accomplishing this task and that is the method:

$this->load->vars();

So in your your constructor method, you are going to use the $this->load->vars() method to pass through your links array in to each of your methods/actions like so:

class news extends CI_Controller {

function __construct() {
    $data['links'] = $this->link_generator->get_links();
    $this->load->vars($data);
}

function list_news() {
    //code to go here
}

function news_item() {
     //code to go here
}

}



Using the above, every single method/action that is called will be able to successfully access the links variable.

Now, and this is where it gets a little more complicated, we are going to take the above and combine it with extending the CI_controller, so that we can use the code we created in the construct method in all our controllers, so that we do not repeat ourselves. Please believe me, once you have mastered extending the Basic CI controller, the amount of work you will find yourself doing will decrease considerably.

As I do not like to steal other people's work I will point you in this direction: here which will take you to a post from a chap named Phil Sturgeon. Once you have mastered that, you should be able to apply the examples above in to base classes you create using Phil Sturgeon's methods.

I for example, create a base class for every major area of the website and most of my applications have this structure when it comes to base classes.

-- base_admin
-- base_public

...with each controller extending the ones above. Using this method then allows me, for example to add in all my session and user identity checks within my base admin class' constructor and it only has to be called once, rather than in every single controller.  Of course, this is only one simple example, believe me, you can do some really cool stuff with this. I have used this method in so many ways; in shopping sites, user based driven sites, so please give it a go, you'll be extremely glad you did.



Labels: , , , , ,

Tuesday, January 24, 2012

Turn a word into an array in PHP

Was mucking around with creating a little word game the other day and needed to turn a string in to an array, this is how it was done:

$word = 'words';
$len = strlen($word);
$exploded_word = array();

for($i = 0; $i < $len; $i++) {
    $exploded_word[] = $word[$i];
}
Would produce:
Array
(
    [0] => w
    [1] => o
    [2] => r
    [3] => d
    [4] => s
)

Labels: ,

Monday, January 9, 2012

Looping through select fields and getting the selected text using jQuery

I spent a little time wrestling with this and I thought it may come in useful to pop on to the blog. In my situation I had to loop through a number of select items as I needed to post some data. I needed to do this as I had to pass 3 sets of information from each select / drop down box. It had to be done this way because I was using the dropdown boxes for a number of different functions, some needed to pull the text and some needed to pull the value.
  • The name of the select box
  • The value of the select box
  • The text of the selected item
$('select').each(function(){
     var selected_text = $(this).children('option:selected').text();
})
Happy programming :)

Labels: , ,

Thursday, January 5, 2012

Work out the days between two dates using PHP

I've popped this up as I needed to work out the number of dates in between two dates. Again something similar might be out there, but I thought it may be useful for any one out there that may need it.

Parameters

Required - date1 and date2: Dates in the following format yyyy-mm-dd
Optional - inclusive: If you set to true it includes the two provided dates in it's calculation
Optional - return format of the dates: Combination of PHP's date formatting options

Return Values

Returns an array of dates and the number of days

The Code

/**
    * Get the difference between two dates...
    * @param string $date1 yyyy-mm-dd
    * @param string $date2 yyyy-mm-dd
    * @param boolean $inclusive
    * @param string $return format
    *
    * @return array of total number of days and the dates in the prescribed format
*/

function days_between($date1, $date2, $inclusive = true, $return_format = 'Y-m-d') {
       
        $date_array = array();
        $output_array = array();
       
        $date1_ts = strtotime($date1);
        $date2_ts = strtotime($date2);
        $second_difference = $date2_ts-$date1_ts;
        $num_days = floor($second_difference / 86400);
     
        if (is_numeric($num_days) && $num_days > 0) {
         
            for ($i = 0; $i <= $num_days; $i++) {
                $new_day = $date1_ts + ($i*86400);
                $date_array[] = date($return_format,$new_day);
            }

            if (!$inclusive) {
                unset($date_array[0]);
                array_pop($date_array);
            }
         
            $output_array['number_of_days'] = sizeof($date_array);
            $output_array['dates_between'] = $date_array;  
         
        }

        return ($output_array);
     
}    
Usage

days_between('2011-12-31', '2012-01-05', 1,'d M Y');

Labels: ,

Friday, December 30, 2011

Use search with Codeigniter pagination

Like I mentioned in a previous post, Codeigniter is extremely flexible and the following code is a perfect example of this. This post on using search with Codeigniter pagination uses only standard Codeigniter functions and requires no extra classes or functions.

Benefits to using this method

No need to use any caching to pass information across pages
No need to use any JavaScript / jQuery to amend links
You can pass any number of parameters through the $_GET array - these may include number of links per page, result ordering information etc. as it rebuilds the URL at page load

Downsides

You are using GET which isn't really the Codeigniter way

Notes on usage

This solution requires a form submitted through GET

The beauty of this method is that it allows the programmer to use any number of form fields. There is no need to use a session cookie to keep track of what a user is searching for, it is all done through the $_GET array.

The example uses some pretty standard stuff in terms of Codeigniter. I have called my model 'm_db_results', but that will probably be something completely different in your application. You could also improve the script by writing a better method for counting your results, I use this method for brevity only.

For all other info or help, just follow the inline help :)

public function search() {
 
    $this->load->model('m_db_results');                
    $this->load->library('pagination');
 
    //you can add any key => value parameter to your base_url
    //it just needs one to create a well formed GET request
    $config['base_url'] = '/site/search/?search=true';        

    // as we are unsure as to what values are going to passed to your pagination page
    // we need to build the base url on the fly  
    foreach ($_GET as $key=>$value) {
       if ($key != 'search' && $key != 'offset') {
           $config['base_url'] .= '&'.$key.'='.$value;
       }
    }
 
    //this will need to go to a model method which determines the total number of results
    //based on your form/search parameters. In my case the model method takes an array which in this case
    //is based on key/value pairs that are same mapped the same as the form inputs
    $results_info = $this->m_db_results->search($_GET);
 
    //the key to using this is that it will append your uri with the query string segment parameter
    //using this allows in conjunction with the $config['page_query_string'] being set to true
    //as it adds the offset to the URI as &offset=x
    $config['query_string_segment'] = 'offset';
    $config['page_query_string'] = true;

    $config['total_rows'] = count($results_info);
    $config['per_page'] = $number_per_page = 10;
 
    //now instead of using the uri segment to determine the number of results we offset
    //from our results we use the parameter we specified above in the $config['query_string_segment']      
    if (!empty($_GET['offset'])) {
       $offset = $_GET['offset'];
    } else {
       $offset = 0;
    }

   $this->pagination->initialize($config);
 
    //now we run the same method as above but we supply an amount of results we want to be returned and the offset
    //as the second and third parameters
    //and pass off the data to our view
    $data['results'] = $this->m_db_results->search($_GET, $number_per_page, $offset);
    $this->load->view('results-page.php', $data);

}

Further info

Now as your form is submitted through get, it will not be able to use the set_value() method if you normally use that to repopulate your forms. You will have to code it in yourself, more than likely using isset() or empty() to determine if the field has a value.

P.S. If you found this post useful or not, can you leave some feedback in the comments as I want to improve the code as much as I can for other 'igniter fans. Thanks

Labels: , , ,

Saturday, November 19, 2011

Using PHP to backup an entire MySQL server

This post can be used as a free way to use PHP to backup your MySQL server. The code below comes from a script I developed so that we could back up the server with the execution of a single PHP page. Due to the way the script works, it passes off most of the heavy lifting to the system using exec() function so that it doesn't have to run through the webserver. It does this by using the mysqldump client program that is installed on the MySQL server - The PHP script is just used as a trigger. This is especially helpful if a non-technical person needs to run a database backup, yet doesn't know how to use console commands.

A few notes:
  • Each database is backed up in to it's own file and zipped up using gzip - make sure you have it installed. If not, you can always remove the zip command. 
  • The database script also adds the date to the file name
  • If you do not want to backup the entire database, just alter the database_list array with the names of the databases you want to backup, i.e.
$database_list = array('database_1', 'database_2');
Usage:

To use the script, all you have to do is modify the connection variables at the top and make sure your dump folder has the correct permissions.
  1. I List databases using a little bit of PHP and add them to an array
  2. Loop through the databases
The script:
set_time_limit(0);
$username = 'username';
$password = 'password';
$host = 'localhost';
/**
* Put your path here - Can be any valid path, but remember that you must have write permission on the folder
*/
$dump_path = './dumps/';
$dbc = mysqli_connect($host,$username,$password);
$result = mysqli_query($dbc,'SHOW DATABASES'); 
$database_list = array();
while($row = mysqli_fetch_array($result)) {
   $database_list[] = $row['Database'];
}
foreach ($database_list as $database) {
    $dump_name = $database.'_'.date('d-m-y');
    $dump_string = 'mysqldump --host '.$host.' --user='.$username.' --password='.$password.' '.$database.' > '.$dump_path.$dump_name.'.sql';
    exec($dump_string);
    $zip_cmd = 'gzip '.$dump_path.$dump_name.'.sql';
    exec ($zip_cmd );
}
mysqli_close($dbc);
Download
If you would like to download the script, you may get it here

Expansion
Another one of the benefits of using the exec() function is that it can easily adapted to perform scheduled, reliable backups using a CRON job. This is one of the reasons I used the date() function to append the backup name, so that you could have more than copy of the database in the same directory. One further modification to the script you could perform is to sub categorise your database backups so that they are easier to manage.

James

Labels: , ,

Everyday MySQL console commands and MySQL hints

PHPMyAdmin is a great tool, but due its web based nature it can sometimes fall a little short, especially when dealing with large amounts of data. Don't get me wrong, it's still very useful for browsing large data sets, but if you are looking to do imports, exports, adding indexes etc., then you really need to use the console or some other application.

To be honest, most of the commands here, you will find on many other websites, I just wanted to compile this list as a reference: All the following commands were ran using the console in BASH, but should work on Windows/Mac consoles.

Logging in with a supplied username and using password option:

mysql --user=your_username -p

Logging in with a supplied username and a supplied password:

mysql --user=your_username --password=your_password

Logging in to a different host with a supplied username and password:

mysql --host  host_address --user=your_username --password=your_password

List databases

SHOW DATABASES;

Select a table

USE table_name;

Add an index

When you have a million plus records, this can save so much time in your lookups. I had a table that 1.5 million records, using a single WHERE clause on a non-index field was taking around 1.9 seconds, adding an index reduced the time to 0.0005 seconds:

ALTER TABLE table_name add index (column_name);

Export a database to a file:

mysqldump --user=your_username --password=your_password database_name > name_and_path_of_sql_file_to_create.sql

Will use a modification of the above to show how you can use PHP to backup an entire database in a future post - you can read that here: backup up your MySQL server with PHP

Import a database on to a different host with a supplied username and password:

mysql --host host_address --user=your_username --password=your_password db_name < your_sql_file.sql

Hope this is of use to you.

James



Labels: ,