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
20 changes: 16 additions & 4 deletions SOLUTION.md
Original file line number Diff line number Diff line change
@@ -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
Binary file added interview-coding.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions setup.sql
Original file line number Diff line number Diff line change
@@ -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 ;
Expand All @@ -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');
39 changes: 39 additions & 0 deletions src/App/Person.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace BOF\App;

use Doctrine\DBAL\Driver\Connection;

class Person
{
protected $tableName = 'profiles';
protected $db = null;
protected $persons = null;

function __construct($db)
{
/** @var $this->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;
}
}
75 changes: 75 additions & 0 deletions src/App/Statistics.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace BOF\App;

class Statistics
{
protected $db = null;
public $year = null;
public $months = null;
protected $persons = null;
protected $personStats = null;
protected $personCurrentYearStat = null;

public function __construct($db)
{
/** @var $this ->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, '.', ',');
}
}
48 changes: 48 additions & 0 deletions src/App/Views.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

namespace BOF\App;

use Doctrine\DBAL\Driver\Connection;

class Views
{
protected $tableName = 'views';
protected $db = null;
public $views = null;

function __construct($db)
{
/** @var $this->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];
}

}
30 changes: 26 additions & 4 deletions src/Command/ReportYearlyCommand.php
Original file line number Diff line number Diff line change
@@ -1,32 +1,54 @@
<?php

namespace BOF\Command;

use Doctrine\DBAL\Driver\Connection;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use BOF\App\Person;
use BOF\App\Views;
use BOF\App\Statistics;


class ReportYearlyCommand extends ContainerAwareCommand
{
protected $persons = null;

protected function configure()
{
$this
->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);

}
}
23 changes: 9 additions & 14 deletions src/Command/TestDataResetCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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));
}
Expand Down