Hello, I'm trying to develop my project so that layers can be separated.

One thing I'm trying to do is to populate Unordered Lists.

For example:

<ul>Fruit</ul>
<li>Apples</li>
<li>Oranges</li>
</ul>
<ul>Vegetables</ul>
<li>Corn</li>
<li>Spinach</li>
</ul>

I want to do this from the database and I think the best methodology would be to use XSLT. However, I'm not 100% certain.

My table layouts for this sort of example would look something like the following:

FoodTypes

FoodTypeID (auto_increment PK)
FoodTypeDescription

Foods

FoodID (auto_increment PK)
FoodDescription
FoodTypeID (FK)

My select statement would be like the following:

select ft.FoodTypeDescription, f.FoodID, f.FoodDescription
from Foods f
join FoodTypes ft
on f.FoodTypeID = ft.FoodTypeID

My output therefore would look something like the following:

Fruit, 1, Oranges
Vegetables, 2, Corn
Fruit, 3, Apples
Vegetables, 4, Spinach

How would I go about taking the data from my SQL query and transforming it into these unordered lists? I want to do this without having to use an Order By clause in my SQL statement so that I separate the data and presentation layers.

I found this example in ASP.NET, but I'm looking for the PHP equivalent or whatever the best solution is: http://forums.asp.net/t/1104154.aspx

    First off, I actually believe that you'd be mixing logic and presentation layers if you go down the XSLT road to group data by categories.
    Data storage and retrieval is separate, and since what you want to achieve has a direct relation to the meaning of this data, I'd say you should indeed do the grouping at this level: i.e. with a group by clause in the query, or in the class managing the ORM mapping.
    An alternative view of things would be that the category data carries different meaning depending on how you are currently using the system, and as such it would be viable to do (or not to do) the grouping in your business logic. Still, if you do deal with it here, you might want to ask your data layer to handle the grouping for you anyway.
    But, using the presentation layer to perform this grouping would be bad practice in my opinion.

    Apart from that, I seriously doubt that achieving what you want through XSLT will be more efficient than letting the DB handle it for you. As such, the only performance reason you'd have to move from handling it in the DB would be if your DB is under so much stress that it can't do this properly. If your other server(s) have less stress, you could create a grouped array on the fly by iterating over the DB result resource, and it would be simple to do this.
    Moving to the XSLT approach would let you not deal with it at the server level at all, if you instead rely on client side transformation, e.g. in a browser, which makes this the only valid point as I see it. But it comes with more issues.

    I believe that most or all modern browsers do have XSLT support, but as far as I know, support is rather sketchy, and is also limited to XSLT 1.0, not 2.0. Without 2.0, you have no support for for-each-group which would probably make it easier.

    Without this, one way would be to process the document once per existing category, and either create an XSLT document that contains all possible categories, wether they were in your query or not. Or you could issue an additional query (or run through the result set) to see what categoriese were in this query, and produce your XSLT on the fly as well.

    As far as XSLT goes, it is identical no matter what processor you use. How you achieve the transformation depends on the processor used. As a crap example to achieve what you want by displaying data.xml in firefox

    <?xml version="1.0" encoding="UTF-8"?>
    <?xml-stylesheet type="text/xsl" href="/tohtml_bygroup.xsl"?>
    <items>
    	<item category="fruit">
    		<id>1</id>
    		<name>Oranges</name>
    		<category>fruit</category>
    	</item>
    	<item category="vegetable">
    		<id>2</id>
    		<name>Corn</name>
    		<category>vegetable</category>
    	</item>
    	<item category="fruit">
    		<id>3</id>
    		<name>Apples</name>
    		<category>fruit</category>
    	</item>
    	<item category="vegetable">
    		<id>4</id>
    		<name>Spinach</name>
    		<category>vegetable</category>
    	</item>
    </items>

    Which tells firefox to transform it by using tohtml_bygroup.xsl

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="items">
    	<html>
    	<head><title>Transformed</title></head>
    	<body>
    		<h3>Fruit</h3>
    		<xsl:for-each select="item[category='fruit']">
    			<div>
    				<xsl:value-of select="name"/>
    			</div>
    		</xsl:for-each>
    		<h3>Vegetable</h3>
    		<xsl:for-each select="item[category='vegetable']">
    			<div>
    				<xsl:value-of select="name"/>
    			</div>
    		</xsl:for-each>
    	</body>
    	</html>
    </xsl:template>
    </xsl:stylesheet>
    

    Perhaps there's a smarter way than this, but I'm not an algorithm genius, not was I ever very good with functional languages, and I know little XSLT as well.

    But, back to my initial argument. You'd be better off generating a proper data structure to begin with, resulting in an XML that makes sense for your use case, i.e.

    <?xml version="1.0" encoding="UTF-8"?>
    <categories>
    	<category>
    		<name>fruit</name>
    		<items>
    			<item>
    				<id>1</id>
    				<name>Oranges</name>
    			</item>
    			<item>
    				<id>3</id>
    				<name>Apple</name>
    			</item>
    		</items>
    	</category>
    	<category>
    		<name>vegetable</name>
    		<items>
    			<item>
    				<id>2</id>
    				<name>Corn</name>
    			</item>
    			<item>
    				<id>4</id>
    				<name>Spinach</name>
    			</item>
    		</items>
    	</category>
    </categories>
    

    You can then use XSLT to produce whatever presentation you wish: plain text, html document, different layouts for different resolutions or print vs screen (which can also be achieved through CSS of course). To produce your unordered list of categories containing unodered lists of products, you'd only need to process the document once, and it might look like this

    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="categories">
    	<html>
    	<head><title>Transformed</title></head>
    	<body>
    		<xsl:for-each select="category">
    			<h3><xsl:value-of select="name"/></h3>
    			<ul>
    				<xsl:for-each select="items/item">
    					<li>
    						<xsl:value-of select="name"/>
    					</li>
    				</xsl:for-each>
    			</ul>
    		</xsl:for-each>
    	</body>
    	</html>
    </xsl:template>
    </xsl:stylesheet>
    

      Thank you for your reply.

      I'm not sold on using XSLT as a solution and I'm open to other opportunities. I'm not looking to gain performance efficiency as much as I'd like to gain code maintainability.

      In my past processes I'd take a record set like the one up above, use an Order By in the SQL to sort the data on the categories. Like...

      select ft.FoodTypeDescription, f.FoodID, f.FoodDescription
      from Foods f
      join FoodTypes ft
      on f.FoodTypeID = ft.FoodTypeID
      order by ft.FoodTypeDescription,f.FoodDescription
      

      Then in my PHP code I'd have some logic as I fetch rows to check the previous value of the FoodType to the current one. If it's different, then the group has changed. I realized that this was not a great way of doing things. Processing efficient? Yes, but not very maintainable.

      I do like your idea of creating a GroupedBy array while iterating over the data. I'm having a bit of a time trying to picture in my head exactly how that works, though I think I get the gist of it. I assume I would use a multi-dimensional array and assign values to it from as I read rows from the database. If I need to sort the data then I could use array_multisort. Of course this would add a bit of performance overhead and that maybe is my trade-off for trying to separate these layers.

      What do you think would be the best solution?

        KrelianXgs;10960719 wrote:

        I assume I would use a multi-dimensional array and assign values to it from as I read rows from the database. If I need to sort the data then I could use array_multisort. Of course this would add a bit of performance overhead and that maybe is my trade-off for trying to separate these layers.

        What do you think would be the best solution?

        I agree that the approach of checking previous category while doing your output is not optimal from a design point of view.
        As I see it, the simplest way to get your items divided into categories is by simply issuing your query as previously (although you can now drop sorting by category) iterate over the result set and use category as top level array key

        $prod = array();
        while ($row = ...) {
        	$prod[$row['category'][] = $row;
        }
        

        The cost for this approach is that you will have to go over the data set twice: once to create it, once to output it.
        Assuming you want alphabetical ordering, so that Fruits are displayed before Vegetables, simply ksort($prod). With N items from M categories, N will be >= M, and also most likely a lot larger than M. As such, sorting M array elements with ksort will be more efficient than sorting N rows in the DB.

        But, if you are using pagination, you would need to stick to the order by clause since you'd also want a limit caluse in your query. Without the ordering, you'd have to pull out all data from the db, put it into the array, sort it, and then use only part of it.

        Either way, I think it's a good idea to create a base class that has generic ORM functionality for handling 1-to-many relations from the db and extend this with a ProductList class that deals with the particulars.
        And perhaps you'd want to create a function that generates lists based off arrays, or even create a class for it that lets you instantiate with an array containing data and implements functions for create (returns html) and render (direct output), and if you have other needs than html output, such as pdf or plain text, it can be dealt with in the same place.

          Write a Reply...