First off, a border (bottom, top or otherwise) is not the same thing as an hr element. It's up to you wether to generate any element or not, so if you use hr, you have to deal with this yourself.
If you go with borders instead, you can have CSS deal with this for you, since you can use selectors to your advantage.
Either way, I believe you should look at it from a reverse position: You are generating either an hr element BEFORE all blocks but the first one (rather than hr elements after all but last), or all elements but the first one have a TOP border (rather than bottom border for all but last). There are two reasons for this. If you only have a first element, there should be no border/hr (it could be argued that this single element is also the last, but my point of view still makes sense codewise). Ther second reasons is the adjacent sibling selector if you use the border approach.
#top_news > li
{
background-color: red;
color: blue;
}
/* all but the first one also look like this (more specific rules take precedence over less specific).
So this rules replaces the above value for background-color, while the value for color is
retained from above since there is no replacement for it here.
And the thing you specifically wanted was the border-top for these elements */
#top_news > li + li
{
border-top: 1px solid black;
background-color: white;
}
</style>
</head>
<body style="margin: 30px;">
<?php
$news = array('One', 'Two', 'Three');
echo '<ul id="top_news">'.PHP_EOL;
foreach ($news as $n)
{
echo "\t<li>$n</li>\n";
}
echo '</ul>'.PHP_EOL;
Edit:
If you go with the hr approach, I'd do the same thing as the CSS does for you in the above example, i.e. treat the first element separately
$news = array('One', 'Two', 'Three');
echo '<ul id="top_news">'.PHP_EOL;
$first = array_shift($news);
echo "\t<li>$first</li>\n";
foreach ($news as $all_but_first)
{
echo "\t<hr />".PHP_EOL;
echo "\t<li>$all_but_first</li>\n";
}
echo '</ul>'.PHP_EOL;