Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 35 additions & 3 deletions SOLUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,43 @@ SOLUTION

Estimation
----------
Estimated: n hours
Estimated: 5 hours

Spent: x hours
Spent: 3.5 hours


Solution
--------
Comments on your solution
I'm not really familiar with Symfony framework so I'm making a lot of assumptions regarding the implementation process. I'm sure there are a lot out-of-the-box functionalities inside the Symfony framework and I'm also sure that this simple task could be done in x-y ways.
But I'm a quick learner and I always try to follow good coding practices (the general and best practices by each framework).
After reading the Symfony Docs I'm prety sure that I should create a Service class for my ReportyViewCountByProfile class and not just a vanilla PHP class as I did. The idea is that my service class would be responsible for everything ; database ; queries ; input arguments, DI, etc. Current solution is not optimal and two classes are mixing responsibilities - which is not OK. The front-end class (ReportYearlyCommand) should only delegate execution to a internal Service class (ReportyViewCountByProfile) and this class should prepare the environment (db, query, ...) and then just return the results to the calling class which is still going to display the results.
Of course each report has to have it's own class with this strategy we avoid sphageti code and if we seperate our code there is a lot more
opportunity for code reuse. Me not using ORM was a decision by-design because I don't think that ORM is a good fit for reports&analytics - for performance reasons and query maintenance reasons the plan-sql is the right way to go. ORM is a good fit for basic CRUD actions.

In my opinion, a more suitable (production ready) solution would be to build a RESTFull API for fetching report data. The service would accept input arguments and returned a structured JSON response object. But given that this is not a production code and you don't need to spend time on the presentational part of the app (view) it is a good fit. With API calls we can more easly incorporate user authentication strategies, auditing, scaling, ... .


Test cases :
1. Running bin/console report:profiles:yearly '2016'
Feature : Get a yearly report on views per profile
Scanario : user runs the command and specifies input arguments (year)
Result : The system returns the date for a given year
Output :

2. Running bin/console report:profiles:yearly '2016'
Feature : Get a yearly report on views per profile
Scanario : user runs the command and does not specifies input arguments (year)
Result : The system returns a message to the user explaining that the function has a mandatory argument year
Output : Not enough arguments (missing: "year").

3. Running bin/console report:profiles:yearly '2016'
Feature : Get a yearly report on views per profile
Scanario : user runs the command and an QUERY EXCEPTION ocurres stoping the execution.
Result : The system returns a message to the user explaining that there is a problem with the query passed to the report
Output : EXCEPTION : Exception ocurred while executing the query :

4. Running bin/console report:profiles:yearly '2016'
Feature : Get a yearly report on views per profile
Scanario : user runs the command and an unknown program error ocurres stoping the execution.
Result : The system returns a message to the user explaining that there is a problem with the query passed to the report
Output : Something unexpected happened!! OH-My-OH-My. : You have requested a non-existent service "database_connectionww".
44 changes: 36 additions & 8 deletions src/Command/ReportYearlyCommand.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?php
namespace BOF\Command;

use BOF\Reports\ReportyViewCountByProfile;
use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Config\Definition\Exception\Exception;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

Expand All @@ -14,19 +16,45 @@ protected function configure()
$this
->setName('report:profiles:yearly')
->setDescription('Page views report')
;
->addArgument('year', InputArgument::REQUIRED, 'Report year');

}

protected function execute(InputInterface $input, OutputInterface $output)
{
/** @var $db Connection */
$io = new SymfonyStyle($input,$output);
$db = $this->getContainer()->get('database_connection');
try {
/** @var $db Connection */
$io = new SymfonyStyle($input, $output);

$db = $this->getContainer()->get('database_connection');
$year_argument = $input->getArgument('year');

$output->write('Generating report for year : ' . $year_argument . PHP_EOL);

$report = new ReportyViewCountByProfile();

try {
$profiles = $db->query($report->getByYear($year_argument))->fetchAll();
} catch (\Doctrine\DBAL\Exception\SyntaxErrorException $ex) {
$output->write('EXCEPTION : Exception ocurred while executing the query : ' . PHP_EOL . 'QUERY : ' . $report->getByYear($year_argument));
return 0;
};

$profiles = $db->query('SELECT profile_name FROM profiles')->fetchAll();
//We fill a dummy row that represents no data row
if (sizeof($profiles) === 0) {
$profiles = array_fill(0, 1, array_fill(0, 13, 'N/A'));
}

// Show data in a table - headers, data
$io->table(['Profile'], $profiles);
// Show data in a table - headers, data
$io->table(['Profile ' . str_replace("'", "", $year_argument),
'Sum', 'Jan', 'Feb',
'Mar', 'Apr', 'May',
'Jun', 'Jul', 'Avg',
'Sep', 'Oct', 'Nov', 'Dec'],
$profiles);

} catch (\Exception $ex) {
$output->write("Something unexpected happened!! OH-My-OH-My. : " . $ex->getMessage());
};
}
}
34 changes: 34 additions & 0 deletions src/Reports/ReportyViewCountByProfile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
namespace BOF\Reports;

class ReportyViewCountByProfile
{

public function __construct()
{
}

public function getByYear($arg_year)
{
$profiles = ("SELECT pr.profile_name, FORMAT(SUM(vi.views),0) sum ,
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '1' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '2' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '3' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '4' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '5' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '6' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '7' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '8' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '9' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '10' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '11' THEN (vi.views) ELSE 0 END), 0),
FORMAT(SUM(CASE WHEN MONTH(vi.date) = '12' THEN (vi.views) ELSE 0 END), 0)
FROM profiles pr
LEFT JOIN views vi on pr.profile_id = vi.profile_id
WHERE year(vi.date) = $arg_year
GROUP BY vi.profile_id, year(vi.date)
ORDER BY pr.profile_name");

return $profiles;
}
}