Zend Framework - Helper class in need ? - Mega Doubt Post

The issue:

I have to list several elements that are coming from a database source A and they are:
team_id, team_name and team_score (translated for explanation sake).

I need to loop over them, and display that information.

So, I have, on the DAO side:


    public function listOfTeams()
    {
      $select = $this->select()
        ->from(array('t'=>'teams'), array('cod_team','name','score'));
      return $this->fetchAll($select);
    
    }

On my team controller:


    public function listAction()
    {
      $teamsDao = new TeamsDao();
      $this->view->infoTeam = $teamsDao->listOfTeams();                    
    }


And at the view:


    <?php for($i = 0; $i < 30; $i++): ?>
    
      <?php if(isset($this->infoTeam[$i])): ?>

Now, the thing is, on each of those listed items, I need to add more information.

That information doesn’t come directly from a database, but it’s a result of some calculations.

Take for example the percentage of games done.

 $totalGamesNumber > 0 ? ($gamesDone*100)/$totalGamesNumber : 0);

So, I need to grab the total games number from the database, then, for each team, I need to have the number of games done, so that I can have the percentage to be displayed.

The $gamesDone are obtained by:

 $gameTeamDao->countGamesPerTeam($gameVo, $teamVo);

The doubts:

I’m stuck here, because I cannot see where/how should I call and make the method for calculating the percentage, in order to allow the percentage of games completed to be presented along with the other data.

If you had to write an helper on this circumstances, how would you do it?
I’ve heart about “helpers” but I don’t understand how do they fit the case here.

If your read it all, I want to thank you already, :s
Márcio

In my opinion the calculations seem to be part of the domain model so the DAO is the best place for them.

(pseudo code)


public function listOfTeams() {

	$select = $this->select()
	->from(array('t'=>'teams'), array('cod_team','name','score'));

	$rows = $this->fetchAll($select);

	foreach($rows as &$row) {
		$row['percentage_of_games_done'] = $row['totalGamesNumber'] > 0 ? ($row['gamesDone']*100)/$row['totalGamesNumber'] : 0);
	}
	
	return $rows;

} 

There isn’t anything wrong with adding the calculations after the data has been retrieved from the db. If anything this is one of major advantages of separating business logic from application logic. The model data may be derived from any data source, including calculations applied after the data has been fetched from the db.

Alternatively though you could add a flag to mixin the data or place in a separate method. Though, I would only recommend doing this if the process to derive the data is resource intensive. Otherwise, using a consistent interface will avoid problems down the line.

(pseudo code) - separate model methods


public function listOfTeams() {

	$select = $this->select()
	->from(array('t'=>'teams'), array('cod_team','name','score'));

	return $this->fetchAll($select);

} 

public function listOfTeamWithExtraData() {

       $rows = $this->listOfTeams();

	foreach($rows as &$row) {
		$row['percentage_of_games_done'] = $row['totalGamesNumber'] > 0 ? ($row['gamesDone']*100)/$row['totalGamesNumber'] : 0);
	}
	
	return $rows;
}

(pseudo code) - flag


public function listOfTeams($mixin=false) {

	$select = $this->select()
	->from(array('t'=>'teams'), array('cod_team','name','score'));

	$rows = $this->fetchAll($select);
     

        if($mixin === true) {
	     foreach($rows as &$row) {
		$row['percentage_of_games_done'] = $row['totalGamesNumber'] > 0 ? ($row['gamesDone']*100)/$row['totalGamesNumber'] : 0);
	     }
        }
	
	return $rows;

} 

Disclaimer: I’m not familiar with how CI returns rows, so the actual implementation may change slightly, though the concept will be consistent.

Offtopic:
I do agree with you. The only reason that I can see for doing those calculations on the controller instead of the model is because, that information (let’s say: a number), can then be converted into: “a percentage” - “a base for some other calculations” etc… so we have like a raw number and, with that, a multitude of possible variations that each specific controller can manipulate.

First, my last assumption could be wrong, since I do lack experience on this matters.
Second, in this case however, and as far as I can see, that model calculation will serve only that percentage propose.

Anyway:
The thing is that
$row[‘gamesDone’] is, in fact: $row[‘gamesDoneOnSpecificConditions’]

And that value, is a value that needs to be retrieved from a query COUNT with a lot of joins that, at the end will return the desired number.

And my question is, having query A based on a given “teamId” and another query b that will return the “gamesDoneOnSpecificConditions” based on the same “teamId” - how can I put them together on a view, so that they I can properly list them as a group.

Note: I’m on ZF, not CI.

Thanks again,
Márcio

Well you could either add the logic to the initial query to reduce the number of hits to the db or create separate methods that return that aggregate data, perhaps adding some type of cache for deriving the calculation – depending how frequently the calculation changes per team.

(pseudo code)


public function listOfTeams() {

	$select = $this->select()
	->from(array('t'=>'teams'), array('cod_team','name','score'));

	$rows = $this->fetchAll($select);

	foreach($rows as &$row) {
	
		$total = $this->_getTeamsTotalGames($row['teamid']);
		$done = $this->_getTeamsGamesDone($row['teamid']);
		
		$row['percentage_of_games_done'] = $total > 0 ? ($done*100)/$total : 0);
	}
	
	return $rows;

} 

private function _getTeamsTotalGames($teamid) {
   /* ... */
}

private function _getTeamsGamesDone($teamid) {
   /* ... */
}

The return values of the two private methods could be cached in some form so that the aggregate query only needs to be issued once per request, session, etc – depending on how often they change.

The other way is to modify the initial query your building in CI Query Builder to provide the aggregate data all in a single query, with the initial data.

You could do the same as what oddz mentioned and perform the calculations on the row array before passing to the view or use a view helper which would wrap access to a model method such as below



//from the view

<?=$this->percentageHelper($this->infoTeam[$i]);?>


and the helper code


class Zend_View_Helper_PercentageHelper{

	public function percentageHelper($rowArray){
		$teamsDao = new TeamsDao();
		$number = $teamsDao->doSomeCalcs();
		return $number;
	}
}


perhaps looking at caching the method call if its going to slow down your results page if called for every row.

btw, you may want to look into zend_paginator rather than $i<30 etc loops as it handles that sort of stuff for you.

I will give it a try.

True. For all 30 records, I would have to do that calculation and, as you say, it could be very database intensive when retrieving the results.

But that $this->percentageHelper($this->infoTeam[$i] absolutly was what I was looking for, ignoring the performance hit that you have just pointed out.

In this case, I will paginate nothing. I have 30 records, and it’s all I can get. :slight_smile: But thanks for the addon.

SO:
I will give it a try on using private methods.

Thanks a lot really,
Márcio

@Oddz or anyone else that could explain it:

1)
Why the & here:

foreach($rows as &$row) {

?

Thanks.

That would be what I meant by the implementation may change. That is an implementation based on the rows being arrays, rather than objects that pass by reference by default in PHP5.


2)

_getTeamsTotalGames($row['teamid']);

Is, in fact:

getTotalGames(); 

On GamesDao Class.

Should I instantiate GamesDao or, should I call it statically ON listOfTeams() or, neither of those ?

Is the other one a method already available somewhere also?

If so I might change my recommendation to either mixing it in at the controller or just calling it in the view. The most politically correct would be passing the model to the view and calling it from there. It depends though. I generally try to avoid model dependencies on other models, that can become out of hand quickly if not managed well imo. I tend to keep models as separate, isolated entities independent of all other models, generally, unless unavoidable.

Just out of curiosity what are the queries to fetch the total games and games done?

Do you mean, if I’m calling that method somewhere else?
Not at present I believe. But it could probably be used somewhere else yes.

I understand the reasons for that change of recommendation.

BUT:
I’ve almost finish your initial recomendation implementation here, so for now, I will stay with that.

I don’t understand the & meaning. But I must mark it for read later on.
For now, I see that: If I remove the & sign, the $row with the percentage is NOT incremented.

If I keep it, the $row IS incremented.

If it’s for helping me out, then, worry not, I believe I’m getting your example implementation (despite my last doubt);

If it’s because you are curious about something else, then, I must appologize for not translating this last one. it’s 01:50 AM here and I have to finish this until tomorrow morning, or should I say, some hours.

However, and if you wish, I can translate them for the sake of a better compreention, later tomorrow - it’s the very least I can do.

So, the total games is a very simple one:

/**
* @desc Counts the total number of games existing;
* @return <string>
 */
 public function totalGames(){

     $select = $this->select()->from(array("g"=>"games"),array('COUNT(*) AS totalGames'));

       $row = $this->fetchRow($select);
            
        return $row->totalGames;
  }

And the other one is:

 private function _contaDesafiosDistintosCompletos($codEquipa)
        {
            $select = $this->getAdapter()->select();
            $select->from(array("e"=>"equipas"),array('COUNT(DISTINCT d.cod_desafio) AS numeroDesafiosCompletosSemRepeticao'));
            $select->join(array("de"=>"desafiosporequipa"),"e.cod_equipa = de.cod_equipaFk");
            $select->join(array("d"=>"desafios"),"d.cod_desafio = de.cod_desafioFk");
            $select->where("de.estado = 1");
            $select->where("e.cod_equipa = ".$codEquipa);

            $row = $this->getAdapter()->fetchRow($select);

            //o $row é array e não objecto. Por influência do getAdapter?
            return $row['numeroDesafiosCompletosSemRepeticao'];
        }

Done it.

:slight_smile:

Your implementation suggestion worked like a charm.

Cheers,
Márcio

Ps- If you are curious about anything else, or/and you wish me to translate that method, please, just let me know.

I got a little preoccupied with something else. Good to see you were able to translate the chicken scratch to something useful and get it working.

Absolutely NO.
The DAO (Data Access Object) is interchangeable - would you really like to re-implement the same calculations with every DAO?
Calculations are Business Logic and thus should reside in the model itself.
A very common approach is to use a mapper which asks the DAO for data, creates an instance of the model and passes that back, e.g. to the controller.
I would stick with that as you implement the Business Logic (here: calculation of games per team) at exactly one point, no matter how many different DAOs (DB, XML, Flatfile, WebService etc.) you implement, all share the same model.
And as the mappers harmonize the data (to fit the model), you are able to rely on your models and - that’s one of the main points - the DAO really gets interchangeable.

A mapper isn’t being used here, therefore that is not a practical solution. The DAO is still interchangeable. The only different is that each method has it own return value based on the business needs at hand. The only real difference between the solution oikram is using is that it lacks a concrete mapping entity. In my opinion that is a better approach because the entire system becomes more flexible and every method isn’t bound to some mapper entity interface. Mappers have their place but oikram isn’t using a mapper and there really isn’t a need for one. Blindly following a mapper religion and enforcing it on others at every wimp is not something I believe in. Especially for a situation that doesn’t really warrant it.