Monday, February 17, 2014

Compare two Databases with PHP

Very recently I faced a big problem where I had a older DB working with new version of files. To be precise, I was working with Magento 1.8 files with Magento 1.6 database filled with various products and customer information. And frankly speaking, it landed me into deep water as various stuffs like shopping cart, checkout process, reIndexing services, catalog price updation etc started to give errors. It all happened just because new magento files are expecting new fields in the DB. So, I wrote a piece of code to match the original DB (v1.8) with my current working DB (v1.6). And very soon I was able to track down the table differences in two databases and I just manually added those extra columns which the current DB (v1.6) was lacking. Soon, all the errors on the site mostly vanished. 

Manually column addition is a bad idea because it does not create the Indexes which already exist in correct/Original DB. For that, I'll be writing another script. Till that time, check out the php code for this simple Database comparison.

Download table_comparison.php

This solution works the following way :: 

1. We connect to 2 Databases within a script. If the server allows multiple DB connection on a single script, then there won't be any problem. Otherwise we need to use a fourth parameter (true) with 2nd mysql_connect() onwards.
   
 mysql_connect( db_host, db_username, db_password, true );

2. We use a "SHOW TABLE"  query to fetch all the tables from a Database and store them in an Array. This way we create 2 arrays, first one holding all the table names from DB1 and the second one holding all table names from DB2. So, if a DB has 300 tables and another has 400 tables, that differences can be easily found by counting elements in each array.

3. Now, we would iterate through the first array (holding table names from correct database DB1); taking each table name we would run a "DESCRIBE table_name" SQL and fetch all the column names and store them in an array. Now we would do this for each table in each database and then we would match column names.

4. Reporting would be a very essential thing. We need to use various flags/counters/Strings etc to keep track of changes and use them in generating the report.

Check out some screenshots showing the output ::

























Friday, February 14, 2014

File download script using PHP

Opening a file in another window or tab within a browser, we can use an anchor to that file like this : 

<a href="test.pdf" target="_blank">Download the PDF here</a>

it will open the file in a new tab. To force the browser to download it on the viewer's PC, we need to write some server side code. Here I am showing some sample codes which would show "Save As" message box on browsers.

Setting the headers is the most important thing here. Here, code for downloading PDF file is shown. We are reading "sample.pdf" and forcing the browser to download it as "test.pdf" on viewer's machine. We can have variety on our solutions.

Solution 1 ::

<?php

/// Set the header
header("Content-type:application/pdf"); 
header("Content-Transfer-Encoding: binary");
header("Content-Disposition:attachment; filename=test.pdf");

/// Deliver the content
readfile("sample.pdf");
?>

Solution 2 ::

<?php

// GET the Source file's contents
$str = file_get_contents("sample.pdf") or die("Could not open file");

// Get the Target
$fp = fopen("php://output","w+") or die("Could not open output stream");

// Set the Header
header("Content-type:application/pdf"); 
header("Content-Transfer-Encoding: binary");
header("Content-Disposition:attachment; filename=test.pdf");

// Send Data
fputs($fp, $str);

// Close file
fclose( $fp );
?>

Solution 3 ::

<?php

// GET the Source file
$fp = fopen("sample.pdf","r+") or die("Could not open file");

// Set the Header
header("Content-type:application/pdf"); 
header("Content-Transfer-Encoding: binary");
header("Content-Disposition:attachment; filename=test.pdf");

// Read and Send Data
while( !feof($fp) )
 echo fread( $fp, 1024 );

// Close file
fclose( $fp );
?>

All the above solutions would work smoothly but the first one needs output buffering to be enabled. The 3 solutions above have different file reading and rendering methods only, the header information remain same. We are reading a file called 'sample.pdf' and forcing it to be downloaded with a default name 'test.pdf'. 

The "Content-Disposition:attachment" is very important. This would prompt for downloading the file instead of directly showing it in Browser.  

We can force the browser to download other file types like Excel file but for that we need to modify the MIME 'Content-type' in response header. For an Excel file to be downloaded we may use the following code :

<?php

// GET the Source file
$filename  = "tester.xls";
$fp = fopen($filename,"r+") or die("Could not open file");

// Set the Header
header("Content-Type:application/vnd.ms-excel; charset=utf-8"); 
header("Content-Transfer-Encoding: binary");
header("Content-Disposition:attachment; filename=genuine.xls");

// Read and Send Data
echo fread( $fp, filesize($filename) );

// Close file
fclose( $fp );
?>

The above code will download all kind of files if we correctly change the "Content-Type" settings in header(). Some content types are shown below :

If the MIME type starts with "vnd" ( in case of .ppt, .word or .xls files ), it means to be vendor specific. If the type starts with "x-", it means that it is non-standard, ( not registered with the "Internet Assigned Numbers Authority" ).

Images :: 
.png : header("Content-Type:image/png") 
.jpg : header("Content-Type:image/jpg") 
.gif : header("Content-Type:image/gif") 
.bmp : header("Content-Type:image/bmp") 

Texts ::
.txt : header("Content-Type:text/plain") 
.html : header("Content-Type:text/plain") 
.php : header("Content-Type:text/plain") 
.js : header("Content-Type:text/plain") or header("Content-Type:text/javascript") (obsolete) or header("Content-Type:application/javascript")
.xml : header("Content-Type:text/xml")
.csv : header("Content-Type:text/csv")
.css : header("Content-Type:text/css")

Video ::
.mpg : header("Content-Type:video/mpeg")
.mp4 : header("Content-Type:video/mp4")
.3gp : header("Content-Type:video/3gpp")

Audio ::
.aac : header("Content-Type:audio/x-aac")
.mp3 : header("Content-Type:audio/mpeg")

Zip ::
.7z : header("Content-Type:application/x-7z-compressed")
.bz : header("Content-Type:application/x-bzip")
.zip : header("Content-Type:application/zip")

Other ::
.pdf : header("Content-Type:application/pdf")
.xls : header("Content-Type:application/vnd.ms-excel; charset=utf-8")
.ppt : header("Content-Type:vnd.ms-powerpoint")
.doc : header("Content-Type:application/vnd.openxmlformats-officedocument.wordprocessingml.document")
.swf : header("Content-Type:application/x-shockwave-flash")
.bin/.exe : header("Content-Type:application/octet-stream")

Thursday, February 13, 2014

Text File Reading in PHP

A text file can be read in many ways, however reading PDFs or Excel files will be different because there format is complex. We usually do not need to read complex file types, so, we'll mostly stick to reading text files. 

Usually text files can be read -- i) all at once ii) character by character iii) line by line iv) in bytes. Check the code below.

First we would read the total content at once from a file. 

<?php
// Method 1
// This prints the text in original format
// file_get_contents() read the whole content 
// into a string variable $str
echo $str = nl2br(file_get_contents("test.txt"));

// Method 2 :: readfile() writes to output buffer.

// If output buffering is turned off, content won't 
// be displayed
readfile("test.txt");

// If we clean the buffer, the content is 

// not displayed on browser
readfile("test.txt");
ob_end_clean();

// Let's get all the buffered content into a string

// ob_get_clean() returns the buffer content as a string
// This again prints in content in original format
readfile("test.txt");
$str = ob_get_clean();
echo nl2br( $str );

/// Method 3 :: Grab the total content using fread()

// Open the file
$file_name = "test.txt";
$fp = fopen($file_name,"r+") or die("File can't be opened");
// GEt the total contents
echo nl2br(fread($fp, filesize($file_name) ));
fclose($fp);
?>

Now, let's try to read the character by character. Check the code below.

<?php
// Open the file
$file_name = "test.txt";
$fp = fopen($file_name,"r+") or die("File can't be opened");

// Loop every character

while( ($char = fgetc($fp))!== false )

  if( ord($char) == 13 )  
    echo "<br>";
  else
    echo $char;
}

// Close the file handle/pointer
fclose($fp);
?>

The ord() function returns the ASCII code of passed character. So, for every newline character, we are printing a <br> element to keep the original formatting.  

Next, we would be reading a file line by line using fgets() function. Check the code below.

<?php
// Open the file
$file_name = "test.txt";
$fp = fopen($file_name,"r+") or die("File can't be opened");

// Loop thru lines

while( ($str = fgets($fp))!= NULL )
 echo $str . "<br>";

// Close the file handle/pointer

fclose($fp);
?>

The above code reads all the lines one by one from beginning. To read a file line by line from the end, check the article How to read a file backward in PHP?.

Next, we would read a file byte by byte which is again done with fread().

<?php
$file_name = "test.txt";
$fp = fopen($file_name,"r+") or die("File can't be opened");

// Loop thru bytes
while ( ( $data = fread($fp, 10) )!= false )
 echo nl2br($data);

// Close file 
fclose($fp);
?>

The above code reads 10 bytes at a time including the newline character. Let's twist the output of the above program and discover some good stuffs.

Let's change the while loop as shown below :

<?php
// Loop thru bytes
while ( ( $data = fread($fp, 10) )!= false )
{
  $data = str_replace("\n","@",$data);
  $data = str_replace("\r","#",$data);
  echo "[$data]<br>";
}
?>

Suppose the file test.txt has the following content ::

Line 1 :: THIS IS JUST a TEST
Line 2 :: THIS IS JUST a TEST
Line 3 :: THIS IS JUST a TEST

and if the above code is run on Xampp or Wamp on Windows, the above file content would be perceived as following ::

Line 1 :: THIS IS JUST a TEST\r\n
Line 2 :: THIS IS JUST a TEST\r\n
Line 3 :: THIS IS JUST a TEST\r\n

Newline character on Windows is comprised of '\r' and '\n'. The output of the above code will prove that. The output is shown below :

[Line 1 :: ]
[THIS IS JU]
[ST a TEST#]
[@Line 2 ::]
[ THIS IS J]
[UST a TEST]
[#@Line 3 :]
[: THIS IS ]
[JUST a TES]
[T#@]


The above output proves i) fread() reads through newline character and ii) Newline character on Windows is '\r\n'.

The $bytes_pos holds a value 0 (zero) initially and file pointer is standing at 0th location. Then fread() function reads 10 bytes, hence the file pointer points to 10th location now which is returned by ftell(). So, this way the $bytes_pos array keeps on storing positions like 0, 10, 20, 30 40. When it comes to the reading last 10 bytes, ftell() still returns the position of current bytes (which can be EOF or a newline) within file which is stored at the last position in the array $bytes_pos. This would cause a small problem in our next example. 

Let's try a useless but a different thing with fread(). Let's read all these data packs (10 bytes each) backward. Check the code below.

<?php
$read_length = 10;

$file_name = "tester.txt";
$fp = fopen($file_name,"r+") or die("File can't be opened");

// Store the First Position 0, file reading starts from
// Position 0 within file
$bytes_pos = array(0);

// Loop thru bytes and store bytes position in an array
while ( ( $data = fread($fp, $read_length) )!= false )
 $bytes_pos[] = ftell( $fp );

// Reverse the array for reading it Backward 
$bytes_pos = array_reverse( $bytes_pos );

// Finally, reading 10 bytes from stored positions
foreach($bytes_pos as $pos)
{
  // Move the file pointer
  fseek( $fp, $pos );
  
  // Read
  $data = fread($fp, $read_length) ;
  $data = str_replace("\n","@",$data);
  $data = str_replace("\r","#",$data);
  echo "[$data]<br>";
}

// Close file 
fclose($fp);
?>

Here again, we started with reading 10 bytes and storing the position in an array $bytes_pos. The ftell() function returns the current position of file pointer. When the array is filled with various positions 10 bytes apart, we just reversed it. Then finally we iterated through the array using the foreach loop construct. Next, we used the fseek() function to move the file pointer to desired location and read 10 bytes from thereon using fread() function. Check the output below. It is almost the opposite to the previous output.

 [] [T#@]
[JUST a TES]
[: THIS IS ]
[#@Line 3 :]
[UST a TEST]
[ THIS IS J]
[@Line 2 ::]
[ST a TEST#]
[THIS IS JU]
[Line 1 :: ]

The first line "[]" is coming because of the fact that file position of EOF or newline is stored at the last location within the array. This was discussed awhile ago. However this problem can be overcome by removing the last item from that array.

Friday, November 15, 2013

Handling XML in PHP - II

If we want to generate XML in an organized way, we better use DOM extensions that comes with PHP. This creates DOMDocument object and the corresponding methods follow a pattern of adding, removing or manipulating elements and text nodes or adding attributes to DOM elements. The methods are quite similar to JavaScript DOM handling functions. 

Our first example loads an XML document using PHP DOM extension, then renders it on browser.

<?php
// Create new object
$xml_doc = new DOMDocument();
// Use load() method to load a document
$xml_doc->load('test.xml');
//Render it on screen
echo $xml_doc->saveXML();
?>

The content of test.xml is shown as output but only the text nodes. Tags are not shown. We need to view the source to get the actual XML structure. To render the whole XML structure on browser (without viewing the source), we need to include the header call : header('Content-type:text/xml'); before calling the saveXML() method.

Our next example, creates an XML document on the fly. Let's check it out.

<?php
header('Content-type:text/xml');

// Create new DOM object
$xml_doc = new DOMDocument();

// Add the root element 'students'
$students = $xml_doc->createElement('students');

// Append the new node to the doc
$xml_doc->appendChild($students);

// Now create First student Node
$student1 = $xml_doc->createElement('student');

// Create some attributes for student node
$attr1 = $xml_doc->createAttribute('id');
$attr1->value = "1";
$attr2 = $xml_doc->createAttribute('section');
$attr2->value = "A";
$attr3 = $xml_doc->createAttribute('username');
$attr3->value = "john_smith";

// Append the new attributes to the created element 'student'
$student1->appendChild($attr1);
$student1->appendChild($attr2);
$student1->appendChild($attr3);

// Now Insert a comment Node
$comm1 = $xml_doc->createComment('Teacher : Niel Hertz');
$student1->appendChild($comm1);

// Now Insert other child elements under "student" node
$fname = $xml_doc->createElement('fname');
$textNode = $xml_doc->createTextNode('John');
$fname->appendChild($textNode);

$lname = $xml_doc->createElement('lname');
$textNode = $xml_doc->createTextNode('Smith');
$lname->appendChild($textNode);

$roll_no = $xml_doc->createElement('roll_no');
$textNode = $xml_doc->createTextNode('109');
$roll_no->appendChild($textNode);

// Append all child nodes to 'student' node
$student1->appendChild($fname);
$student1->appendChild($lname);
$student1->appendChild($roll_no);

// Finally Append the 'student' node to 'students'
$students->appendChild($student1);

// Render it on screen
echo $xml_doc->saveXML();
?>

The above code generates and renders the following XML on the browser : 

<students>
 <student id="1" section="A" username="john_smith">
   <!--Teacher : Niel Hertz-->
   <fname>John109</fname>
   <lname>Smith</lname>
   <roll_no>109</roll_no>
 </student>
</students>

In the above code, we are using a DOMDocument object first, then using functions like createElement(), createAttribute(), createComment(), createTextNode() etc to create a DOM structure under it. All the created elements, attributes, comments, text nodes do not get into picture until we give calls to appendChild(). This function adds the element [or attribute etc] to its parent.

Next, we would parse an XML structure using the DOMDocument objects. Let's check out a program which loads an XML file and prints every elements and its contents on the screen.

<?php

// Create new DOM object
$xml_doc = new DOMDocument();

// Load external XML file

//$xml_doc->load('test.xml');  /// This is also OK
$xml_doc->loadXML(file_get_contents('test.xml'));

/// Get all the student element
$students = $xml_doc->getElementsByTagName('student');

//// Iterate thru each student node
foreach( $students as $stu )
{
  // Get Attributes of an element
  $id = $stu->getAttribute('id');
  $section = $stu->getAttribute('section');
  $username = $stu->getAttribute('username');
  
  // GET Child Elements/nodes and their values
  // A NodeList is returned by getElementsByTagName
  $nodes = $stu->getElementsByTagName('fname');
  $fname= $nodes->item(0)->nodeValue;

  $nodes = $stu->getElementsByTagName('lname');
  $lname= $nodes->item(0)->nodeValue;
  
  $nodes = $stu->getElementsByTagName('roll_no');
  $roll_no= $nodes->item(0)->nodeValue;
  
  $nodes = $stu->getElementsByTagName('class');
  $class= $nodes->item(0)->nodeValue;

  $nodes = $stu->getElementsByTagName('nickname');
  $nickname= $nodes->item(0)->nodeValue;


  /// Print on screen
  echo "<b>Student Details ::: </b><br>";
  echo "Name : $fname $lname [NickName : $nickname]<br>";
  echo "ID : $id, Section : $section, Class : $class<br>";
  echo "Username : $username<br><br>";
  
}

?>

The above program loads content of an XML file, then parses it and prints the student details. The functions like getElementsByTagName(), getElementById() locates elements within the XML document. The function getElementsByTagName() returns DOMNodeList class object holding all the elements found. Then we can simply use the item() method to access each element. Attributes of elements can be retrieved using getAttribute() method.

Check out the first part - Handling XML in PHP - I

Wednesday, November 13, 2013

How to check whether a remote file exists in PHP

Sometimes, we may need to execute remote php files and work on the resultant output. For example, very recently I have worked on a project in which file-checking PHP scripts on various remote servers are executed at regular intervals by a cron set on a single server at my disposal [I can control this server]. Those file-checking PHP scripts check if any file has been modified recently on its local server. This facility helps in tracking if any file has been modified by recent hacking attack.

In this situation, I needed to use CURL methods to execute the remote scripts, then get and parse the output. But I got error messages when the remote file-checking scripts were removed/deleted/renamed by hacking or any other ways. So, before executing remote scripts, we needed to check if the script existed at all.

There are many ways we can determine whether a remote script exists or not.

1. The first method is using functions like fopen()/file_get_contents()/file_exists() method. Check the code below..

<?php

/// We are trying to open the remote file in READ-ONLY mode
/// Using fopen() function 
$fp = fopen("http://example.org/file_check.php","r") or die("Could not open remote file");

/// Using file_get_contents() function
$str = file_get_contents("http://example.org/file_check.php") or die("Could not open remote file");

/// Using file_exists() function
echo  file_exists("http://example.org/file_check.php") or die("ERROR");

?>

2. The second method is using the function get_headers(). This function returns all the header sent by the server. Check the code below.

<?php

// GET the URL status
$status = get_headers( 'http://example.org/file_check.php' , 1) ;

// Check the status CODE
if( $status[0] == 'HTTP/1.0 404 Not Found' )
{
  // ... 
}

?>

The get_headers() function returns the headers in an array format and that array's zeroth position always hold the response code. 

All the above methods would require the allow_url_fopen settings in php.ini file to be set to 'On'. Otherwise they would fail.

3. The third method is to use CURL library. Check the code below.

<?php

// Init CURL
$ch = curl_init();

// set URL and other appropriate options
$curl_url = "http://example.org/file_check.php";
curl_setopt($ch, CURLOPT_URL, $curl_url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE );

// grab URL and pass it to the browser
$str = curl_exec($ch);

/// Check if the file_check.php exists
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);

/// HTTP Status 200 means file exists
if( $http_status == "200" )
{
   //// The result/Output is already stored in $str
   //// We will parse the value of $str
}

?>

The above program is quite self-explanatory.  The curl_getinfo() function returns the response code which is 200 in case the page exists. The CURL method does not care about the value set to allow_url_fopen directive, hence very convenient and powerful tool to use.

Wednesday, August 21, 2013

Basic Authentication with .htpasswd in Xampp (Windows)

The basic http authentication created with .htaccess and .htpasswd files works pretty well on online real server. However implementing them locally on our Xampp server (on Windows) may be a little problematic. Here I am going to discuss the steps required to achieve this.

First of all, we need 2 files need to be created, .htaccess and .htpasswd. All the user passwords are stored inside the .htpasswd file. Windows does not allow to create such nameless files, however we can create them with Command Prompt. Check the picture below.



We have used the DOS command "copy con <filename>". Pressing CTRL + Z would end the process and creates a new file with the name provided. This way, we created the required .htaccess file. Our working folder is "htpassword". We created the .htaccess inside that only.

Next we would create the .htpasswd file with the help of command 'htpasswd', the corresponding windows executable file 'htpasswd.exe' resides in xampp\apache\bin folder. So, check the next screenshot to understand how we create the password file required for authentication.



We gave the command "htpasswd -c -m -b .htpasswd admin admin_pass". "admin" is going to be the username and "admin_pass" would be our password.

-c : create a new file
-m : MD5 encryption is enforced
-b : Use the password given at the command prompt

So, our .htpasswd file is successfully created. Our working folder "htpassword" also has an index.php file which contains a one-liner welcome message.

Now, open the .htaccess file, we need to write the following lines inside it.

AuthName "My Protected Area"
AuthType Basic
AuthUserFile c:\xampp\htdocs\htpassword\.htpasswd
require valid-user


Check the third line where the path of the password file is clearly mentioned. Now when we try to go to http://localhost/htpassword, a prompt appears asking for user id and password ( this is shown in screenshot below ). If valid id and password given, then we are redirected to index.php or other file set to be the default file.
 


Friday, July 26, 2013

Handling XML in PHP - I

XML documents are much stricter than HTML. XML is case sensitive whereas HTML tags can be in any case. Multiple spaces are ignored in HTML whereas XML preserves whitespaces. In HTML, tags may not be closed, fo example <br> tag does not need to have a closing </br> tag. But in XML, tags must be properly closed. Attribute quoting is essential in XML; which means the value for an element attribute must be wrapped in a quote. But this is not mandatory in HTML. Lastly, In XML, all data inside tag must be escaped. Ampersand (&) must be escaped to &amp; We need htmlspecialchars() or htmlentities() functions to do this for us.

There are various XML handling extension in PHP for reading, manipulating XML document. Here we would check out SimpleXML extension which is quite capable of handling general XML document.

XML document must start with an <xml> tag with a version number as shown below.

<?xml version="1.0"?>

To generate an XML document in PHP requires correct Content-Type header for the document. A simple example to generate XML document is given below.

<?php
header('Content-type:text/xml');
echo "<?xml version='1.0'?>";
echo <<<XML
 <student>
  <fname>John</fname>
  <lname>Smith</lname>
  <roll_no>109</roll_no>
  <class>VII</class>
 </student>
XML;
?>


The XML declaration statement <?xml version='1.0'?> may cause a problem. It purely matches with any PHP code block with short_open_tag directive set to on. In that case PHP processing might start thinking that it is a PHP statement. To prevent this, this XML declaration is printed with echo or print.

For parsing XML document, we would use simplexml_load_file() function. Check the example below. Here we assume that an xml file test.xml has the following content.

<?xml version='1.0'?>
<students>
 <student id='1' section='A'>
  <!-- Teacher : Niel Hertz -->
  <fname>John</fname>
  <lname>Smith</lname>
  <roll_no>109</roll_no>
  <class>VII</class>
 </student>
 <student id='2' section='B'>
  <!-- Teacher : Jim Cartel -->
  <fname>Jeff</fname>
  <lname>Smith</lname>
  <roll_no>110</roll_no>
  <class>VIII</class>
 </student>
</students>


Notice that XML elements can have attributes as HTML elements have. But in XML, the attribute values need to be quoted. The XML document can have comments (Similar to HTML comments).

Now our PHP code loads this file and parses it.

<?php
// Load the XML file in an Object
$all_students = simplexml_load_file('test.xml');

// This prints an array-like structure
// which is very easier to browse
// print_r( $all_students );



// Browse each node
foreach( $all_students->student as $st )
{
  echo "<br>ID :: {$st['id']}, Student Name : {$st->fname} {$st->lname}, ";
  echo "Roll : {$st->roll_no}, Class : {$st->class}";
}
?>


Output ::
ID :: 1, Student Name : John Smith, Roll : 109, Class : VII
ID :: 2, Student Name : Jeff Smith, Roll : 110, Class : VIII


The simplexml_load_file() function reads the XML document, puts the root element 'students' in an SimpleXMLElement object variable $all_students. The variable '$all_students' now have all the <student> nodes in array format where each student detail is again stored in SimpleXMLElement object. So, in the foreach loop, the $st variable points to each student node (now converted to an object) and all the child nodes become properties of that object. Hence $st->fname prints the <fname> node content appearing under the <student> node. So, basically SimpleXML converts all XML elements into object properties which
then becomes easier to handle.

Also notice, how the 'id' attribute of each <student> element is accessed through the array construct $st['id']. All node attributes are stored in an array. However comments are not captured by SimpleXML extension.

SimpleXML extension comes with another function called simplexml_load_string() which loads the XML from a string. Check the example below.

<?php
$str = <<<XML
<?xml version='1.0'?>
<students>
 <student id='1' section='A'>
  <!-- Teacher : Niel Hertz -->
  <fname>John</fname>
  <lname>Smith</lname>
  <roll_no>109</roll_no>
  <class>VII</class>
 </student>
 <student id='2' section='B'>
  <!-- Teacher : Jim Cartel -->
  <fname>Jeff</fname>
  <lname>Smith</lname>
  <roll_no>110</roll_no>
  <class>VIII</class>
 </student>
</students>
XML;

// Now load the XML and turn it into an object
$all_students = simplexml_load_string($str);
?>


The same effect can be achieved by using SimpleXMLIterator class object. This is shown below ..

<?php
// $str variable is defined above
// Load the XML and turn it into an Iterator object

$all_students = new SimpleXmlIterator($str);

// Rewind is necessary to move the pointer
// to the first element

$all_students->rewind();

// Now start Looping; The key() function
// returns key in each iteration

while( $all_students->key() )
{
  // Get the current item
  $st = $all_students->current();
 
  // Print
  echo "<br>ID :: {$st['id']}, Student Name : {$st->fname} {$st->lname}, Roll : {$st->roll_no}, Class : {$st->class}";
 
  // Move the pointer to next item
  $all_students->next();
}
?>


The code above is quite self-explanatory. The functions rewind(), next() are used to move the iterator pointer to at the beginning and next item respectively. The  current() function points to current item.

Below, we are using objects of another class called SimpleXMLElement to convert an XML string to an object.

<?php
// Convert to SimpleXMLElement object
$all_students = new SimpleXMLElement($str);

// Show count of total immediate children under root
echo $all_students->count() . "<br>";

// Browse thru children for Printing
foreach($all_students->children() as $st)
{
 echo "<br>ID:{$st['id']}, ". $st->getName()." Name:{$st->fname} {$st->lname}, ";
 echo "Roll:{$st->roll_no}, Class:{$st->class}, Section:{$st['section']}";
}
?>


The count() method of SimpleXMLElement object counts the children of an element. At the beginning, the variable $all_students holds all the XML document including the root element. Hence calling count() function just reports "2" (i.e 2 <student> tags under the root element <students>). The children() method of the SimpleXMLElement class object finds children of given node. In our cases, 2 <student> nodes are listed as children of the root element <students>. So, a foreach() loop prints the details of each child node or <student> tag.

Let's add attributes and child nodes to the above XML structure.

<?php
// Now load the XML and turn it into an object
$all_students = new SimpleXMLElement($str);

// Get All the children
$child = $all_students->children();

// Browse thru children for adding
// Attributes and Child elements

for($i=0; $i<count($child); $i++ )
{
  // Get each child
  $st = $child[$i];
 
  // Create a username text
  $username = strtolower($st->fname . "_" . $st->lname);
  $username = str_replace(" ", "", $username );
 
  // Add a new Attibute called 'username' to each <student> node
  $st->addAttribute('username', $username );
 
  // Add a child Node called nickname inside each <student> node
  $st->addChild('nickname', $username );
}
// Now print the XML on browser
// SET the header

header('Content-Type:text/xml');
// Print XML
echo $all_students->asXML();
?>


The above code is quite self-explanatory. We are just looping through each child element occurs under the root element 'students'. We can add any attribute to any tag or node using addAttribute() method. This method takes attribute name and value as first and second parameters respectively. The addChild() method adds child under any node and takes new node's name and content as first & second parameters respectively. Finally, the asXML() method prints the new XML, but for the browser to print the XML in correct format, we must setup the Content-Type in the header() call. The output is shown below.

<students>
 <student id="1" section="A" username="john_smith">
  <!-- Teacher : Niel Hertz -->
  <fname>John</fname>
  <lname>Smith</lname>
  <roll_no>109</roll_no>
  <class>VII</class>
  <nickname>john_smith</nickname>
 </student>
 <student id="2" section="B" username="jeff_smith">
  <!-- Teacher : Jim Cartel -->
  <fname>Jeff</fname>
  <lname>Smith</lname>
  <roll_no>110</roll_no>
  <class>VIII</class>
  <nickname>jeff_smith</nickname>
 </student>
</students>


Notice the new attributes and child elements '<nickname>' have been added (marked in orange) in the XML structure.

XPath is used to navigate through elements and attributes in an XML document. XPath support is available in SimpleXML. Which means we can run XPath query on any XML data. The xpath() method of SimpleXML extension searches for any SimpleXML node matching the path provided as XPath. Check one example below.

<?php
// Load the XML and turn it into an object
$all_students = new SimpleXMLElement($str);

// Get all first Names only
$first_names = $all_students->xpath('/students/student/fname');
foreach ($first_names as $fname)
{
  echo " $fname";
}
?>


The above code displays content of all <fname> tags appearing in XPath 'students/student'. The method xpath() takes the path of the node as argument and returns all the nodes that appear in that path specified. If error, then FALSE is returned by xpath() method, otherwise an array of SimpleXML nodes are returned. If no matching node is found, then an empty array is returned.


Check out the 2nd part of this article - Handling XML in PHP - II

To see Complex XML parsing using SimpleXMLElement, check article Parsing Complex XML with SimpleXML in PHP