diff --git a/SOLUTION.md b/SOLUTION.md index defe675..bb1bc93 100755 --- a/SOLUTION.md +++ b/SOLUTION.md @@ -1,13 +1,25 @@ SOLUTION ======== +I have added primary key to main table profiles, added new column to table views with primary key. +I have changed src\TestDataResetCommand to get more logic data (one view number per day, per profile). Before it had 4 records for same day and same profile (random numbers). +fix: setup.sql script. Drop user if exists works only on mysql 5.7+, ref https://dev.mysql.com/doc/refman/5.7/en/drop-user.html, docs 5.6 https://dev.mysql.com/doc/refman/5.6/en/drop-user.html Estimation ---------- -Estimated: n hours - -Spent: x hours +Estimated: 1 hours +Spent: 2 hours Solution -------- -Comments on your solution +I should use framework PDO for building sql query. This would avoid any sql injectons. +Using PDO would allow us to use any other sql database supported by framework and it would make a lot easier to update host or framework. +If is this a end user application, I would add one simple web form to display data. Estimate would be aprox. 1h. +I should separete few parts of code into own methods inside class ReportYearlyCommand. + +Test scenarios +-------- +- check input data (input parameters) +- check output table +- check database data integrity and if valid data +- check speed of app and set acceptance duration time diff --git a/interview-coding.jpg b/interview-coding.jpg new file mode 100644 index 0000000..93e70d8 Binary files /dev/null and b/interview-coding.jpg differ diff --git a/setup.sql b/setup.sql index 1e0d56e..6235bcd 100755 --- a/setup.sql +++ b/setup.sql @@ -1,7 +1,8 @@ DROP DATABASE IF EXISTS `bof_test`; CREATE DATABASE IF NOT EXISTS `bof_test` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; -DROP USER IF EXISTS 'bof-test'@'localhost'; +DROP USER 'bof-test'@'localhost'; + CREATE USER 'bof-test'@'localhost' IDENTIFIED BY 'bof-test'; GRANT USAGE ON * . * TO 'bof-test'@'localhost' IDENTIFIED BY 'bof-test' WITH MAX_QUERIES_PER_HOUR 0 MAX_CONNECTIONS_PER_HOUR 0 MAX_UPDATES_PER_HOUR 0 MAX_USER_CONNECTIONS 0 ; @@ -10,15 +11,16 @@ GRANT ALL PRIVILEGES ON `bof\_test` . * TO 'bof-test'@'localhost' WITH GRANT OPT DROP TABLE IF EXISTS `bof_test`.`profiles`; CREATE TABLE `bof_test`.`profiles` ( -`profile_id` INT NOT NULL , +`profile_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `profile_name` VARCHAR( 100 ) NOT NULL ) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci; DROP TABLE IF EXISTS `bof_test`.`views`; CREATE TABLE `bof_test`.`views` ( +`view_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY, `profile_id` INT NOT NULL , `date` DATE NOT NULL , -`views` INT NOT NULL +`views` INT NOT NULL ) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci; INSERT INTO `bof_test`.`profiles` VALUES(1, 'Karl Lagerfeld'), (2, 'Anna Wintour'), (3, 'Tom Ford'), (4, 'Pierre Alexis Dumas'), (5, 'Sandra Choi'); \ No newline at end of file diff --git a/src/App/Person.php b/src/App/Person.php new file mode 100644 index 0000000..81545d9 --- /dev/null +++ b/src/App/Person.php @@ -0,0 +1,39 @@ +db Connection */ + $this->db = $db; + } + + public function getPersons() + { + if ($this->persons) { + return $this->persons; + } + + $personsTmp = $this->db->query(" + SELECT profile_id, profile_name + FROM {$this->tableName} + ORDER BY profile_name + ")->fetchAll(); + + /* fetchPairs */ + foreach ($personsTmp as $person) { + $this->persons[$person['profile_id']] = $person['profile_name']; + } + /* end */ + + return $this->persons; + } +} \ No newline at end of file diff --git a/src/App/Statistics.php b/src/App/Statistics.php new file mode 100644 index 0000000..876791d --- /dev/null +++ b/src/App/Statistics.php @@ -0,0 +1,75 @@ +db Connection */ + $this->db = $db; + + for ($month = 1; $month <= 12; $month++) { + $this->months[$month] = date("M", mktime(0, 0, 0, $month, 10)); + } + } + + public function getYearStat($year = null, $showPersonName = false) + { + if (!$year) { + die("Missing argument"); + } + + $personsClass = new Person($this->db); + /* if personId(s) passed, get only mentioned perons */ + $this->persons = $personsClass->getPersons(); + + $viewClass = new Views($this->db); + + foreach ($this->persons as $profileId => $profileName) { + if (isset($this->personStats[$year][$profileId])) { + continue; + } + + $yearStatsTmp = $viewClass->getViewsByYear($profileId, $year); + /* there must be some method to get sql "fetchPairs" to get array with own id as array key */ + $yearStats = array(); + foreach ($yearStatsTmp as $yearStat) { + $yearStats[$yearStat['month']] = $yearStat['monthViews']; + } + /* end */ + + $returnStats = array(); + if ($showPersonName) { + $returnStats[0] = $profileName; + } + foreach ($this->months as $monthNumber => $month) { + $returnStats[$monthNumber] = $this->formatNumber($yearStats[$monthNumber]); + } + + $this->personStats[$year][$profileId] = $returnStats; + } + + return $this->personStats[$year]; + } + + public function getMonths() + { + return $this->months; + } + + public function formatNumber($number) { + if (intval($number) == 0) { + return 'n/a'; + } + + return number_format($number, 0, '.', ','); + } +} \ No newline at end of file diff --git a/src/App/Views.php b/src/App/Views.php new file mode 100644 index 0000000..fa1f143 --- /dev/null +++ b/src/App/Views.php @@ -0,0 +1,48 @@ +db Connection */ + $this->db = $db; + } + + public function getViews($profileIds = null) + { + /* TODO: implement multiple profileid query */ + $this->views = $this->db->query(" + SELECT * + FROM {$this->tableName} + ")->fetchAll(); + + return $this->views; + } + + public function getViewsByYear($profileId, $year) + { + if (isset($this->views[$profileId][$year])) { + return $this->views[$profileId][$year]; + } + + $this->views[$profileId][$year] = $this->db->query(" + SELECT MONTH(date) as month, SUM(views) as monthViews + FROM {$this->tableName} + WHERE profile_id = {$profileId} + AND YEAR(date) = {$year} + GROUP BY MONTH(date) + ORDER BY MONTH(date) + ")->fetchAll(); + + return $this->views[$profileId][$year]; + } + +} \ No newline at end of file diff --git a/src/Command/ReportYearlyCommand.php b/src/Command/ReportYearlyCommand.php index 97f026f..667c22e 100755 --- a/src/Command/ReportYearlyCommand.php +++ b/src/Command/ReportYearlyCommand.php @@ -1,32 +1,54 @@ setName('report:profiles:yearly') ->setDescription('Page views report') - ; + ->addArgument('year', InputArgument::REQUIRED, "Show data for year..."); } protected function execute(InputInterface $input, OutputInterface $output) { /** @var $db Connection */ - $io = new SymfonyStyle($input,$output); + $io = new SymfonyStyle($input, $output); $db = $this->getContainer()->get('database_connection'); - $profiles = $db->query('SELECT profile_name FROM profiles')->fetchAll(); + /* $year argument is required */ + $year = intval($input->getArgument('year')); + /* TODO: better input error handeling */ + if (strlen($year) != 4) { + die("Error! Invalid year number"); + } + + /* didn't spent time to figure out how to get connection in own class by not extending container class, getting just a connection, symphony stuff */ + $displayClass = new Statistics($db); + $yearlyStats = $displayClass->getYearStat($year, true); + + $tableHeads = array_merge(array( + "Profile {$year}" + ), $displayClass->getMonths() + ); // Show data in a table - headers, data - $io->table(['Profile'], $profiles); + $io->table($tableHeads, $yearlyStats); } } diff --git a/src/Command/TestDataResetCommand.php b/src/Command/TestDataResetCommand.php index bd0d52a..0da3e96 100755 --- a/src/Command/TestDataResetCommand.php +++ b/src/Command/TestDataResetCommand.php @@ -24,8 +24,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $startDate = strtotime('2014-09-01'); $endDate = strtotime('2017-02-11'); - $dataPerDay = 3; - $db->query('TRUNCATE views'); $profiles = $db->query('SELECT * FROM profiles')->fetchAll(); @@ -38,19 +36,16 @@ protected function execute(InputInterface $input, OutputInterface $output) while ($currentDate <= $endDate) { - for ($i = 0; $i <= $dataPerDay; $i++) { - - $views = rand(100, 9999); - $date = date('Y-m-d', $currentDate); + $views = rand(100, 9999); + $date = date('Y-m-d', $currentDate); - $sql = sprintf( - "INSERT INTO views (`profile_id`, `date`, `views`) VALUES (%s, '%s', %s)", - $profileId, - $date, - $views - ); - $db->query($sql); - } + $sql = sprintf( + "INSERT INTO views (`profile_id`, `date`, `views`) VALUES (%s, '%s', %s)", + $profileId, + $date, + $views + ); + $db->query($sql); $currentDate = mktime(0,0,0, date('m', $currentDate), date('d', $currentDate) + 1, date('Y', $currentDate)); }