Avatar of Michel Plungjan
Michel PlungjanFlag for Denmark asked on

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
PHP

Avatar of undefined
Last Comment
Michel Plungjan

8/22/2022 - Mon
Chris Stanyon

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

ASKER
Michel Plungjan

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.
Member_2_248744

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
Experts Exchange is like having an extremely knowledgeable team sitting and waiting for your call. Couldn't do my job half as well as I do without it!
James Murphy
Chris Stanyon

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

ASKER
Michel Plungjan

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
Chris Stanyon

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

Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER
Michel Plungjan

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

Chris Stanyon

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

Member_2_248744

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

Your help has saved me hundreds of hours of internet surfing.
fblack61
Ray Paseur

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

ASKER
Michel Plungjan

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
ASKER
Michel Plungjan

@ChrisStanyon - I think you may be right.

I'll debug some more
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
Ray Paseur

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.
Ray Paseur

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

Member_2_248744

@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.
This is the best money I have ever spent. I cannot not tell you how many times these folks have saved my bacon. I learn so much from the contributors.
rwheeler23
SOLUTION
Member_2_248744

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
See how we're fighting big data
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
ASKER
Michel Plungjan

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);
    }
ASKER CERTIFIED SOLUTION
Log in to continue reading
Log In
Sign up - Free for 7 days
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER
Michel Plungjan

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

Ray Paseur

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!
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER
Michel Plungjan

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
ASKER
Michel Plungjan

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
Ray Paseur

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

All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck
Member_2_248744

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
Chris Stanyon

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 :)
ASKER
Michel Plungjan

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
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
Chris Stanyon

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 :)
ASKER
Michel Plungjan

Ah, I am running 5.2

I can upgrade to 5.3 - will have to look at implications
Ray Paseur

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
I started with Experts Exchange in 2004 and it's been a mainstay of my professional computing life since. It helped me launch a career as a programmer / Oracle data analyst
William Peck
ASKER
Michel Plungjan

Thanks, Ray!