PHP - SplFileObject - Wrong output for second line when using seek() method - php

Go to UPDATE to read what's the actual problem now. Old question was already resolved with the first answer submitted by Bert Peters.
OLD QUESTION:
I have few files named as file.1.txt, file.2.txt, file.3.txt, ... I'm reading first file with SplFileObject and using foreach loop to iterate through its content:
$file = new SplFileObject("file.1.txt");
foreach ($file as $row) {
// ...
}
Other files may be or may not be read, depending on the contents of the first file I'm reading. In all cases there should be only one file of others (file.2.txt or file.3.txt) which may be used in the next step. So somewhere inside foreach loop there is if statement which handles this.
All files have the same structure, so there comes the problem. I wouldn't like to create new foreach for reading next file - as I wrote it may not be needed at all, so I would like to use existing foreach instead of writing new one. Is there any possibility to overwrite $file variable with the contents of other file and iterate over it with using only one foreach or any other loop? For example:
foreach ($file as $row) {
// ...
if ($contentContainsSomething) {
$file = new SplFileObject("file.2.txt");
// somehow reset foreach to read file.2.txt from start
}
}
I wouldn't like to use goto statement to solve this problem. The recursion seems to be appropriate solution, but if there's a way to change object in loop on the fly, I would prefer this solution.
UPDATE:
As mentioned in "old question" all used files (file.1.txt, file.2.txt, ...) have the same structure, so that's why I wouldn't like to write more same loops and copy code. Instead I used code from #Danack (suggested by him on SO chat) which is already a part of solution. Here's the basic code for reading more files without any upgrade I need:
$path = "file.1.txt";
$whileCounter = 0;
while ($path != null) {
$file = new SplFileObject($path);
$file->setFlags(SplFileObject::READ_CSV);
$file->setCsvControl("\t");
$path = null;
foreach ($file as $rowKey => $row) {
// echo row }
$path = "file.2.txt";
if ($whileCounter > 0) {
break; // solution to stop loop, just for now
}
$whileCounter++;
}
So this code is working without any problem and outputs the file's lines as expected. The problem is when I would like to read next line of file with seek() method, because I would like to make decision on some information which is appended to each next line. So if I use seek($rowKey + 1) which helps me to get next line data (I use $file->current() when line is changed) and after that I call seek($rowKey) to get to previous line, then next file will output first line twice and second line will be missed. The third line and all after then are printed well. This is the problem achieved with the code below:
$path = "file.1.txt";
$whileCounter = 0;
while ($path != null) {
$file = new SplFileObject($path);
$file->setFlags(SplFileObject::READ_CSV);
$file->setCsvControl("\t");
$path = null;
foreach ($file as $rowKey => $row) {
if ($whileCounter > 0) {
var_dump($row);
echo "<br>";
}
$file->seek($rowKey + 1);
if ($file->valid()) {
$file->seek($rowKey);
} else {
var_dump($row);
echo "<br>";
$path = "file.2.txt";
}
}
$whileCounter++;
}
If you apply custom .csv files (with at least five non-empty lines) instead of file.1.txt and file.2.txt, you will see that second and third output are the same (second and third output are first and "second" lines of file.2.txt). What could be wrong here?

There is not. Foreach uses an iterator over your $file variable, and that iterator continues to be valid even though you changed the value of $file.
Or, to put this in another way, foreach will continue to look at the previous contents of $file, regardless of what you do with it afterwards. This is because $file is not actually the SplFileObject, but rather a reference to it, and the reference is used by foreach.

Related

PHP If-Else does not work for comparing filecontents

I am trying to make a PHP application which searches through the files of your current directory and looks for a file in every subdirectory called email.txt, then it gets the contents of the file and compares the contents from email.txt with the given query and echoes all the matching directories with the given query. But it does not work and it looks like the problem is in the if-else part of the script at the end because it doesn't give any output.
<?php
// pulling query from link
$query = $_GET["q"];
echo($query);
echo("<br>");
// listing all files in doc directory
$files = scandir(".");
// searching trough array for unwanted files
$downloader = array_search("downloader.php", $files);
$viewer = array_search("viewer.php", $files);
$search = array_search("search.php", $files);
$editor = array_search("editor.php", $files);
$index = array_search("index.php", $files);
$error_log = array_search("error_log", $files);
$images = array_search("images", $files);
$parsedown = array_search("Parsedown.php", $files);
// deleting unwanted files from array
unset($files[$downloader]);
unset($files[$viewer]);
unset($files[$search]);
unset($files[$editor]);
unset($files[$index]);
unset($files[$error_log]);
unset($files[$images]);
unset($files[$parsedown]);
// counting folders
$folderamount = count($files);
// defining loop variables
$loopnum = 0;
// loop
while ($loopnum <= $folderamount + 10) {
$loopnum = $loopnum + 1;
// gets the emails from every folder
$dirname = $files[$loopnum];
$email = file_get_contents("$dirname/email.txt");
//checks if the email matches
if ($stremail == $query) {
echo($dirname);
}
}
//print_r($files);
//echo("<br><br>");
?>
Can someone explain / fix this for me? I literally have no clue what it is and I debugged soo much already. It would be heavily gracious and appreciated.
Kind regards,
Bluppie05
There's a few problems with this code that would be preventing you from getting the correct output.
The main reason you don't get any output from the if test is the condition is (presumably) using the wrong variable name.
// variable with the file data is called $email
$email = file_get_contents("$dirname/email.txt");
// test is checking $stremail which is never given a value
if ($stremail == $query) {
echo($dirname);
}
There is also an issue with your scandir() and unset() combination. As you've discovered scandir() basically gives you everything that a dir or ls would on the command line. Using unset() to remove specific files is problematic because you have to maintain a hardcoded list of files. However, unset() also leaves holes in your array, the count changes but the original indices do not. This may be why you are using $folderamount + 10 in your loop. Take a look at this Stack Overflow question for more discussion of the problem.
Rebase array keys after unsetting elements
I recommend you read the PHP manual page on the glob() function as it will greatly simplify getting the contents of a directory. In particular take a look at the GLOB_ONLYDIR flag.
https://www.php.net/manual/en/function.glob.php
Lastly, don't increment your loop counter at the beginning of the loop when using the counter to read elements from an array. Take a look at the PHP manual page for foreach loops for a neater way to iterate over an array.
https://www.php.net/manual/en/control-structures.foreach.php

php retrieve value from any line in a file

I'm trying to grab a specific value out of a file and turn it into a variable. I've manged to figure this out, but there is a catch. I need to get the variable even if the file changes so I can't depend getting this value by reading a certain line from the file as it will change on a regular basis. Here is my file and code:
# the file.props contents:
color=red
height=tall
length=short
weight=heavy
size=small
shape-name=round
Php code:
<?php
$file = "/home/user/files/file.props";
$contents = file($file, FILE_SKIP_EMPTY_LINES);
$shape_name = substr(trim($contents[5]), 11);
?>
<?php echo "$shape_name"; ?>
The above works but only if "shape-name=round" is on line 6 of the file as I am using $contents[5] to get it. Is it possible to do this if the line the "shape-name=round" is constantly being altered? IE: tomorrow it will be on line 9, the next day it could be on line 4 etc... Basically I can't depend on what line "shape-name=round" is on but I need to grab it. Not sure I am describing this correctly so please let me know if I need to clarify anything.
Maybe you mean something like this?
foreach($contents as $line) {
list($option, $value) = explode('=', $line);
if ($option == 'shape-name') {
$shape_name = $value;
} elseif ($option == 'size') {
$size = $value;
}
// you can include as many option as possible here
}
you need to loop your data. like this:
$row = 0;////get the row number.
foreach ($contents as $cs){
$row++;
if($row >= 6){
////do something
}
}
HAPPY CODING!

PHP function GLOB: get and modify the results

I'm trying to import many xml files that I do not know the name.
I use this code:
foreach(glob('OLD/*.xml') as $file) {
$url= basename($file) . ', ';
$all_urls = array($url);
foreach ($all_urls as $url) {
$xml = simplexml_load_file($url);
I have a lot of files like agency.xml, annunci_324.xml, annunci_321.xml, ecc...
I only need the files that begin for annunci and end .xml. I also need to delete last value's comma and put it in the last foreach. how can i do it?
I think you can check if name contains annunci with strstr function (documentation here)
if(strstr($file, 'annunci')
{
//we found a file with name we are interessed in.
Now you can build directly your array without caring about commas
$all_urls = array();
foreach(glob('OLD/*.xml') as $file)
{
if(strstr($file, 'annunci')
{
$all_urls[] = array(basename($file));
}
}
This way we have all_urls as array of all the files starting with annunci and you can loop in it to simple_load them all.

How to update csv column names with database table header

I am facing this problem some past days and now frustrate because I have to do it.
I need to update my CSV file columns name with database table header. My database table fields are different from CSV file. Now the problem is that first I want to update column name of CSV file with database table headers and then import its data with field mapping into database.
Please help me I don't know how I can solve this.
This is my php code:
$file = $_POST['fileName'];
$filename = "../files/" . $file;
$list = $_POST['str'];
$array_imp = implode(',', $list);
$array_exp = explode(',', $array_imp);
$fp = fopen("../files/" . $file, "w");
$num = count($fp);
for ($c = 0; $c < $num; $c++) {
if ($fp[c] !== '') {
fputcsv($fp, $array_exp);
}
}
fclose($fp);
require_once("../csv/DataSource.php");
$path = "../files/test_mysql.csv";
$dbtable = $ext[0];
$csv = new File_CSV_DataSource;
$csv->load($path);
$csvData = $csv->connect();
$res='';
foreach($csvData as $key)
{ print_r($key[1]);
$myKey ='';
$myVal='';
foreach($key as $k=>$v)
{
$myKey .=$k.',';
$myVal .="'".$v."',";
}
$myKey = substr($myKey, 0, -1);
$myVal = substr($myVal, 0, -1);
$query="insert into tablename($myKey)values($myVal)";
$res= mysql_query($query);
You have got an existing file of which the first line needs to be replaced.
This has been generally outlined here:
Overwrite Line in File with PHP
Some little explanation (and some tips that are not covered in the other question). Most often it's easier to operate with two files here:
The existing file (to be copied from)
A new file that temporarily will be used to write into.
When done, the old file will be deleted and the new file will be renamed to the name of the old file.
Your code does not work because you are already writing the new first line into the old file. That will chop-off the rest of the file when you close it.
Also you look misguided about some basic PHP features, e.g. using count on a file-handle does not help you to get the number of lines. It will just return 1.
Here is step by step what you need to do:
Open the existing file to read from. Just read the first line of it to advance the file-pointer (fgets)
Open a new file to write into. Write the new headers into it (as you already successfully do).
Copy all remaining data from the first file into the new, second file. PHP has a function for that, it is called stream_copy_to_stream.
Close both files.
Now check if the new file is what you're looking for. When this all works, you need to add some more steps:
Rename the original file to a new name. This can be done with rename.
Rename the file you've been written to to the original filename.
If you want, you then can delete the file you renamed in 5. - but only if you don't need it any longer.
And that's it. I hope this is helpful. The PHP manual contains example code for all the functions mentioned and linked. Good luck. And if you don't understand your own code, use the manual to read about it first. That reduces the places where you can introduce errors.
If you are managing to insert the table headers then you're half way there.
It sounds to me like you need to append the data after the headers something like:
$data = $headers;
if($fp[c]!=='')
{
$data .= fputcsv($fp, $array_exp);
}
Notice the dot '.' before the equals '=' in the if statement. This will add none blank $fp[c]values after the headers.

How do I increment a variable so it chooses differnet lines from a text files in a While Loop

I have a script im writing. Here is whats happening. There is a while loop. In the while loop is a variable which is constant to X. How do i make X change from line one, line two, etc for each cycle of the while loop and pull X from a .txt file. Everything is in root. Thanks
$f = fopen("some.txt", "r");
while (!feof($f) && $some_condition) {
$x = fgets($f);
// do something
}
fclose($f);
Would this be sufficient?
Here is the pseudo code captain Kirk:
//we assume current working directory is root
fileHandle = openFile("Read","some.txt");
X = pull("X",fileHandle);
while( X is constant )
{
XFactor = factor(X);
}
I can refine and improve this with more details about what universe you are from, the programming language you intend to use, and more specifics about what you want to happen.
//get the lines of the file into an array
$file_array = file($file_name);
//go through the array line by line
foreach ($file_array as $line_number => $line)
{
//you didn't tell us what you are doing with each line
//so you will need to change this to your liking
$X = $line; // Handle the line
}
Edit: Note for very large files this may not be a good approach because this will load the entire file into memory at one time.

Categories