Solved

Why does this function now work?

Posted on 2014-10-30
10
140 Views
Last Modified: 2014-11-03
I upload a CSV file using this:

if(isset($_FILES['csv_file']) && is_uploaded_file($_FILES['csv_file']['tmp_name']))
{
    //upload directory 
    $upload_dir = "csv/"; 
    //create file name 
    $file_path = $upload_dir . $_FILES['csv_file']['name']; 
    //move uploaded file to upload dir 
    if (!move_uploaded_file($_FILES['csv_file']['tmp_name'], $file_path)) { 
        //error moving upload file 
        echo "Error moving file upload"; 
    } 
} 

Open in new window


Perfect! It works! Now I want to look at the column headings on the CSV file that I've just uploaded.

Here's the function I'm using to create an array where I can view that first row of data which is going to be my column headings:

function csv_to_array($filename, $delimiter)
{
    if(!file_exists($filename) || !is_readable($filename))
        return FALSE;

    $header = NULL;
    $data = array();
    if (($handle = fopen($filename, 'r')) !== FALSE)
    {
        while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
        {
            if(!$header)
                $header = $row;
            else
                $data[] = array_combine($header, $row);
        }
        fclose($handle);
    }
    return $data;
}

Open in new window


So I kick things off with my object which looks like this:

$csv_file="csv/atlanta.csv";
$comma=",";

$bruce=csv_to_array($csv_file, $comma);
echo $bruce->data;

I am groping, here, because while I could probably figure this out using a procedural approach, I'm bound and determined to use an OOP dynamic, so...

When I run the page, after the file has been successfully uploaded, I get no errors, but I get no data, and I don't even know what that data would look like. And while I've got $csv_file="csv/Atlanta.csv," fact is, I need to figure out how to pass the file's temp name into my function and I don't know how I would do that.

So, bottom line: I need to identify the column headings on the csv file that has just been uploaded and I want to do it right and I want to craft it as a function and not a procedural approach.

How?

And I do have...

ini_set('display_errors', 1);
error_reporting(E_ALL);

...at the top of my page, which I know is often the first question in scenarios like this.

Bring it!
0
Comment
Question by:brucegust
  • 4
  • 3
  • 3
10 Comments
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 40414507
Let me see if I can rephrase the question.  "I want to upload a CSV file, and assume that the first row of the CSV contains column names.  I want to display those column names."  Does that capture it?
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 40414517
Also, I'm not seeing the OOP part of this application.  But if my assumptions about the question are correct, I think I can help.
0
 
LVL 108

Expert Comment

by:Ray Paseur
ID: 40414633
This is not very well-thought out or well tested, but it seems to work well enough to provide a starting point.

Here is my test data set:
A,B,C
1,2,3
4,5,6

Open in new window

Here is the script located at http://iconoun.com/demo/temp_brucegust.php
<?php // demo/temp_brucegust.php
error_reporting(E_ALL);
echo '<pre>';

Class MyCSV
{
    public    $csv;
    protected $header;
    protected $data;

    public function __construct($dir = 'storage')
    {
        // LIST OF THE ERRORS THAT MAY BE REPORTED IN $_FILES[]["error"] (THERE IS NO #5)
        $errors = array
        ( 0 => "Success!"
        , 1 => "The uploaded file exceeds the upload_max_filesize directive in php.ini"
        , 2 => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form"
        , 3 => "The uploaded file was only partially uploaded"
        , 4 => "No file was uploaded"
        , 5 => "UNDEFINED ERROR"
        , 6 => "Missing a temporary folder"
        , 7 => "Cannot write file to disk"
        )
        ;

        // IF THERE HAS NOT BEEN AN UPLOAD
        if (empty($_FILES['csv']['name']))
        {
            return FALSE;
        }

        // AFTER AN UPLOAD, IF THERE ARE ERRORS
        $this->error_code = $_FILES["csv"]["error"];
        if ($this->error_code)
        {
            trigger_error($errors[$this->error_code], E_USER_ERROR);
        }

        $this->csv = getcwd() . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR . $_FILES['csv']['name'];

        // SAVE THE DATA AND RETURN THE SUCCESS SIGNAL
        if (move_uploaded_file($_FILES['csv']['tmp_name'], $this->csv))
        {
            return TRUE;
        }
        else
        {
            trigger_error("Unable to move_uploaded_file to $this->csv", E_USER_ERROR);
        }
    }

    public function getFile()
    {
        return $this->csv;
    }

    public function getHeader()
    {
        if ($this->header) return $this->header;

        $this->fpr = fopen($this->csv, 'rb');
        if (!$this->fpr) trigger_error("Unable to open $this->csv for 'rb'", E_USER_ERROR);
        $this->header = fgetcsv($this->fpr);
        if (!$this->header) trigger_error("Unable to read $this->csv", E_USER_ERROR);
        return $this->header;
    }

    public function getData()
    {
        if ($data = fgetcsv($this->fpr)) return $data;
        return FALSE;
    }

    public function showForm()
    {
        $form = <<<ENDFORM

<p>Upload one CSV file
<form enctype="multipart/form-data" method="post">
<!-- MAX_FILE_SIZE MUST PRECEDE THE FILE INPUT FIELD -->
<input type="hidden" name="MAX_FILE_SIZE" value="8000000" />
Find a CSV file to Upload: <input name="csv" type="file" />
<input type="submit" value="Upload" />
</form>
</p>
ENDFORM;

        echo $form;
        return FALSE;
    }
}

// USE CASE
$x = new MyCSV;

// IF THERE WAS NOTHING RETURNED WAIT FOR THE CLIENT TO SUBMIT THE FORM
if (empty($x->csv))
{
    $x->showForm();
    die();
}

// IF THE FORM HAS BEEN SUBMITTED, READ IT AND DISPLAY IT
echo PHP_EOL . 'File: ' . $x->getFile();

$top = $x->getHeader();
echo PHP_EOL . "Header Row: ";
print_r($top);

echo PHP_EOL . "Data Row(s): ";
while ($row = $x->getData())
{
    echo PHP_EOL;
    print_r($row);
}

Open in new window

0
 
LVL 51

Assisted Solution

by:Julian Hansen
Julian Hansen earned 100 total points
ID: 40415115
Lets break it down

You are calling like this

$bruce=csv_to_array($csv_file, $comma);
echo $bruce->data;

Open in new window


However lets look at your csv_to_array definition
...
while (($row = fgetcsv($handle, 1000, $delimiter)) !== FALSE)
        {
            if(!$header)
                $header = $row;
            else
                $data[] = array_combine($header, $row);
        }
        fclose($handle);
    }
    return $data;  // <==== WHAT's THIS

Open in new window


You are returning an array - not an Object

$bruce->data is meaningless in this context. Your return value from your function is an array. So after the call

$bruce is an Array()

So if you did this

$bruce=csv_to_array($csv_file, $comma);
echo "<pre>" . print_r($bruce, true), "</pre>";

Open in new window


What do you get?
0
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 40415123
Here is some code
Input (e057.csv)
Name,Surname,Email
John,Smith,john@mail.com
Fred,Jones,fred@jones.com

Open in new window

Then taking your code verbatim and dumping as suggested
$result = csv_to_array('e057.csv', ',');
echo "<pre>" . print_r($result, true) . "</pre>";

Open in new window

We get
Array
(
    [0] => Array
        (
            [Name] => John
            [Surname] => Smith
            [Email] => john@mail.com
        )

    [1] => Array
        (
            [Name] => Fred
            [Surname] => Jones
            [Email] => fred@jones.com
        )

)

Open in new window

0
Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

 

Author Comment

by:brucegust
ID: 40416214
Ray, I want to explain back to you what you did so I'm learning and not just copying and pasting...

Lines 7-9, the difference in the type of properties you're establishing is due to the fact that "$header" and "$data" need to stay applicable to the current class and not be allowed to seep into other elements of the page, yes?

Line 11 - you're using the construct function to define where it is that I'm going to be uploading my csv file to.

Line 29 - "return FALSE." This is a way of telling the class / function to stop working because it's here at this point that something went south. "False" is not a word that I should expect to see on my screen. At one point, I intentionally left the upload field blank, wanting to see what would appear on the screen. Nothing changed. No error message, nothing. But that's appropriate. If I want to see an error message, then I could do something like "print "there's nothing to upload."; but I need to position that before "return FALSE."

line 33: $this->error_code=$_FILES["csv"]["error"] - $_FILES["csv"["error"] is a PHP piece of syntax that's giving the user a view of what might've happened should the upload process go south. What you're doing here is establishing a digital placeholder to contain that error message should something go wrong (http://php.net/manual/en/features.file-upload.errors.php).

The logic of "$this->error_code" is "$this" is referring to the current process that's being engaged. The arrow indicator (-> [don't know if that's the correct terminology]) can be translated as the equivalent to saying that "among the things that are occurring within 'this' process is...." - and in this instance you're saying that there's a default collection of error codes that are potentially generated that we're going to represent by the variable "error_code."

You previously established an array of easy-to-understand error messages that correspond to whatever error code is generated by PHP. This combined with the "trigger_error" and "E_USER_ERROR" functionality results in a helpful form of assistance in diagnosing what may be the problem if something goes wrong.

Line 39: You're giving the successfully uploaded CSV file a digital presence using $this->csv combined with the "get current directory (getcwd) functionality. The $dir (directory where the uploaded CSV file was uploaded to) variable is something you established in the context of your construct on line 11. On line 42, you're actually checking to see if there's something occupying the digital presence you just established in the context of a successful upload.

Line 52: Here's where I had a question: It seems like the solo purpose of this function is to simply identify the CSV file that's in the directory it was uploaded to. Yet, was that not done on line 39? Or, is this just a way of representing the current CSV file as "$this->csv" for the duration of the next three functions? Would love some insight here.

Line 61: Line 59 you're looking for the presence of a header, but I'm at a loss as to how your code knows to be looking for a header in the context of the current csv file. I'm thinking the only way you could specifically target the CSV file is to say $this->csv based on what you did on line 54. In any event, line 61 opens up the CSV file using a mode that I couldn't find. What's "rb?" I've seen "r," but I'm not familiar with "rb."

Line 62 offers to trigger an error if we're unable to open the file. Line 63 establishes the content of the header by using fgetcsv. Line 64 offers an error message if we're not able to read the content and line 65 returns the header.

What's the difference between line 59 and line 65? Mind you, Ray, I'm not questioning whether it's right, I'm asking WHY it's right.

Finally, in line 68 you're returning the data.

Is that right and could you answer the questions I've got included.

Thank you, sir!
0
 

Author Comment

by:brucegust
ID: 40416218
Julian, rest assured you've got some points coming your way. I always appreciate your time and expertise. I'm going with Ray's approach just because I perceive it as a cleaner and more elegant solution, but thanks for being willing to clean up my mess!
0
 
LVL 108

Accepted Solution

by:
Ray Paseur earned 400 total points
ID: 40416313
For background, you might want to read the PHP OOP description.  There are many new terms of art that are not likely to be intuitive for programmers who have not gotten some experience in other OOP languages.  PHP's implementation of OOP is pretty good.
http://php.net/manual/en/language.oop5.php

These two articles from our colleague @gr8gonzo are pretty good, too.
http://www.experts-exchange.com/Programming/Languages/Scripting/PHP/A_2626-Beginning-Object-Oriented-Programming-in-PHP.html
http://www.experts-exchange.com/Programming/Languages/Scripting/PHP/A_2631-Advanced-Object-Oriented-Programming-in-PHP.html

Lines 7-9, the difference in the type of properties you're establishing is due to the fact that "$header" and "$data" need to stay applicable to the current class and not be allowed to seep into other elements of the page, yes?
The difference in type has to do with "visibility" which is a term of art in OOP.  The reason for declaring the properties at the top has to do with the output of var_dump().  It makes the properties appear in a predictable place and order.  Simplifies debugging.

Line 11 starts the class constructor.  The argument ($dir) to the constructor has a default value, which is the permanent directory that we want to use for storage of the uploaded file.

Line 29 could probably return anything or nothing (like I said, I didn't give this much thought).  It's just there to stop the logic flow if there has not been any file uploaded.  The script can be started with a POST or GET request, but with uploaded files, PHP puts the information into $_FILES.  Even if there isn't any data, that superglobal array will not be empty, but the defined positions of the array will be empty.  If a file was uploaded, the script will continue.
The logic of "$this->error_code" is "$this" is referring to the current process that's being engaged.
In PHP OOP, $this is a reserved word that refers to the object instance.  Within object methods, you use $this-> to tell PHP to look for object properties and methods.  Without $this, PHP expects to find methods (functions) in the global scope and to find local variables, not object properties.

Line 39 establishes a file name for the CSV and assigns it to a property of the object.  PHP file names, URLs, and path names are sometimes confusing for me, so I try to assign these things to variables or properties. Then I can visualize them easily.

The function definition on line 52 is probably an unnecessary fifth wheel.  Since $this->csv is a public property you can refer to it directly and you do not strictly need a getter / setter method.  Getters and setters are useful when you have protected or private properties.  I sometimes fall back on habits of making my methods public and my properties protected - that seems to be a good "default" position unless you have a reason to use a different definition.

The purpose of the getHeader() method is to return the header row from the CSV.  It's my assumption that this will always be the first line of the data set.  Once we have opened the CSV file and successfully read the header, we would not want to open it and read it again.  So line 59 says, "If I've already got the header, return the header."  You can call this function over and over and it will always return the header.  And the first time you call it, the CSV file will be opened, the header will be read and stored in an object property, and then it will be returned.

Open with 'rb' is documented on the man page.  I use this because it's not yet clear to me what character set might be in use here and I don't know if UTF-8 characters work correctly with files that are not considered "binary."  I've never set up a test or researched it.
http://php.net/manual/en/function.fopen.php

The function on line 68 returns the data, one row at a time and returns FALSE when the data is exhausted.  This is one of those things that is not very well-thought out.  What if a read error occurs?  The script does not test for that.  It might be smarter to have some error checking and correction, and I certainly would do that in a deployed application.
0
 
LVL 51

Expert Comment

by:Julian Hansen
ID: 40417952
@brucegust - just a comment on requirements. Based on your initial code posted it seems you want your data returned with indices for the individual data fields being the corresponding header name for that field - is that correct?
0
 

Author Comment

by:brucegust
ID: 40420354
Julian - yes!

Thanks, guys! I got what I needed as well as a great education!
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Suggested Solutions

Title # Comments Views Activity
echo button 13 48
resizeing PHP image 2 24
html input clean up 3 32
Help cleaning out CSS 2 32
I imagine that there are some, like me, who require a way of getting currency exchange rates for implementation in web project from time to time, so I thought I would share a solution that I have developed for this purpose. It turns out that Yaho…
Part of the Global Positioning System A geocode (https://developers.google.com/maps/documentation/geocoding/) is the major subset of a GPS coordinate (http://en.wikipedia.org/wiki/Global_Positioning_System), the other parts being the altitude and t…
Learn how to match and substitute tagged data using PHP regular expressions. Demonstrated on Windows 7, but also applies to other operating systems. Demonstrated technique applies to PHP (all versions) and Firefox, but very similar techniques will w…
Explain concepts important to validation of email addresses with regular expressions. Applies to most languages/tools that uses regular expressions. Consider email address RFCs: Look at HTML5 form input element (with type=email) regex pattern: T…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now