I am trying to keep presentation and logic separate without using a template engine like Smarty. What I have so far is working, but I am not sure how to do certain things without putting more PHP into my presentation than I would like to. For example, right now I have something like this:
product_list.php
try {
$query = $conn->prepare("SELECT p.id, p.name, p.description, IFNULL(url, title) AS url, GROUP_CONCAT(c.category SEPARATOR ', ') AS category,
FROM products p
LEFT JOIN product_categories pc ON p.id = pc.product_id
LEFT JOIN categories c ON pc.category_id = c.id
WHERE p.active = 1
GROUP BY p.id");
$query->execute();
$result = $query->fetchAll(PDO::FETCH_ASSOC);
}
catch (PDOException $e) {
echo $e->getMessage();
}
include('templates/product_list_tpl.php');
product_list_tpl.php
<div class="card">
<div class="product-list with-header">
<div class="product-list-header center-align">
<h2><?= $header_title; ?></h2>
</div>
<?php foreach ($result as $row): ?>
<!-- Some Product Info -->
Category: <?= $row['category']; ?>
<?php endforeach; ?>
</div>
</div>
In the above example some products will have one category, some will have multiple. They display nicely in a comma separated list, but I would like to make the category names into links. I know I can do something like below, but it seems messy to me.
<div class="card">
<div class="product-list with-header">
<div class="product-list-header center-align">
<h2><?= $div_title; ?></h2>
</div>
<?php foreach ($result as $row): ?>
<?php $categories = explode(', ', $row['category']); ?>
<div class="product-list-item avatar">
<img src="img/product/<?= $row['id']; ?>.jpg" alt="<?= $row['title']; ?>" class="square">
<?= $row['title']; ?>
<p class="caption"><?= $row['caption']; ?></p>
<div class="item-bottom">
<span class="responsive"><?= $row['description']; ?></span>
<p>
Category:
<?php foreach ($categories as $key => $category): ?>
<?= $category; ?>
<?= (sizeof($categories) > 1 && $key == end($categories)) ? ', ' : ''; ?>
<?php endforeach; ?>
</p>
<p>
<span>Rating: <?= $row['rating']; ?></span>
<span class="right">Rated <?= $row['rated']; echo ($row['rated'] == 1) ? ' time' : ' times'; ?></span>
</p>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
Thank you in advance for any suggestions. Also, if anyone has any input on the general separation format I used on the example code I would love to hear it. I am just getting back into coding after an 8 year break.
EDIT: Added missing endforeach and improved indentation on third code block as per #Devon suggested in comments.
EDIT: I updated the third code block to include HTML I previously left out and added all PHP functionality needed to achieve the output I am looking for. It works, but IMO doing it this way removes the little separation I had. I now basically just have one file with my database call and another file with this mess. I feel like I am not going in the right direction for proper business logic / presentation logic separation.
Where am I going wrong?
Lengthy, complicated logic such as:
(sizeof($categories) > 1 && $key == end($categories)) ? ', ' : '';
shouldn't be in the view to bother a front-end developer. "What is this hideous line of code?
What does it do? Why didn't the back-end developer give me something easier to work with?" Part of the power of MVC is not only in separation of concerns, but
also keeping back-end and front-end developers' work separate.
Code like <?php foreach ($result as $row): ?> contains no indication as to what it's working with. The DIVs, Ps, and SPANs are out of control as well.
This is why I'm a fan of view helpers.
I'd suggest:
product_list_tpl.php
<div class="card">
<div class="product-list with-header">
<div class="product-list-header center-align">
<h2><?= $div_title; ?></h2>
</div>
<?= displayItems($items); ?>
</div>
</div>
The view helper used above:
function displayItems($items)
{
foreach ($items as $item)
{
$categories = explode(', ', $item['category']);
$id = $item['id'];
$title = $item['title'];
$url = $item['url'];
$caption = $item['caption'];
$description = $item['description'];
$rating = $item['rating'];
$rated = $item['rated'];
include('product_list_item_tpl.php');
}
}
product_list_item_tpl.php
<div class="product-list-item avatar">
<img src="img/product/<?= $id; ?>.jpg" alt="<?= $title; ?>" class="square">
<?= $title; ?>
<p class="caption"><?= $caption; ?></p>
<div class="item-bottom">
<span class="responsive"><?= $description; ?></span>
<p>
Category:
<?php displayCategories($categories); ?>
</p>
<p>
<span>Rating: <?= $rating; ?></span>
<span class="right">Rated <?= $rated; ?> <?= isPluarl($rated)?'times':'time'; ?></span>
</p>
</div>
</div>
The view helpers used above:
function isPlural($number)
{
return $number != 1;
}
function displayCategories($categories)
{
$last = end($categories);
$count = sizeof($categories);
foreach ($categories as $key => $category)
{
$cat = strtolower($category);
$isLast = $category == $last;
include('product_list_category_tpl.php');
}
}
product_list_category_tpl.php
<?= $category; ?>
<?= ($count > 1 && !$isLast) ? ', ' : ''; ?>
Note I inverted the $key == end($categories) part from what you were using before with !$isLast and swapped $key to $category. This logic still feels dirty as two categories could have the same name. Probably better to just use count($categories) in conjunction with $i++ to decide if it's the last loop.
Edit:
This works nicely and avoids the prior mentioned issue as it relies on the key rather than the value:
function displayCategories($categories)
{
end($categories);
$last = key($categories);
$count = sizeof($categories);
foreach ($categories as $key => $category)
{
$cat = strtolower($category);
$isLast = $key == $last;
include('product_list_category_tpl.php');
}
}
Related
I created a database with 5 categories and created a page to display the available categories that I pull from the database. The problem is that I have 4 divs that have the following class ( col-md-3 ) and one that is ( col-md-6 ).
Code for gathering the first 5 categories ( col-md-3 ):
<?php
$this->db->limit(4);
$categories = $this->db->get_where('category', array('parent' => 0))->result_array();
foreach ($categories as $key => $category):
?>
<div class="gallery-item">
<div class="grid-item-holder">
<div class="listing-item-grid">
<img src="<?php echo base_url('uploads/category_thumbnails/').$category['thumbnail'];?>" alt="" />
<div class="listing-counter">
<span>2</span>Locations
</div>
<div class="listing-item-cat">
<h3><?php echo $category['name']; ?></h3>
<p><?php echo $category['name']; ?></p>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
Code for gathering another 1 category ( col-md-6 ):
<?php
$this->db->limit(1);
$categories = $this->db->get_where('category', array('parent' => 0))->result_array();
foreach ($categories as $key => $category):
?>
<div class="gallery-item gallery-item-second">
<div class="grid-item-holder">
<div class="listing-item-grid">
<img src="<?php echo base_url('uploads/category_thumbnails/').$category['thumbnail'];?>" alt="" />
<div class="listing-counter">
<span>2</span>Locations
</div>
<div class="listing-item-cat">
<h3><?php echo $category['name']; ?></h3>
<p><?php echo $category['name']; ?></p>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
With this code, I get a display of categories but instead of displaying 5 categories, it repeats one of the displayed 4. How can I fix the code so that the display automatically continues to display the categories in all divs? If I forgot to write something, correct it or ask, I will update the question.
It looks like you are getting the first 4 items for the first foreach loop and then getting the first one again for the second loop. Would it make sense to get all 5 and process them in one single foreach loop only adding 'gallery-item-second' on the 5th item, something like ...
<?php
$this->db->limit(5);
$categories = $this->db->get_where('category', array('parent' => 0))->result_array();
$index = 0;
foreach ($categories as $key => $category):
$index++;
if ($index == 5) {
echo '<div class="gallery-item gallery-item-second">';
} else {
echo '<div class="gallery-item">';
}
?>
<div class="grid-item-holder">
. . .
</div>
</div>
<?php endforeach; ?>
Or if you must have the second loop then get 5 items and only show the 5th.
And to make sure you get the same 5 you could use a sort criteria when you get the items.
The problem is resolved with math:
I have added the next code:
...
$num = 5;
$i = 0;
foreach ($categories as $key => $category):
if ($i < ($num - 1)) {
// show div col-md-3
} else {
// show div col-md-6
}
endif;
I'd like to achieve on adding a comma between my authors in my foreach but each time I make any references, it adds a comma at the end also.
Here is what I have below:
<div class="four-sixths first">
<img class="mb-20" src="<?= $recipe->get_thumbnail() ?>" alt=""/>
<h2 class="mb-5"><?= $recipe->get_title() ?></h2>
<span><?= $recipe->get_date() ?></span> -
<?php foreach ($recipe->get_authors() as $profile): ?>
<?= $profile->get_name() ?>,
<?php endforeach; ?>
<p><?= $recipe->get_content() ?></p>
</div>
Which gives me the following results:
How can I prevent a comma at the end?
Since you were skeptical about implode, here's a simple example (just showing the loop):
<?php foreach ($recipe->get_authors() as $profile):
$links[] = '' . $profile->get_name() . '';
endforeach; ?>
<?= implode(', ', $links); ?>
An alternative using join:
<div class="four-sixths first">
<img class="mb-20" src="<?= $recipe->get_thumbnail() ?>" alt=""/>
<h2 class="mb-5"><?= $recipe->get_title() ?></h2>
<span><?= $recipe->get_date() ?></span> - <?= join(", ", array_map(function ($profile) { return sprintf('%s', $profile->get_url(), $profile->get_name()); }, $recipe->get_authors())) ?>
<p><?= $recipe->get_content() ?></p>
</div>
Wasn't able to test it but basically store results to $authors first, and in this way you can count it without recalling the method get_authors. While $cnt keeps track base 1 and can be compared directly on the length.
<?php
$authors = $recipe->get_authors();
$cnt = 0;
?>
<div class="four-sixths first">
<img class="mb-20" src="<?= $recipe->get_thumbnail() ?>" alt=""/>
<h2 class="mb-5"><?= $recipe->get_title() ?></h2>
<span><?= $recipe->get_date() ?></span> -
<?php foreach ($authors as $profile) { $cnt++; ?>
<?= $profile->get_name() ?><?php
if ($cnt < count($authors)) echo ",";
?>
<?php } ?>
<p><?= $recipe->get_content() ?></p>
</div>
Two things really.
I'm trying to limit the returned children, as an example 8 articles. I've tried array_slice and other techniques though just can't get anything to work.
I'd also like to return these children in ascending order based on my 'modified_date' column in my database.
Really appreciate any guidance on this, been working hard for nights, can't get anything to work :(
<?php
// get all pages
$pages = new TFW_Navigation($pageNav);
$pages = $pages->getNavigation();
foreach ($pages as $index => $topPage)
{
$children = $topPage->getChildren(null,true);
if (!empty($children)) {
foreach($children as $child) {
?>
<article class="<?=$child->ext_column_1 ?>">
<a href="<?= $child->url ?>" class='article-border'>
<img src="<?=$child->ext_column_2 ?>" alt="">
<header>
<h3>
<?= $child->ext_column_3 ?>
</h3>
</header>
<footer>
<p>
<?php echo date("d F Y", strtotime($child->last_modified))."<span class='sprite article-link'></span>" ?>
</p>
</footer>
</a>
</article>
<?php
}
}
}
?>
If more information is needed, please let me know.
Thanks, Barry
Add a break statement.
foreach($children as $childIndex => $child) {
if ($childIndex > 8) {
break;
}
...
}
To limit your array , using array_slice is a good solution
$firstEight = array_slice($children, 0, 8); // First 8 items
To return array in ascending order based on your modified_date column, you have to do that from sql query.
$sql = "SELECT * FROM table WHERE x = y ORDER BY last_modified ASC";
and also you can limit the results from sql .
$sql = "SELECT * FROM table WHERE x = y ORDER BY last_modified ASC LIMIT 0,8";
// Using
<?php
// get all pages
$pages = new TFW_Navigation($pageNav);
$pages = $pages->getNavigation();
foreach ($pages as $index => $topPage)
{
$children = $topPage->getChildren(null,true);
$firstEight = array_slice($children, 0, 8);
if (!empty($firstEight)) {
foreach($firstEight as $child) {
?>
<article class="<?=$child->ext_column_1 ?>">
<a href="<?= $child->url ?>" class='article-border'>
<img src="<?=$child->ext_column_2 ?>" alt="">
<header>
<h3>
<?= $child->ext_column_3 ?>
</h3>
</header>
<footer>
<p>
<?php echo date("d F Y", strtotime($child->last_modified))."<span class='sprite article-link'></span>" ?>
</p>
</footer>
</a>
</article>
<?php
}
}
}
?>
This is my footer. There is parade of categories with most articles in. I need to exclude here categories with description that starts with "XXX".
So If some categories have description that starts with "XXX", it may donĀ“t show here.
Is it possible please? Im newbie in PHP so I dont know if can I declare category discreption here.
<?php global $teo_options;?>
<footer role="contentinfo">
<?php if(isset($teo_options['enable_popular_companies']) && $teo_options['enable_popular_companies'] == 1) { ?>
<div class="stripe-regular">
<div class="row">
<div class="column">
<h2><?php _e('Name', 'Couponize');?></h2>
</div>
</div>
<div class="row collapse">
<div class="column">
<div class="popular-companies flexslider">
<ul class="rr slides">
<?php
$args['hide_empty'] = 1;
$args['orderby'] = 'count';
$args['order'] = 'desc';
if(isset($teo_options['blog_category']) && $teo_options['blog_category'] != '')
$args['exclude'] = implode(",", $teo_options['blog_category']);
$categories = get_categories($args);
foreach($categories as $category) {
$image = get_option('taxonomy_' . $category->cat_ID);
$image = $image['custom_term_meta'];
?>
<li>
<a href="<?php echo get_category_link( $category->term_id );?>" class="wrapper-5 small">
<img src="<?php echo aq_resize($image, 130, 130, true); ?>" alt="<?php echo $category->name;?> coupons">
</a>
</li>
<?php } ?>
</ul>
</div>
</div>
</div>
</div>
<?php } ?>
Everything is possible but you're just complicating your problem.
Why would you identify a category by a piece of text in the Description?
Also, searching in the description as it's text could end to be a slow and unnecessary query, if you have a lot of categories.
To solve it, I recommend you take a look at the documentation about Including & Excluding Categories.
What I would do is to make sub-categories and either hide them manually or do a trick between the child and parent categories.
I want two posts at each slide. but getting only one slide. I am new in programming, please help me.
$widget_id = $widget->id.'-'.uniqid();
$settings = $widget->settings;
$navigation = array();
$captions = array();
$i = 0;
?>
<div id="slideshow-<?php echo $widget_id; ?>" class="wk-slideshow wk-slideshow-revista-articles" data-widgetkit="slideshow" data-options='<?php echo json_encode($settings); ?>'>
<div>
<ul class="slides">
<?php foreach ($widget->items as $key => $item) : ?>
<?php
$navigation[] = '<li><span></span></li>';
$captions[] = '<li>'.(isset($item['caption']) ? $item['caption']:"").'</li>';
/* Lazy Loading */
$item["content"] = ($i==$settings['index']) ? $item["content"] : $this['image']->prepareLazyload($item["content"]);
?>
<li>
<article class="wk-content clearfix"><?php echo $item['content']; ?></article>
</li>
<?php $i=$i+1;?>
<?php endforeach; ?>
</ul>
<?php if ($settings['buttons']): ?><div class="next"></div><div class="prev"></div><?php endif; ?>
<?php echo ($settings['navigation'] && count($navigation)) ? '<ul class="nav">'.implode('', $navigation).'</ul>' : '';?>
<div class="caption"></div><ul class="captions"><?php echo implode('', $captions);?></ul>
</div>
</div>
http://i.stack.imgur.com/sy1ih.png
you're missing the { after the foreach and at the end of the loop.
<?php foreach ($widget->items as $key => $item) {
$navigation[] = '<li><span></span></li>';
$captions[] = '<li>'.(isset($item['caption']) ? $item['caption']:"").'</li>';
/* Lazy Loading */
$item["content"] = ($i==$settings['index']) ? $item["content"] : $this['image']->prepareLazyload($item["content"]);
?>
<li>
<article class="wk-content clearfix"><?php echo $item['content']; ?></article>
</li>
<?php
$i=$i+1;
}
?>
Your foreach syntax looks fine. That syntax is a lot easier for some people to read when embedded in HTML than the traditional braces.
Can I ask what the $this variable refers to? I don't see this instantiated anywhere in your code? Is it supposed to be $item instead?
$settings['index'] never changes within the loop, however $i does.
Change to: ($i<2)