PHP/JS: Removing final comma in a delimited list - php

What is the best, most concise coding practice for eliminating the final comma when generating a comma-delimted list in a typical for loop? This comes up ALL THE TIME and I cannot stand writing so many extra lines of code for something so simple... there must be a better technique/pattern.
foreach ($list as $item)
{
echo "'".$item . "',";
}
What is the best way (using PHP and/or JS) to make the above code produce a comma everywhere but the last iteration?
Right now I am doing something like this:
$total = count($images);
$i=0;
foreach ($list as $item)
{
$i++;
echo "'".$item."'";
if ($i<$total) echo ',';
}
But this adds FOUR LINES OF CODE for something so simple...

The Standard PHP Library (SPL) offers a handy class, CachingIterator that can be used to see if there are any more items to be iterated over. The following may not be as concise as you would like but it is flexible (i.e. can be used for far more than just arrays).
$array = range('a', 'g');
$cache = new CachingIterator(new ArrayIterator($array));
foreach ($cache as $item) {
echo "'$item'";
if ($cache->hasNext()) {
echo ',';
}
}
The above example outputs
'a','b','c','d','e','f','g'

In case you didn't simplified the code example:
echo implode(',', $list);
does it (see also implode PHP Manual ).
If there is more code within the foreach loop you need to keep track whether or not you're on the last element and deal with the situation:
$count = count($list);
$current = 0;
foreach ($list as $item)
{
$current++;
$notLast = $current !== $count;
echo $item;
if ($notLast) echo ',';
}
Edit: you added a similar code to the question after I answered this, so if that is too burdensome for your coding fingers and especially (and understandable) you don't want to repeat such code all day long the same, you need to encapsulate it for re-use. One solution is to implement this within a re-useable iterator:
$list = array('a', 'b', 'c');
class PositionKnowingIterator implements iterator
{
/**
* #var iterator
*/
private $inner;
private $count;
private $index;
public function __construct(array $list) {
// NOTE: implement more iterators / objects to deal with in here
// if you like. This constructor limits it to arrays but
// more is possible.
$this->count = count($list);
$this->inner = new ArrayIterator($list);
}
/* SPL iterator implementation */
public function current() {
return $this->inner->current();
}
public function next() {
$this->index++;
$this->inner->next();
}
public function key() {
$this->inner->key();
}
public function rewind() {
$this->index = 1;
$this->inner->rewind();
}
public function valid() {
return $this->inner->valid();
}
/* Position Knowing */
public function isLast() {
return $this->index === $this->count;
}
public function notLast() {
return !$this->isLast();
}
public function isFirst() {
return $this->index === 1;
}
public function notFirst() {
return !$this->isFirst();
}
public function isInside() {
return $this->notFirst() && $this->notLast();
}
}
foreach($iterator = new PositionKnowingIterator($list) as $item)
{
echo "'".$item."'", $iterator->notLast() ? ',' : '';
}

echo implode(",", $list);
without using foreach needed

User implode() function to achieve this. Sometimes it's also necessary to put something around, for example, to quote SQL fields' values:
$fields = '"' . join('", "', $values) . '"';
And for JavaScript use Array.join() method (W3C):
var implodedString = someArray.join(',')

if I get you right, you can use the implode function.
This does the job for you.
BR,
TJ

Why not:
echo implode(",", $list);
?

The common used practice: using 'join' function or its analog. This function exists almost in every language, so it's most simple, clear and environment independent approach.
echo join(", ", $list);

Related

PHP Algorithm trouble: using a "set" function for a complex object

Using a set function in a class as below works great:
<?php
class forest
{
public $tree;
public function set($k, $v)
{
if (property_exists($this, $k)) {
$this->$k = $v;
}
}
}
$myClass = new forest();
$myClass->set('tree', 'birch');
?>
However, I can't work out how to do the same for something more complex.
(in pseudo-code)
tree->deciduous = birch
tree->coniferous = cypress
I've tried this, which didn't work:
<?php
class forest
{
private $tree;
private $climate;
function __construct()
{
$this->tree = new stdClass;
}
public function set($k, $v)
{
if (is_array($k)) {
$d = $this;
foreach ($k as $c => $b) {
$d = $d->$b;
}
$d = $v;
} else {
if (property_exists($this, $k)) {
$this->$k = $v;
} else {
echo "<pre>\n $k does not exist \n</pre>";
}
}
}
}
$myClass->set('climate', 'temperate'); // << works
$myClass->set(['tree', 'deciduous'], 'birch'); // << doesn't work
?>
What I get for my trouble is
$myClass->tree ... nothing.
I can't wrap my head around why this wouldn't work. What am I missing?
This feels like an anti-OOP pattern. I'm not saying it is wrong, but classes are generally used to represent static things. To deeply set something using your code, we're pretty much left to stdClass to represent the specific object to hold properties. So for instance, deciduous has to be an instance of stdClass because we want to give it a value, and also assign it to something. I think ideally an array would be used for this.
That said, this code should do what you are looking for. I wrapped it into a trait just to make it more portable across other classes, but that's not strictly necessary. It is close to your original code, at least in spirit, but I removed the property_exists check.
trait Setter {
public function set(array|string $k, $v) {
if(!is_array($k)){
$k = [$k];
}
$last = array_pop($k);
$obj = $this;
while($k){
$item = array_shift($k);
$obj->$item = new stdClass();
$obj = $obj->$item;
}
$obj->$last = $v;
}
}
class forest
{
use Setter;
private $tree;
private $climate;
}
$myClass = new forest();
$myClass->set('climate', 'temperate'); // << works
$myClass->set(['tree', 'deciduous'], 'birch'); // << doesn't work
var_dump($myClass);
This kicks out:
object(forest)#1 (2) {
["tree":"forest":private]=>
object(stdClass)#2 (1) {
["deciduous"]=>
string(5) "birch"
}
["climate":"forest":private]=>
string(9) "temperate"
}
You can see a demo of it here: https://3v4l.org/EFfa2#v8.0.14
Edit
Thinking about this more, it has a feeling of attempting to model infinite-depth hierarchical database content. At a certain point, you have to decide the value of having object represent things, or if we're really just "tagging" things. I could be wrong about your usage, too, but I just wanted to throw another way out there.
This version uses to classes, one for the forest and one for a tree. A tree is a very simple object that has a single property of name which would map to Silver Birch, for intsance.
The forest also has a single property, trees, that is an array of the possible types, with the last item being an instance of a tree.
class tree
{
public function __construct(public string $name){}
}
class forest
{
public array $trees = [];
public function addTree(string $name, string|array $types): void
{
if(!is_array($types)){
$types = [$types];
}
$last = &$this->trees;
foreach($types as $type) {
if(!array_key_exists($type, $last)){
$last[$type] = [];
}
$last = &$last[$type];
}
$last[] = new tree($name);
}
}
$myClass = new forest();
$myClass->addTree('silver', ['deciduous', 'birch']);
$myClass->addTree('river', ['deciduous', 'birch']);
var_dump($myClass);
Demo here: https://3v4l.org/LALFp#v8.0.14
There is unfortunately a problem with this:
$myClass->addTree('maple', ['deciduous']);
That actually works, however when evaluating the trees array you'd probably have to check the key to determine if it is an integer or a string to determine if this is a sub-type or a category. But that's getting into the weeds.

php: massive assignement

I want to do a massive assignement of my protected vars, i used this code:
protected $_productName = '';
protected $_price = 0;
protected $_categoyId = 0;
public function setAttributes($attributes)
{
foreach($attributes as $key => $val)
{
$var = '_' . $key;
$this->$var = $val;
}
}
$attributes = array('productName'=>'some Product', 'price' => 10, 'categoryId' => 5) for exapmle.
the code above works for me but I feel that it's not clean. Is there any better solution to do that?
thanx.
Code is clean, nothing bad about it. You could maybe additionally see if class field exists before setting it - so you could assure you are not setting any additional fields, which are not defined in a class.
Also to make the code a little shorter, you could do:
$this->{"_{$key}"} = $val;
This is matter of taste what you like - your variant is fine as well.
What you're doing is fine. I would add a check for the property:
foreach($attributes as $key => $val)
{
$var = '_' . $key;
if (property_exists($this, $var))
{
$this->$var = $val;
}
}
Your code is pretty much as a clean as it gets for mass-assignment. There are alternatives, like using array_walk() instead of a foreach loop, but I find loops to be cleaner and easier to understand quickly in situations like these.
it looks ok to me, but if $attributes is always an array probably you should add this to avoid errors
public function setAttributes($attributes=array())
doing that you won't receive a error if attributes is empty because initialize the $attributes array
You can use Magic Methods. Assign array as property value. And for every time you call variable like $this->var invoke __get method
public function setAttributes($attributes)
{
$this->attributes = $attributes;
}
function __get($var) {
if(isset($this->attributes[$var])) return $this->attributes[$var];
return false;
}
function __set($key, $var) {
}
I also used something like the following code awhile ago as a test:
private $data = array();
public function __get($name)
{
if (array_key_exists($name, $this->data))
{
return $this->data[$name];
}
}
public function __set($name, $value)
{
$this->data[trim($name)] = trim($value);
}

Multiple GetBetween calls

I'm making a script that gets multiple CSS files from a webpage and put them together using foreach() and include().
I already found the right function:
function GetBetween($content, $start, $end) {
$r = explode($start, $content);
if(isset($r[1])) {
$r = explode($end, $r[1]);
return $r[0];
}
return '';
}
Is there an alternative to this function to get all in an array?
How can I use this to get multiple strings in an array?
For example:
foreach($css = GetBetween($page, '<link rel="stylesheet" href="','"') {
include("$css");
}
Actually by returning an array from the function you could iterate over it. Example:
$cssFiles = GetBetween($page, '<link rel="stylesheet" href="','"');
foreach ($cssFiles as $cssFile)
{
include($cssFile);
}
However your GetBetween function does not return an array so far. Also you're trying to parse HTML here, you don't need to write a function to parse it, but instead you can make use of the existing HTML Parser in PHP:
/**
* All stylesheet files within a HTML source
*/
class StyleSheets extends DOMDocument implements IteratorAggregate
{
public function __construct($source)
{
parent::__construct();
$this->loadHTML($source);
}
public function getIterator()
{
static $array;
if (NULL === $array)
{
$xp = new DOMXPath($this);
$expression = '//head/link[#rel="stylesheet"]/#href';
$array = array();
foreach($xp->query($expression) as $node)
$array[] = $node->nodeValue;
}
return new ArrayIterator($array);
}
}
You can then easily use it:
foreach(new StyleSheets($page) as $index => $file)
{
printf("#%d: %s\n", $index, $file);
}
Demo / Example output:
#0: file1.css
#1: file2.css
#2: file3.css
Technically you could do something similar with a string search as you do with your GetBetween function making it return an array as well. However as it's HTML I suggest you use a HTML parser instead so it's more fail-safe.

How to access array values inside class object?

I have a array like this in a function:
$value = array("name"=>"test", "age"=>"00");
I made this $value as public inside the class abc.
Now in my other file, I want to access the values from this array, so I create an instance by:
$getValue = new <classname>;
$getValue->value..
I'm not sure how to proceed so that then I can access each element from that array.
You mentioned that $value is in a function, but is public. Can you post the function, or clarify whether you meant declaring or instantiating within a function?
If you're instantiating it that's perfectly fine, and you can use the array keys to index $value just like any other array:
$object = new classname;
$name = $object->value["name"];
$age = $object->value["age"];
// Or you can use foreach, getting both key and value
foreach ($object->value as $key => $value) {
echo $key . ": " . $value;
}
However, if you're talking about declaring public $value in a function then that's a syntax error.
Furthermore if you declare $value (within a function) without the public modifier then its scope is limited to that function and it cannot be public. The array will go out of scope at the end of the function and for all intents and purposes cease to exist.
If this part seems confusing I recommend reading up on visibility in PHP.
The same as you would normally use an array.
$getValue = new yourClass();
$getValue->value['name'];
Use code
foreach($getValue->value as $key=>$value)
<?php
interface Nameable {
public function getName($i);
public function setName($a,$name);
}
class Book implements Nameable {
private $name=array();
public function getName($i) {
return $this->name[$i];
}
public function setName($i, $name) {
return $this->name[$i] = $name;
}
}
$interfaces = class_implements('Book');
if (isset($interfaces['Nameable'])) {
$bk1 = new Book;
$books = array('bk1', 'bk2', 'bk3', 'bk4', 'bk5');
for ($i = 0; $i < count($books); $i++)
$bk1->setName($i, $books[$i]);
for ($i = 0; $i < count($books); $i++)
echo '// Book implements Nameable: ' . $bk1->getName($i) . nl();
}
?>

PHP equivalent to Python's yield operator

In Python (and others), you can incrementally process large volumes of data by using the 'yield' operator in a function. What would be the similar way to do so in PHP?
For example, lets say in Python, if I wanted to read a potentially very large file, I could work on each line one at a time like so (this example is contrived, as it is basically the same thing as 'for line in file_obj'):
def file_lines(fname):
f = open(fname)
for line in f:
yield line
f.close()
for line in file_lines('somefile'):
#process the line
What I'm doing right now (in PHP) is I'm using a private instance variable to keep track of state, and acting accordingly each time the function is called, but it seems like there must be a better way.
There is a rfc at https://wiki.php.net/rfc/generators adressing just that, which might be included in PHP 5.5.
In the mean time, check out this proof-of-concept of a poor mans "generator function" implemented in userland.
namespace Functional;
error_reporting(E_ALL|E_STRICT);
const BEFORE = 1;
const NEXT = 2;
const AFTER = 3;
const FORWARD = 4;
const YIELD = 5;
class Generator implements \Iterator {
private $funcs;
private $args;
private $key;
private $result;
public function __construct(array $funcs, array $args) {
$this->funcs = $funcs;
$this->args = $args;
}
public function rewind() {
$this->key = -1;
$this->result = call_user_func_array($this->funcs[BEFORE],
$this->args);
$this->next();
}
public function valid() {
return $this->result[YIELD] !== false;
}
public function current() {
return $this->result[YIELD];
}
public function key() {
return $this->key;
}
public function next() {
$this->result = call_user_func($this->funcs[NEXT],
$this->result[FORWARD]);
if ($this->result[YIELD] === false) {
call_user_func($this->funcs[AFTER], $this->result[FORWARD]);
}
++$this->key;
}
}
function generator($funcs, $args) {
return new Generator($funcs, $args);
}
/**
* A generator function that lazily yields each line in a file.
*/
function get_lines_from_file($file_name) {
$funcs = array(
BEFORE => function($file_name) { return array(FORWARD => fopen($file_name, 'r')); },
NEXT => function($fh) { return array(FORWARD => $fh, YIELD => fgets($fh)); },
AFTER => function($fh) { fclose($fh); },
);
return generator($funcs, array($file_name));
}
// Output content of this file with padded linenumbers.
foreach (get_lines_from_file(__FILE__) as $k => $v) {
echo str_pad($k, 8), $v;
}
echo "\n";
PHP has a direct equivalent called generators.
Old (pre php 5.5 answer):
Unfortunately, there isn't a language equivalent. The easiest way is to either to what you're already doing, or to create a object that uses instance variables to maintain state.
There is however a good option if you want to use the function in conjunction with the foreach-statement: SPL Iterators. They can be used to achieve something quite similar to python generators.
I prototype everything in Python before implementing in any other languages, including PHP. I ended up using callbacks to achieve what I would with the yield.
function doSomething($callback)
{
foreach ($something as $someOtherThing) {
// do some computations that generates $data
call_user_func($callback, $data);
}
}
function myCallback($input)
{
// save $input to DB
// log
// send through a webservice
// etc.
var_dump($input);
}
doSomething('myCallback');
This way each $data is passed to the callback function and you can do what you want.
Extending #Luiz's answer - another cool way is to use anonymous functions:
function iterator($n, $cb)
{
for($i=0; $i<$n; $i++) {
call_user_func($cb, $i);
}
}
$sum = 0;
iterator(10,
function($i) use (&$sum)
{
$sum += $i;
}
);
print $sum;
There may not be an equivalent operator, but the following code is equivalent in function and overhead:
function file_lines($file) {
static $fhandle;
if ( is_null($fhandle) ) {
$fhandle = fopen($file, 'r');
if ( $fhandle === false ) {
return false;
}
}
if ( ($line = fgets($fhandle))!== false ) {
return $line;
}
fclose($fhandle);
$fhandle = null;
}
while ( $line = file_lines('some_file') ) {
// ...
}
That looks about right. Sorry, I haven't tested it.
The same sentence 'yield' exists now on PHP 5.5:
http://php.net/manual/en/language.generators.syntax.php

Categories