Jump to content

scope of variables inside a user-defined function


flounder

Recommended Posts

Hello all,

 

I have a 14 column csv that I load into an array, retrieving only the required fields, load the array into columns and perform a sort:

foreach ($array as $key => $row) {
$firstname[$key] = $row["firstn"];
    	$lastname[$key] = $row["lastn"];
    	$address1[$key] = $row["addr1"];
    	$address2[$key] = $row["addr2"];
    	$address3[$key] = $row["addr3"];
$city[$key] = $row["cit"];
$stateprov[$key] = $row["state"];
$country[$key] = $row["cntry"];
$membernumber[$key] = $row["num"];}
array_multisort($membernumber,$lastname,$firstname,$address1,$address2,$address3,$city,$stateprov,$country,$array);

When I pass say the first three letters of a last name ($find = "smi"), all records matching the search criteria are returned:

foreach ($array as $key => $row) {
if (strncasecmp($find, $lastname[$key], strlen($find)) == 0) {
	echo "<tr><td>" . $firstname[$key] . "</td>";
	echo "<td>" . $lastname[$key] . "</td>";
	echo "<td>" . $address1[$key] . "</td>";
	echo "<td>" . $address2[$key] . "</td>";
	echo "<td>" . $address3[$key] . "</td>";
	echo "<td>" . $city[$key] . "</td>";
	echo "<td>" . $stateprov[$key] . "</td>";
	echo "<td>" . $country[$key] . "</td>";
	echo "<td>" . $membernumber[$key] . "</td></tr>" . chr(13);
	}}}

So far so good. However, I'd like to put the search part into a function so I can call it a number of times without having to reload the entire csv:

function FindMember() {
foreach ($array as $key => $row) {
	if (strncasecmp($find, $membernumber[$key], strlen($find)) == 0) {
		echo "<tr><td>" . $firstname[$key] . "</td>";
		echo "<td>" . $lastname[$key] . "</td>";
		echo "<td>" . $address1[$key] . "</td>";
		echo "<td>" . $address2[$key] . "</td>";
		echo "<td>" . $address3[$key] . "</td>";
		echo "<td>" . $city[$key] . "</td>";
		echo "<td>" . $stateprov[$key] . "</td>";
		echo "<td>" . $country[$key] . "</td>";
		echo "<td align='right'>" . $membernumber[$key] . "</td></tr>" . chr(13);
		}}}}

The search criteria would come from a second txt file with every line containing 2 numbers, separated by a comma:

foreach ($lines as $member => $numbers) {
$exploded = explode(",", $numbers);
$find = $exploded[1];
FindMember();

here $exploded[1] corresponds to $membernumber[$key], so this is where I would call the function, but this is where I run into trouble, nothing gets returned.

 

Does this have something to do with the scope of variables inside a user-defined function?

 

I'd appreciate it if someone could point me in the right direction.

 

TIA

Link to comment
Share on other sites

Yes, unless you use the global keyword, no variables with a script-wide scope will be available in functions. Using global is generally frowned upon however, as it makes the function you created dependent on outside information, which greatly reduces its portability and re-usability. Functions should only be dependent on the information you pass into its argument list. Is there a reason you don't just pass the relevant arrays into the function via the argument list?

Link to comment
Share on other sites

When creating a function in PHP (and in pretty much any programming language) You have to option of specifying parameters in the argument list (The stuff between the parentheses) that allows you to pass information to the function that is available at the time of the function call. So for example

 

function foo($arg1, $arg2){//the ($arg1, $arg2) part is the argument list
echo $arg1 . " is friends with " . $arg2;
}

//some random amount of code or whatever
//...

foo("John", "Nick");//output: John is friends with Nick
foo("Mike", "Kyle");//output: Mike is friends with Kyle

//the names John, Nick, Mike, and Kyle aren't available when we define the function (we don't know the names yet!)
//but will be available when  you run the script later on, and decide who you say is friends with who
//obviously the names are arbitrarily picked in this code, and could be hard coded in the function
//but the example illustrates my point well enough I think

 

In the case of your code, the arguments that you may want to pass would be the arrays that you are iterating through (namely $array and $membernumber)

 

Link to comment
Share on other sites

So what am I missing here? I changed the function to:

function FindNumber($arr, $find) {
foreach ($arr as $key => $row) {
	if (strncasecmp($find, $membernumber[$key], strlen($find)) == 0)

Pass it the arguments:

FindNumber($array, $exploded[1]);

Stll nothing gets returned....

Link to comment
Share on other sites

Hi Nightslyr, that was a helpful hint:

I changed my function to:

function FindNumber($arr, $find) {
foreach ($arr as $key => $row) {
	if (strncasecmp($find, $membernumber[$key], strlen($find)) == 0) {
		return ($lastname[$key]}}}

The call to the function:

foreach ($lines as $donor => $numbers) {
$exploded = explode(",", $numbers);
FindNumber($array, $exploded[1]);
echo $lastname[$key];}

Now in each case the very last record gets returned though. I thought the return statement would halt execution of the function and return the current record...

Link to comment
Share on other sites

If $membernumer is unique, then sorting it like this

array_multisort($membernumber,$lastname,$firstname,$address1,$address2,$address3,$city,$stateprov,$country,$array);

 

is redundant. Instead, use

 

array_multisort($membernumber,$array);

 

 

Link to comment
Share on other sites

<?php 

$csv = array(
array( 'uid'=>'1', 'fname'=>'John', 'lname'=>'Smith', 'city'=>'Toronto' ),
array( 'uid'=>'2', 'fname'=>'Jim', 'lname'=>'Smith', 'city'=>'Calgary' ),
array( 'uid'=>'3', 'fname'=>'Steve', 'lname'=>'Smitty', 'city'=>'Toronto' ),
array( 'uid'=>'4', 'fname'=>'Bob', 'lname'=>'Johnson', 'city'=>'Toronto' ),
array( 'uid'=>'5', 'fname'=>'Tom', 'lname'=>'Smithers', 'city'=>'Vancouver' ),
);

$search = new arraySearch( $csv );

$result = $search->findMultiple( 'lname','smi', 'city','tor' );

print_r( $result );

class arraySearch {

private $data;

public function __construct( $array ) {
	$this->data = $array;
}

public function findByStart( $col, $val, $data = FALSE ) {
	$r = array();
	$l = strlen( $val );
	if( $data == FALSE ) $data = $this->data;
	foreach( $data as $line ) {
		if( !isset($line[$col]) ) continue; // Column doesn exist
		if( strncasecmp($line[$col], $val, $l) === 0 )
			$r[] = $line;
	}
	return $r;
}

public function findMultiple() {
	$args = func_get_args(); $num = func_num_args();
	$data = $this->data;
	for( $i = 0; $i < $num; $i+=2 ) {
		$data = $this->findByStart($args[$i], $args[$i+1], $data);
	}
	return $data;
}

}

?>

 

If you get lost in the OOP, let me know and I'll type up a procedural version.

Link to comment
Share on other sites

Yo xyph,

 

thank you very much for taking the time to post what you did.

 

As I stated earlier, I'm quite new to php. However, I've been programming since 1985 starting with a Radio Shack TRS80 with a cassette deck for a storage device.

 

As far as I can tell, my problem currently is the array pointer; I have no idea how to relate your post with my situation,

 

Thanks again,

 

Chris

Link to comment
Share on other sites

I don't really follow your logic in the code provided above.

 

Rather than try to follow your logic through array pointers, I have provided a script that does what you want in a differnt, possibly easier to understand way. I'll rewrite it in procedural form with comments and hopefully you'll follow.

Link to comment
Share on other sites

<?php 

// This is sort of how you've arranged your data, I'm assuming.
$csv = array(
array( 'uid'=>'5', 'fname'=>'John', 'lname'=>'Smith', 'city'=>'Toronto' ),
array( 'uid'=>'2', 'fname'=>'Jim', 'lname'=>'Smith', 'city'=>'Calgary' ),
array( 'uid'=>'1', 'fname'=>'Steve', 'lname'=>'Smitty', 'city'=>'Toronto' ),
array( 'uid'=>'4', 'fname'=>'Bob', 'lname'=>'Johnson', 'city'=>'Toronto' ),
array( 'uid'=>'4', 'fname'=>'Tom', 'lname'=>'Smithers', 'city'=>'Vancouver' ),
);

// Quickly sort by uid
usort( $csv, 'usort_UID' );
// $csv is now sorted by [x]['uid']

// Get an array of values of 'lname' that start with 'smi'
$csv_smi = findByStart( $csv, 'lname', 'smi');
// Preview this array
echo '<pre>'; print_r( $csv_smi ); echo '</pre>';

// Get an array of values of 'city' that start with 'tor' from our previous
// results of 'lname' that starts with 'smi'
$csv_tor = findByStart( $csv_smi, 'city', 'tor' );
// Notice how I use our previously returned results ($csv_smi) to only search
// through those results

// Here's where the work's done!

// Function I use to quickly sort by a sub-key in an array while using usort()
// Check it out in the manual for more details
function usort_UID( $a, $b ) {
return $a['uid'] > $b['uid'];
}

/**
* 
* Used to return only results from an array that match the start of a given
* subkey's value. Case-insensitive.
* @param $data The array to search.
* @param $col The column of the subarrays to search.
* @param $val The starting value to match.
*/
function findByStart( $data, $col, $val ) {
// This will hold our result data
$r = array();
// This gets the length of the string to check, needed for strncasecmp()
$l = strlen( $val );
// Loop through each entry in the array
foreach( $data as $row ) {
	// Check if the column even exists for this row
	if( !isset($row[$col]) ) continue; // Skips the rest of this iteration
	// Check if the values are equal up to the given length
	// (strncasecmp returns 0 if they are)
	if( strncasecmp($row[$col], $val, $l) === 0 )
		// Add a new array into our result data containing the matching data
		$r[] = $row;
}
// Return the result data
return $r;
}

?>

 

Hope that helps.

Link to comment
Share on other sites

I see xyph already helped you, I'll add the below code if anyone ever comes across this topic.

 

I had a play with it yesterday and tried to mimic SQL like functionality in PHP for CSV files. Please use SQL for any large CSV file.

 

class CSVFileObject
{
    private $_file;
    private $_skipFirst;
    
    public function __construct($csvFile, $delimiter = ',',
            $enclosure = '"', $escape = '\\', $skipFirst = false) {
        $this->_file = new SplFileObject($csvFile);
        $this->_file->setFlags(SplFileObject::READ_CSV);
        $this->_file->setCsvControl($delimiter, $enclosure, $escape);
        
        $this->_skipFirst = $skipFirst;
    }
    
    public function rewind() {
        $this->_file->rewind();
    }
    
    public function readLine($line = null)
    {
        if ($line !== null) {
            $this->_file->seek($line);
        }
        
        if (!$this->_file->valid()) {
            return false;
        }
        
        if ($this->_file->key() === 0 && $this->_skipFirst) {
            $this->_file->next();
            if (!$this->_file->valid()) {
                return false;
            }
        }
        
        $line = $this->_file->current();
        $this->_file->next();
        
        if (!is_array($line)) {
            return false;
        }
        return $line;
    }
}

interface Matcher
{
    public function match($o);
}

class CSVComplexSearch
{
    private $_csv;
    
    public function __construct(CSVFileObject $csv) {
        $this->_csv = $csv;
    }
    
    public function findWhereMatches(Matcher $matcher)
    {
        $result = new ArrayObject;
        
        while (($line = $this->_csv->readLine()) !== false) {
            if ($matcher->match($line)) {
	$result->append($line);
            }
        }
        return $result;
    }
}

class MyMatcher implements Matcher
{
    private $_line;
    
    public function match($o) {
        $this->_line = $o;
        
        return (
            $this->_whereFirstNameIs('foo') && 
            $this->_whereLastNameIs('bar')
        );
    }
    
    private function _whereFirstNameIs($str) { return $this->_line[0] === $str; }
    private function _whereLastNameIs($str) { return $this->_line[1] === $str; }
}

 

 

You use it like this:

 

$search = new CSVComplexSearch(new CSVFileObject('users.csv'));
var_dump($search->findWhereMatches(new MyMatcher));

Link to comment
Share on other sites

So it took me a couple of days to figure this out (I'm old and I'm slow). This is what I was looking for:

 

The function

function FindNumber($arr, $find) {
foreach ($arr as $key => $value) {
foreach ($value as $num => $val) {
	if ($value["num"] == $find) {
	$result = $value;
	return $result;}}}}

The call passing the parameters:

foreach ($lines as $donor => $numbers) {
$exploded = explode(",", $numbers);
$name = FindNumber($array, $exploded[1]);
foreach ($name as $num => $val) {
echo $val;}}

Works like a charm!

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.