diff --git a/SOLUTION.md b/SOLUTION.md index defe675..44cb21f 100755 --- a/SOLUTION.md +++ b/SOLUTION.md @@ -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". diff --git a/src/Command/ReportYearlyCommand.php b/src/Command/ReportYearlyCommand.php index 97f026f..98ef5bb 100755 --- a/src/Command/ReportYearlyCommand.php +++ b/src/Command/ReportYearlyCommand.php @@ -1,9 +1,11 @@ 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()); + }; } } diff --git a/src/Reports/ReportyViewCountByProfile.php b/src/Reports/ReportyViewCountByProfile.php new file mode 100644 index 0000000..31b9d7d --- /dev/null +++ b/src/Reports/ReportyViewCountByProfile.php @@ -0,0 +1,34 @@ +