Array rTrim or not?

Having implemented an rTrim on my array I still get commas at the end of my csv

What am I missing and is it by the way an elegant way to not have to test each item for existence or should I change my array creation?
for ($j=0;$j<count($object);$j++) {
    $line = array();
    $line[] = $object[$j]->{"item 1"};
    $line[] = $object[$j]->{"item 2"};
    $line[] = $object[$j]->{"item 3"};
    $line[] = $object[$j]->{"item 4"};
    while ("" === end($line)) { // pop off empty stuff before writing
      array_pop($line);
    }    
    fputcsv($out,$line);
  }
}  

Open in new window


I was thinking to make another array:
$fields = ["item 1","item 2","item 3","item 4"];
for ($i=0;$i<count($object);$i++) {
    $line = [];
   for ($j=0;$j<count($fields);$j++) {
     if ($object[$i] has a value in $fields[$j]) {
       $line[] = $object[$i]->$fields[$j];
      }    
    }
    fputcsv($out,$line);
  }
}  

Open in new window

but I do not know how to

a) test $object[$i] has a value in $fields[$j]
b) use the $fields[j] instead of ->{"item X"};

Thanks for your thoughts
LVL 75
Michel PlungjanIT ExpertAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Chris StanyonWebDevCommented:
Can you explain what you are trying to do. Normally in a CSV file, you will have commas at the end of a line if a value is empty, so your lines might look like

value 1, value 2,
value 1, value 2, value 3


You can then clearly see that the first line has an empty value, but the number of fields per line still match.

Not exactly sure what you're trying to do, put would this not be a cleaner way to do it:

foreach ($object as $info) {
	$line = array(
		$info->{'item 1'},
		$info->{'item 2'},
		$info->{'item 3'},
		$info->{'item 4'}
	);

	fputcsv($out,$line);
}

Open in new window

0
Michel PlungjanIT ExpertAuthor Commented:
I do not need the trailing commas since they do not serve any purpose

I use the construct to easily add or comment out items.
0
Slick812Commented:
Maybe -
$line = array();
$fields = ["item 1","item 2","item 3","item 4"];
foreach ($object as $val) {
    if (array_search($val, $fields) !== false) {
        $line[] = $val;
        }
    }
if (count($line) > 0) {
    fputcsv($out,$line);
    } else echo 'ERROR , No Valid values in Object';

Open in new window


there is also a PHP  empty( ) function if all you need is to see if there is anything in a VALUE
0
Angular Fundamentals

Learn the fundamentals of Angular 2, a JavaScript framework for developing dynamic single page applications.

Chris StanyonWebDevCommented:
OK. Instead of using the fputcsv() function, format your own line from the array and write that using fwrite:

foreach ($object as $info) {
	$line = array(
		$info->{'item 1'},
		$info->{'item 2'},
		$info->{'item 3'}
	);
	
	//create a comma separated line
	$line = implode(",", $line);
	
	//trim trailing commas
	$line = rtrim($line, ",") . PHP_EOL;
	
	//write the line to your file
	fwrite($out,$line);
}

Open in new window

0
Michel PlungjanIT ExpertAuthor Commented:
I believe I should use fputcsv since it will automatically wrap fields with spaces in quotes when necessary

I do not see

foreach ($object as $val) {
    if (array_search($val, $fields) !== false) {

as being very performant but I also do not quite understand it

I have 25 or so fields in object and I want to extract
item1value,item2value,item3value,item4value
but if there is no value in item3 I want
item1value,item2value,,item4value

and if there are no values in item4, I want

item1value,item2value,item3value

without trailing comma

If there are spaces or commas in the field value, I want it wrapped in quotes which is what fputcsv does automatically
0
Chris StanyonWebDevCommented:
Fair enough. Back to array_pop then:

foreach ($object as $info) {
	$line = array(
		$info->{'item 1'},
		$info->{'item 2'},
		$info->{'item 3'},
		$info->{'item 4'}
	);
	
	while ("" === end($line)) {
		array_pop($line);
	}

	fputcsv($out, $line);
}

Open in new window

0
Michel PlungjanIT ExpertAuthor Commented:
But is that not EXACTLY the same as

for ($j=0;$j<count($object);$j++) {
    $line = array();
    $line[] = $object[$j]->{"item 1"};
    $line[] = $object[$j]->{"item 2"};
    $line[] = $object[$j]->{"item 3"};
    $line[] = $object[$j]->{"item 4"};
    while ("" === end($line)) { // pop off empty stuff before writing
      array_pop($line);
    }    
    fputcsv($out,$line);
}

Open in new window

0
Chris StanyonWebDevCommented:
Then something else is going on because the code I've just posted works fine. Maybe we need to see your data ($object). Run the following code:

<?php
$object = array();

$object[0]->{'item 1'} = 'Value 1';
$object[0]->{'item 2'} = '';
$object[0]->{'item 3'} = 'Value 3';

$object[1]->{'item 1'} = 'Value 1';
$object[1]->{'item 2'} = '';
$object[1]->{'item 3'} = '';

$out = fopen('file.csv', 'w');

foreach ($object as $info) {
	$line = array(
		$info->{'item 1'},
		$info->{'item 2'},
		$info->{'item 3'}
	);
	
	while ("" === end($line)) { array_pop($line); }

	fputcsv($out,$line);
}
?>

Open in new window

It will generate a CSV file with the following content:

"Value 1",,"Value 3"
"Value 1"

Open in new window

0
Slick812Commented:
OK, you seem to be where you are without much experience in PHP, as to the  -
 if (array_search($val, $fields) !== false) {

if you look at the function array_search( ) in the php manual it says -
array_search — Searches the array for a given value and returns the corresponding key if successful and returns false if NOT in the array values

Seem to me to be exactly what you need?, as to your - "as being very performant ", for arrays with only a small amount of values like "25 or so fields", this is very Fast, I will guess that you did not even try that code?

this is different and only tests for a $object value being without a Value, BUT it will return false for a string of "0" or a number of Zero, which may not be what you need
foreach ($object as $val) {
    if (!empty($val)) {
        $line[] = $val;
        }
    }

Open in new window

0
Ray PaseurCommented:
fputcsv() is probably your friend.  You can also make a string from an array with implode().  If you wanted quoted values, you could do something like this:

$str = '"' . implode('","', $arr) . '"';

Open in new window

0
Michel PlungjanIT ExpertAuthor Commented:
You are correct, Slick, my PHP is not fully formed

In JavaScript it would be
var obj = [
{
  "Item 1":"value 1",
  "Item 2":"value 2",
  "Item 3":"",
  "Item 4":"value 4"
},
{
  "Item 1":"value 1",
  "Item 2":"value-2", // no space
  "Item 3":"",
  "Item 4":"",
  "Item 5":"this will be ignored since I do not need it"
}
]

for (var i=0;i<obj.length;i++) {
  var line = [];
  line[line.length] = obj[i]["Item 1"];
  line[line.length] = obj[i]["Item 2"];
  line[line.length] = obj[i]["Item 3"];
  line[line.length] = obj[i]["Item 4"];
  for (var j=line.length-1;j>=0;j--) {
    if (line[j]==="") delete line[j];
  }
  // write line here, only quoting values with space
}

Open in new window

which would result in
"value 1","value 2",,"value 4"
"value 1",value-2
0
Michel PlungjanIT ExpertAuthor Commented:
@ChrisStanyon - I think you may be right.

I'll debug some more
0
Ray PaseurCommented:
I think there is no harm in quoting values, whether or not the values contain spaces.  Quoting is more of a "required" item when there are spaces or embedded quotes.  But I do not see it as an issue except when it is omitted on a required element.
0
Ray PaseurCommented:
Try this, and be sure to note that the output will have extra double quotes on the lines -- that is just the way PHP displays this stuff.

<?php // RAY_temp_mplungjan.php
error_reporting(E_ALL);

/* SEE http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_28237757.html#a39488464
var obj = [
{
  "Item 1":"value 1",
  "Item 2":"value 2",
  "Item 3":"",
  "Item 4":"value 4"
},
{
  "Item 1":"value 1",
  "Item 2":"value-2", // no space
  "Item 3":"",
  "Item 4":"",
  "Item 5":"this will be ignored since I do not need it"
}
]

for (var i=0;i<obj.length;i++) {
  var line = [];
  line[line.length] = obj[i]["Item 1"];
  line[line.length] = obj[i]["Item 2"];
  line[line.length] = obj[i]["Item 3"];
  line[line.length] = obj[i]["Item 4"];
  for (var j=line.length-1;j>=0;j--) {
    if (line[j]==="") delete line[j];
  }
  // write line here, only quoting values with space
}
*/

// ALL DATA ELEMENTS
$thing = array
( array
  ( "Item 1" => "value 1"
  , "Item 2" => "value 2"
  , "Item 3" => ""
  , "Item 4" => "value 4"
  )
, array
  ( "Item 1" => "value 1"
  , "Item 2" => "value-2"  // NO SPACE
  , "Item 3" => ""
  , "Item 4" => ""
  , "Item 5" => "this will be ignored since I do not need it"
  )
)
;

// REQUIRED DATA ELEMENTS
$required = array
( 'Item 1'
, 'Item 2'
, 'Item 3'
, 'Item 4'
)
;

// PROCESS THE DATA ELEMENTS
$out = array();

// USING EACH INPUT ARRAY
foreach ($thing as $sub_array)
{
    // CREATE AN ARRAY OF THE INPUTS
    $lin = array();
    foreach ($sub_array as $key => $value)
    {
        // IF THIS IS NOT ONE OF THE REQUIRED ELEMENTS
        if (!in_array($key, $required)) continue;

        // SAVE THE QUOTED VALUE
        $lin[$key] = '"' . $value . '"';
    }

    // USE IMPLODE TO CONNECT THE QUOTED VALUES INTO COMMA-SEPARATED STRINGS
    $out[] = implode(',', $lin);
}

// SHOW THE CONSTRUCTED DATA
echo '<pre>';
var_dump($out);

Open in new window

0
Slick812Commented:
@mplungjan, I looked at your javascript, and I will first say that In javascript and PHP the ARRAY and OBJECT access to elements in code work are DIFFERENT, in ways they are similar and in other ways they are different, but you give this in your example for PHP -
or ($j=0;$j<count($object);$j++) {
    $line = array();
    $line[] = $object[$j]->{"item 1"};
    $line[] = $object[$j]->{"item 2"};
    $line[] = $object[$j]->{"item 3"};
    $line[] = $object[$j]->{"item 4"};
    while ("" === end($line)) { // pop off empty stuff before writing
      array_pop($line);
    }    
    fputcsv($out,$line);
  }
}

Open in new window

which I try to see if it does what you want or not compared to your jscript -
for (var i=0;i<obj.length;i++) {
  var line = [];
  line[line.length] = obj[i]["Item 1"];
  line[line.length] = obj[i]["Item 2"];
  line[line.length] = obj[i]["Item 3"];
  line[line.length] = obj[i]["Item 4"];
  for (var j=line.length-1;j>=0;j--) {
    if (line[j]==="") delete line[j];
  }
  // write line here, only quoting values with space
}

Open in new window

However, it's not to plain the result you may need in line, However it is plain to me now that you want more than ONE CSV file , you need a separate CSV file for each Object or array in the $object or obj container. the thing in your result that I can not understand is this -
"value 1","value 2",,"value 4"
there is a BLANK between the two commas between "value 2" and "value 4"  even though you have this-
if (line[j]==="") delete line[j];
so if there is a String without characters as "" you still need it to output nothing between two commas? ?
To me this changes the whole game plan, please try and give me some more about the output you need? ? trying to get this workable for you, sorry I missed your explanation.
0
Slick812Commented:
OK,  I started all over to consider How to go about this one, and I saw a couple of problems that I did not think of before, so I did the code work to see if I could iron them out. You seem to mix the PHP objects and arrays, as you look like you have an array of objects for your $object , , I tried to duplicate this and call mine $objects, because your $object is NOT an object. . . .
here is my code for PHP, and as far as I can tell, it works on my server -
$out = fopen('csv0.csv', 'w');
$objects = array (
     (object)array('key 1'=>'value 1', 'key 2'=>'value 2', 'key 3'=>'', 'key 4'=>'value-4', 'key 5'=>'value-5'),
    (object)array('key 1'=>'value-1', 'key 2'=>'value 2', 'key 3'=>'', 'key 4'=>'', 'key 5'=>'value 5', 'key 6'=>'value 6'),
    (object)array('key 1'=>'value-1', 'key 2'=>'', 'key 3'=>''),
    (object)array('key 1'=>'no1', 'key 2'=>'', 'key 3'=>'no3', 'key 4'=>'')
    );
$valid = array('key 1', 'key 2', 'key 3', 'key 4');// I had to have an array of VALID keys
$max = count($valid);// maximum of allowed Array length, chop off all elements more than this

for ($i=0;$i<count($objects);++$i) { // roll through all objects in $objects
	$objects[$i] = (array)$objects[$i];//convert objects  to arrays for fputcsv( )
	$amt = count($objects[$i]);// needed to use for "Short Arrays"
	if ($amt > $max) {
	$objects[$i] = array_slice($objects[$i], 0, $max);// chop off all longer arrays
	$amt = $max;
	}
	if ($objects[$i][$valid[$amt-1]] != '') {// if last one is OK zoom it through fputcsv( )
           fputcsv($out, $objects[$i]);
        } else { // last one is "", start chopping off ''
        for ($c=$amt-1;$c>-1;--$c) {
            if ($objects[$i][$valid[$c]] == '') {
                unset($objects[$i][$valid[$c]]);
                } else break;
            }// for ($c=$amt-1;$c>
        fputcsv($out, $objects[$i]);
        } // else {
    }// for ($i=0;$i<count($objects)
fclose($out);

Open in new window

maybe this will do some of what you say?  Outputs in csv0.csv -
"value 1","value 2",,value-4
value-1,"value 2"
value-1
no1,,no3
0
Michel PlungjanIT ExpertAuthor Commented:
Ok, I found the problem I think

After I run my pop I have

Array
(
    [0] => Item 1
    [1] => Item 2
    [2] =>
    [3] =>
    [4] =>
    [5] =>
    [6] =>
    [7] =>
    [8] =>
)

should be

Array
(
    [0] => Item 1
    [1] => Item 2
)

So the test

   while ("" === end($line)) { // pop off empty stuff before writing
      array_pop($line);
    }    

is not correct. I believe I need to add test for null somehow

This gives an error

<b>Fatal error</b>:  Can't use function return value in write context

   while (empty(end($line))) { // pop off empty stuff before writing
      if (end($line)!==0) array_pop($line);
    }
0
Ray PaseurCommented:
Some versions of PHP require you to assign function return values to variables before the values can be used in the next function call.  Stacking up end(), empty(), and while() apparently trips over this issue.

Not sure exactly where you're going with the concept of truncating the array, but one strategy might be to unset() the values you want to remove.  If you have embedded empty positions and you only want to remove the trailing empty positions, you might reverse the array and truncate from the top, then reverse the array again.
http://www.laprbass.com/RAY_temp_mplungjan.php

<?php // RAY_temp_mplungjan.php
error_reporting(E_ALL);

// SEE http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_28237757.html#a39489277

// ALL DATA ELEMENTS
$old = array
( 'Item 1'
, 'Item 2'
, NULL
, NULL
, NULL
)
;

$dummy = array_reverse($old);
foreach ($dummy as $key => $value)
{
    if (!empty($value)) break;
    unset($dummy[$key]);
}
$new = array_reverse($dummy);

echo '<pre>';
print_r($old);
print_r($new);

Open in new window

HTH, ~Ray
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Michel PlungjanIT ExpertAuthor Commented:
Thanks for the explanation. That is very disappointing that such a trivial task is not allowed.

To reverse twice sounds like too much processing for the simple task of not having trailing commas in my csv.

I see Slick812's post now I am on my computer and not my phone. I like the idea of testing the last item first.

Sounds Like I should fill the array in reverse order or just test for content when I create it.

I wanted to NOT have to get the value twice

Does this code make sense and is it valid PHP?

$fields = ["item 1","item 2","item 3","item 4"];
for ($i=0;$i<count($object);$i++) {
   $line = [];
   $found=false;
   for ($j=count($fields)-1;$j>=0;$j--) {
     $val = $object[$i][$fields[$j]];
     if ($found) {
         $line[$j] = $val?$val:"";
     }
     else {
       if ($val) {
         $line[$j] = $val;
         $found=true;
       }
      }
    }
    fputcsv($out,$line);
  }
}

Open in new window

0
Ray PaseurCommented:
reverse twice sounds like too much processing ...
Nope, this amount of processing will be so small as to be unmeasurable in the larger context of a script that includes writing a CSV file.  In-memory operations like manipulating arrays run about 100,000 times faster than a simple disk I/O operation, and there are many, many disk I/O operations required to write a CSV file.

The code may be valid PHP (however $object is undefined) but it seems unnecessarily complicated.  I would just use foreach(), since PHP does not have an array truncation function that fits well here.  There might be some way to play array_slice(), but that would require another round of testing and by the time you've tested it, you've already spent more time than you can save by running it!
0
Michel PlungjanIT ExpertAuthor Commented:
I am of course also hammering on this to learn more PHP. I seem to often run into the array vs object container issue.

Thanks for your thoughts, I'll try the reverse/reverse
0
Michel PlungjanIT ExpertAuthor Commented:
The reverse worked well.

PS: To answer a previous question: No I only need one CSV since the programs that read the CSV (Splunk and Excel) do not care about trailing commas
0
Ray PaseurCommented:
If you're willing to put up with an English-language book, I recommend this one (get the latest version)
http://www.amazon.com/PHP-MySQL-Web-Development-Edition/dp/0321833899

When you are reading data, the array vs object thing mostly comes down to a difference in syntax.  Element pointers of an array are written like this:

$my_array['my_pointer']

Property pointers of an object are like this:

$my_object->my_pointer

In both cases, a variable can be used in place of the literal string my_pointer.  Iterators like foreach() work more or less the same.  The advantage of object notation is that you avoid the fiddly punctuation with the quotes.  This lets you use HEREDOC with greater ease.  Unlike arrays elements, object properties are not readily addressable with explicit numeric indexes.

Where arrays and objects differ significantly is in the mutation of the data.  Objects are passed by reference, whereas arrays are passed by copying the array and allowing functions and foreach() to access the copy.  If you assign an array to a variable, you get a copy of the array.  But if you assign an object, you get an object pointer in the new variable, and anything that is done to the new variable pointer will affect the object.  If you want to replicate the object, you must either instantiate it from its class, or use clone, which comes under control of class methods.

<?php // RAY_temp_mplungjan.php
error_reporting(E_ALL);

// SEE http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_28237757.html

// CREATE AN ARRAY
$old = array
( 'Item 1'
, 'Item 2'
, NULL
, NULL
, NULL
)
;

// COPY THE ARRAY AND MUTATE IT
$new = $old;
$new[0] = 'Splat!';

// SHOW THE ARRAYS
var_dump($old, $new);


// CREATE AN OBJECT
$obj_1 = new stdClass;
$obj_1->abc = 'ABC';
$obj_1->def = 'DEF';

// "COPY" THE OBJECT, BUT PHP ONLY COPIES THE POINTER IN THE SYMBOL TABLE
$obj_2 = $obj_1;

// MUTATE THE OBJECT
$obj_2->abc = 'Splat!';

// SHOW THAT THE MUTATION APPLIED TO OBJECT #1
var_dump($obj_1);

Open in new window

0
Slick812Commented:
Since I already did this just to see if I could do this with out a list of VALID keys, I'll post it here, even though this question has been answered. . . If you have a chance to work through this code you may can compare it to may previous working code in comment ID: 39489175  , and see some of the programming posiblities in PHP,, ,  in this one I incorporate a rarely used ARRAY access using the PHP array pointers with some of the functions like end( ) and prev( ), manual at -
http://www.php.net/manual/en/function.end.php

$out = fopen('csv0.csv', 'w');
$objects = array (
    (object)array('key1'=>'value 1', 'key2'=>'value 2', 'key3'=>'', 'key4'=>'value-4', 'key5'=>'value-5'),
    (object)array('key1'=>'value-1', 'key2'=>'', 'key3'=>'', 'key4'=>'', 'key5'=>'value 5', 'key6'=>'value 6'),
    (object)array('key1'=>'val1', 'key2'=>'', 'key3'=>''),
    (object)array('key1'=>'no1', 'key2'=>'', 'key3'=>'no3', 'key4'=>'')
);
echo 'objects 0 key2= ',$objects[0]->key2,'<br />';
//$valid = array('key1', 'key2', 'key3', 'key4'); // Don't need this now
$max = 4; //count($valid); STILL need a max allowed, or it is impossible for me to figure out how to get rid of extended useless

for ($i=0;$i<count($objects);++$i) {
    $objects[$i] = (array)$objects[$i];
    $amt = count($objects[$i]);
    if ($amt > $max) {
        $objects[$i] = array_slice($objects[$i], 0, $max);
        $amt = $max;
	}
    $last=end($objects[$i]);
    if ($last != '') fputcsv($out, $objects[$i]); else
        {
        while($last === '') {
            $drop=key($objects[$i]);
            $last = prev($objects[$i]);
            unset($objects[$i][$drop]);
            }
	fputcsv($out, $objects[$i]);
        }
    }
fclose($out);

Open in new window

also I changed the OBJECT keys to things without spaces, although you can have spaces in the object properties, you can not use the standard access reference on them , like -
$objects[0]->key 2    // because of space this will not work
but this may work
$s = 'key 2';
$objects[0]->$s
0
Chris StanyonWebDevCommented:
empty() is a language construct and not a function, so it expects a variable as an argument - not another function, which is why you can't use:

empty( end($line) )

What you could have done instead was to create and use your own function:

Your code would then look like:

$out = fopen('file.csv', 'w');

foreach ($object as $info) {
	$line = array(
		$info->{'item 1'},
		$info->{'item 2'},
		$info->{'item 3'}
	);
	
	while (myEmpty(end($line))) { array_pop($line); }

	fputcsv($out,$line);
}

function myEmpty($var) { return empty($var); }

Open in new window

Nice and simple :)
0
Michel PlungjanIT ExpertAuthor Commented:
Thanks slick, I'd  still need the $valid since my actual data of course not are the first 4 but are named items from the object

Chris - I do not understand why something that looks like a function cannot take the output from something else that looks and behaves like a function and returns a something
0
Chris StanyonWebDevCommented:
empty is not a function - it's a language contruct - part of the PHP base, and can only take a variable as an argument - it can't take an expression as an argument, such as another function.

Actually, as of PHP 5.5.0 it can take expressions as well :)
0
Michel PlungjanIT ExpertAuthor Commented:
Ah, I am running 5.2

I can upgrade to 5.3 - will have to look at implications
0
Ray PaseurCommented:
From 5.2 to 5.3 you may hit some "deprecated" messages, but nothing is lost yet.  Look out for ereg() functions, split() and date_default_timezone; IIRC those are the ones that seem to come up the most often.
http://php.net/manual/en/migration53.php

Example of a deprecation notice in the docs:
http://php.net/manual/en/function.split.php
0
Michel PlungjanIT ExpertAuthor Commented:
Thanks, Ray!
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
PHP

From novice to tech pro — start learning today.