Jump to content


Photo

Testing for the first element in a wirearray


  • Please log in to reply
10 replies to this topic

#1 everfreecreative

everfreecreative

    Sr. Member

  • Members
  • PipPipPipPip
  • 106 posts
  • 43

  • LocationCT, USA

Posted 12 July 2012 - 12:59 PM

I'm creating a news section on my homepage which outputs a list of article previews with an image, link, and summary for each. I have this working perfectly. However, I want to have the first article in the list have a larger image and some different classes for styling. Here is my code, which uses first() on the $newsChildrenArray array to test against the current item ($newsChildPage) in the foreach loop and create a larger image if they match up:

foreach($newsChildrenArray as $newsChildPage) {

  //Test for an image on the current page
  if($newsChildPage->images->eq(0) != null) {
   //If this is the first article, make a larger image
   if( $newsChildPage == $newsChildrenArray->first() ) {
	$newsImg = $newsChildPage->images->first()->size(157,151);
   }
   else {
	$newsImg = $newsChildPage->images->first()->size(77,71);
   }
  }
  
  echo '
   <a href="' . $newsChildPage->url . '">
	<li '; if($newsChildPage == $newsChildrenArray->last())echo 'class="last"'; echo '>';
  
	if($newsImg) {
	 echo '<img src="' . $newsImg->url . '" alt="' . $newsImg->description . '" />';
	}
  
	echo '
	 <p>
	  <span class="preview-title">' . $newsChildPage->title . '</span> &bull; <span class="preview-date">' . $newsChildPage->date . '</span>
	  <br />' . $newsChildPage->summary . '
	  &nbsp; <a href="' . $newsChildPage->url . '" class="read-more">Read More...</a>
	 </p>
	</li>
   </a>
  ';
}


Unfortunately, ProcessWire chokes on the test. It echos the first element in the array correctly and then spits out "Fatal error: Nesting level too deep - recursive dependency?"

Strangely, it has no problem with the similar test I perform later on using last() to output a class on the last array element.

I created a simple workaround by adding a counter to the foreach loop and testing for the first element in the loop, but I'm curious--for future reference and a better understanding of the API--why this would be failing. Is there a better way of doing this that would still utilize the PW API?

#2 SiNNuT

SiNNuT

    Sr. Member

  • Members
  • PipPipPipPip
  • 366 posts
  • 231

Posted 13 July 2012 - 06:18 AM

I'm not sure but couldn't this has got something to do with using non-strict comparison on objects and circular references?

http://www.richardlo...sive-dependency
http://stackoverflow...ular-references

#3 Soma

Soma

    Hero Member

  • Moderators
  • 3,183 posts
  • 1732

  • LocationSH, Switzerland

Posted 13 July 2012 - 06:43 AM

The problem lays in the

if( $newsChildPage == $newsChildrenArray->first() ) {

Comparing object shoudl be done using "==="

Not sure why the error, but with "===" it works.

However I cleaned up a little your code, using heredoc (<<<_END) which can be handy to generate output code so the indentation stays.


foreach( $newsChildrenArray as $newsChildPage ) {
	$newsImg = '';
	//Test for an image on the current page
	if( count($newsChildPage->images) ) {
		//If this is the first article, make a larger image
		if( $newsChildPage === $newsChildrenArray->first() ) {
			$newsImg = $newsChildPage->images->first()->size(157,151);
		} else {
			$newsImg = $newsChildPage->images->first()->size(77,71);
		}
	}
	$class = '';
	$imgstr = '';
	if( $newsChildPage === $newsChildrenArray->last() ) $class = ' class="last"';
	if( $newsImg ) {
		$imgstr = "<img src='{$newsImg->url}' alt='$newsImg->description'/>";
	}
	$out = <<<_END

<a href="{$newsChildPage->url}">
	<li$class>$imgstr
		<p>
			<span class="preview-title">{$newsChildPage->title}</span>
			&bull; <span class="preview-date">{$newsChildPage->date}</span><br />
			{$newsChildPage->summary}
			&nbsp; <a href="{$newsChildPage->url}" class="read-more">Read More...</a>
		</p>
	</li>
</a>

_END;
	echo $out;
}


Also changed some things to avoid further problems. Using ->eq(0) for example to check if there's an image is wrong. It won't work if there no image. Use count();

Also the check if($newsImg) will fail if there's no image as the variable won't exists. So you need to set it before.

@somartist | modules created | support me, flattr my work flattr.com


#4 Soma

Soma

    Hero Member

  • Moderators
  • 3,183 posts
  • 1732

  • LocationSH, Switzerland

Posted 13 July 2012 - 07:12 AM

Always forget about it but thought I'd mention it. You can also use

if( $newsChildPage->id == $newsChildrenArray->first()->id ) { ...


which is even better and should be faster although not sure how much really. :)

@somartist | modules created | support me, flattr my work flattr.com


#5 arjen

arjen

    Sr. Member

  • Members
  • PipPipPipPip
  • 333 posts
  • 124

  • LocationHoogeveen, The Netherlands

Posted 13 July 2012 - 07:29 AM

Another option I learned on the forums here is to use a do while loop if you want to display the first item differently than the following.

$newsItemList = $page->children;
$newsItemListCount = count($newsItemList);
$cnt = 0;
do {
$cnt++;
$newsItem = $newsItemList->shift();

if ($cnt == 3) {
  // do stuff
} else {
  // do stuff
}
} while(count($newsItemList));


work will always be the curse of the drinking classes...

#6 Soma

Soma

    Hero Member

  • Moderators
  • 3,183 posts
  • 1732

  • LocationSH, Switzerland

Posted 13 July 2012 - 07:43 AM

Thanks arjen for posting this. Well it has nothing really to do with the while loop it's just another variant. It can be done with a foreach or for loop

$newsItemList = $page->children;

$cnt = 0;
foreach($newsItemList as $news) {
  $cnt++;
  if ($cnt == 1) {
  // do stuff
  } else {
  // do stuff
  }
}


@somartist | modules created | support me, flattr my work flattr.com


#7 everfreecreative

everfreecreative

    Sr. Member

  • Members
  • PipPipPipPip
  • 106 posts
  • 43

  • LocationCT, USA

Posted 13 July 2012 - 09:52 AM

Thanks a lot Soma. Using a counter like Arjen suggested is the workaround I am currently using, but I think I will use your code because I like it better :)

Also the check if($newsImg) will fail if there's no image as the variable won't exists. So you need to set it before.


Well, I want to check whether it exists and do nothing if it doesn't. I suppose I should use isset()?

#8 ryan

ryan

    Hero Member

  • Administrators
  • 5,753 posts
  • 3102

  • LocationAtlanta, GA

Posted 13 July 2012 - 12:22 PM

I think it might be a little better to use a counter just because that is native PHP with no function call (i.e. always faster/more efficient). And you should be able to get that counter right out of the foreach().

foreach($newsChildrenArray as $cnt => $newsChildPage) {
  $newsImg = $newsChildPage->images->first();
  if($cnt == 0 && $newsImg) {
    $newsImg = $newsImg->size(157,151); // big
  } else if($newsImg) {
    $newsImg = $newsImg->size(77,71); // small
  }
  // the rest…
}


#9 everfreecreative

everfreecreative

    Sr. Member

  • Members
  • PipPipPipPip
  • 106 posts
  • 43

  • LocationCT, USA

Posted 13 July 2012 - 03:22 PM

I ended up using:

if( $newsChildPage->id === $newsChildrenArray->first()->id )

to keep thing consistant since I am also using last(), which can't be found (I don't think?) with the counter method.

Thanks for demonstrating how to use the counter with foreach, Ryan. Didn't know about that one!


If anyone is interested in using something like this on their own project, here is my updated code, which includes the logic for removing articles that we don't want in our list (making use of a custom checkbox on news article pages). Instead of using the heredoc syntax, I tried to go with more separation of the markup and php. Not sure if I succeeded or not :)

<ul class="news-previews">
<?php
//Create an array from the children of the news page
$newsPage = $pages->get("/news/");
$newsChildrenArray = $newsPage->children;

//Remove articles we don't want published on the homepage from the array
foreach($newsChildrenArray as $newsChildPage) {
  if($newsChildPage->publishOnHomepage == false) {
   $newsChildrenArray = $newsChildrenArray->remove($newsChildPage);
  }
}

//Loop through the modified array and output the articles
foreach($newsChildrenArray as $newsChildPage) {
 
  //Grab the first image in the article, if one exists
  $newsImg = '';
  if( count($newsChildPage->images) ) {
   //If this is the first article, make a larger image
   if($newsChildPage->id === $newsChildrenArray->first()->id) {
    $newsImg = $newsChildPage->images->first()->size(150,150);
   }
   else {
    $newsImg = $newsChildPage->images->first()->size(57,51);
   }
  }
 
  //Set a class if this is the first or last item
  $class = '';
  if( $newsChildPage->id === $newsChildrenArray->first()->id ) $class = 'class="first"';
  if( $newsChildPage->id === $newsChildrenArray->last()->id ) $class = 'class="last"';
 
  //Markup time
  ?>
 
  <a href="<?php echo $newsChildPage->url ?>">
   <li <?php echo $class ?>>
    <?php if($newsImg)echo '<img src="'.$newsImg->url.'" alt="'.$newsImg->description.'"/>'; ?>
    <p>
	 <h4 class="preview-title">
	  <?php echo $newsChildPage->title ?> &bull; <span class="preview-date"><?php echo $newsChildPage->date ?></span>
	 </h4>
	 <?php echo $newsChildPage->summary ?>
	 <a href="<?php echo $newsChildPage->url ?>" class="read-more">Read More...</a>
    </p>
   </li>
  </a>
 
<?php
}
?>
</ul>


#10 Soma

Soma

    Hero Member

  • Moderators
  • 3,183 posts
  • 1732

  • LocationSH, Switzerland

Posted 13 July 2012 - 03:37 PM

I think in this case correct would be == not === but both will work. There's some coverage of it here http://www.php.net/m...-comparison.php

There seem to be a case when testing with "==" when "===" should be used to indentify objects can result in a overhead that could be avoided.

Not sure what really is the better option id==id or object===object. Maybe someone else can tell better.

@somartist | modules created | support me, flattr my work flattr.com


#11 ryan

ryan

    Hero Member

  • Administrators
  • 5,753 posts
  • 3102

  • LocationAtlanta, GA

Posted 16 July 2012 - 09:10 AM

Not sure what really is the better option id==id or object===object. Maybe someone else can tell better.


I don't think it matters much here. The object===object is probably about the same thing as an id==id integer comparison, when it gets down into the low level code that it's written in. That's because object===object is comparing that two objects are the same instance, which translates to: do they occupy the same place in memory? (1234567==1234567)

But there is a good reason to use the id==id over object===object in ProcessWire:

$about1 = $pages->get('/about/');
$about2 = $pages->get('/about/');
echo $about1 === $about2 ? "Same Instance " : "Different Instance ";

This should output "Same Instance". But then try this:

$about1 = $pages->get('/about/');
$about1->set('title', 'About Test')->save(); // can be any page that is changed then saved
$about2 = $pages->get('/about/');
echo $about1 === $about2 ? "Same Instance " : "Different Instance ";

This should output "Difference Instance". Why? The entire memory cache is cleared when you save a page where one or more fields changed, or if you delete a page. It doesn't matter if it was $about1, or some other page, the memory cache is wiped. Since the code above kept it's own copy of the /about/ page before it was saved, there are now two copies of /about/ in memory: your copy and ProcessWire's copy.

For this reason, you may prefer to compare the 'id' property if the code you are working with is manipulating any pages or interacting with modules that do. But for most front-end situations, it doesn't matter.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users