Created
October 15, 2025 11:58
-
-
Save paulfcdd/b7dbe0286e0ca97853796c391377a1d8 to your computer and use it in GitHub Desktop.
Pavlo Novykov Test task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Q1 | |
| THe error is occur because of fetchAll load all result into the memory, so in case of big tables it can finish with error memory. | |
| To prevent it we need to read results line by line using fetch, do not use SELECT * and select columns we need instead. Example of the | |
| code which fix this issue | |
| ``` | |
| <?php | |
| $sql = 'SELECT id, col1, col2 | |
| FROM largeTable | |
| ORDER BY id'; | |
| $stmt = $pdo->query($sql, PDO::FETCH_ASSOC); | |
| if ($stmt === false) { | |
| throw new \RuntimeException('Query failed'); | |
| } | |
| while ($row = $stmt->fetch()) { | |
| // manipulate the data here | |
| } | |
| $stmt->closeCursor(); | |
| ``` | |
| # Q2 | |
| Error occurs because indexOf() returns 0 if string begin from the first symbol, so in case of string "Superman is awesome!" | |
| indexOf will return 0 which is false and that's why we got an error | |
| To preven it we need to check indexOf only on -1 (symbol not found) (!indexOf() === -1) | |
| ``` | |
| function validateString(str) { | |
| if (str.toLowerCase().indexOf('superman') === -1) { | |
| throw new Error('String does not contain superman'); | |
| } | |
| } | |
| ``` | |
| # Q3 | |
| ``` | |
| <?php | |
| function formatPhoneToUS(string $phone): ?string { | |
| $digits = ''; | |
| $len = strlen($phone); | |
| for ($i = 0; $i < $len; $i++) { | |
| $ch = $phone[$i]; | |
| if ($ch >= '0' && $ch <= '9') { | |
| $digits .= $ch; | |
| } | |
| } | |
| if (strlen($digits) === 11 && $digits[0] === '1') { | |
| $digits = substr($digits, 1); | |
| } elseif (strlen($digits) > 10) { | |
| $digits = substr($digits, -10); // останні 10 цифр | |
| } | |
| if (strlen($digits) !== 10) { | |
| return null; | |
| } | |
| return substr($digits, 0, 3) . '-' . substr($digits, 3, 3) . '-' . substr($digits, 6); | |
| } | |
| ``` | |
| # Q4 | |
| function colorFromName(name) { | |
| var hash = 0, i, chr; | |
| for (i = 0; i < name.length; i++) { | |
| chr = name.charCodeAt(i); | |
| hash = chr + ((hash << 5) - hash); | |
| hash |= 0; | |
| } | |
| var color = '#'; | |
| for (i = 0; i < 3; i++) { | |
| var value = (hash >> (i * 8)) & 0xFF; | |
| color += ('00' + value.toString(16)).slice(-2); | |
| } | |
| return color; | |
| } | |
| # Q5 | |
| final class FizzBuzzTest extends TestCase | |
| { | |
| public function testSingleNumberCases(int $n, string $expected): void | |
| { | |
| $this->assertSame($expected, fizzBuzz($n, $n)); | |
| } | |
| public static function singleNumberProvider(): array | |
| { | |
| return [ | |
| [1, '1'], | |
| [2, '2'], | |
| [3, 'Fizz'], | |
| [4, '4'], | |
| [5, 'Buzz'], | |
| [6, 'Fizz'], | |
| [10, 'Buzz'], | |
| [15, 'FizzBuzz'], | |
| [30, 'FizzBuzz'], | |
| ]; | |
| } | |
| public function testConcatenationForRange1to15(): void | |
| { | |
| $expected = '12Fizz4BuzzFizz78FizzBuzz11Fizz1314FizzBuzz'; | |
| $this->assertSame($expected, fizzBuzz(1, 15)); | |
| } | |
| public function testConcatenationForRange14to16(): void | |
| { | |
| $this->assertSame('14FizzBuzz16', fizzBuzz(14, 16)); | |
| } | |
| public function testZeroIsFizzBuzz(): void | |
| { | |
| $this->assertSame('FizzBuzz12Fizz4Buzz', fizzBuzz(0, 5)); | |
| } | |
| public function testDefaultArgumentsProduceNonEmptyString(): void | |
| { | |
| $out = fizzBuzz(); | |
| $this->assertIsString($out); | |
| $this->assertNotSame('', $out); | |
| $this->assertStringEndsWith('9899BuzzFizz101', substr($out, -12)); | |
| } | |
| public function testInvalidRangesThrow(int $start, int $stop): void | |
| { | |
| $this->expectException(InvalidArgumentException::class); | |
| fizzBuzz($start, $stop); | |
| } | |
| public static function invalidRangeProvider(): array | |
| { | |
| return [ | |
| [10, 5], | |
| [-1, 5], | |
| [1, -5], | |
| ]; | |
| } | |
| } | |
| For JS approach will be a bit different - another test framework like Jest, use different errors like RangeError | |
| # Q6 | |
| All buttons will log 'Line 10' because var has function scope, not block. So because of this `i` is shared by all clousure functions | |
| inside the loop. | |
| To prevent this and to have correct output we need can capture value of i in new scope for each iteration like this: | |
| (function () { | |
| for (var i = 0, l = 10; i < l; i++) { | |
| (function (iCopy) { | |
| document.getElementById('button-' + iCopy).onclick = function () { | |
| console.log('Line %s', iCopy); | |
| }; | |
| })(i); | |
| } | |
| })(); | |
| in this code we are create new scope for each loop iteration and iCopy is a copy of i which is passed to new scope | |
| # Q7 | |
| function isIterable(obj) { | |
| return obj != null && typeof obj[Symbol.iterator] === 'function'; | |
| } | |
| # Q8 | |
| final class Document | |
| { | |
| public function __construct( | |
| private string $name, | |
| private User $user | |
| ) { | |
| if (mb_strlen($this->name) <= 5) { | |
| throw new InvalidArgumentException('Document name must be > 5 characters.'); | |
| } | |
| } | |
| public function getName(): string | |
| { | |
| return $this->name; | |
| } | |
| public function getUser(): User | |
| { | |
| return $this->user; | |
| } | |
| public function getTitle(?Database $db = null): ?string | |
| { | |
| // TODO: move to DocumentRepository; entities shouldn't query DB. | |
| $db = $db ?? Database::getInstance(); | |
| $stmt = $db->prepare('SELECT title FROM document WHERE name = :name LIMIT 1'); | |
| $stmt->execute([':name' => $this->name]); | |
| $title = $stmt->fetchColumn(); | |
| return $title !== false ? (string)$title : null; | |
| } | |
| public static function getAllDocuments(): array | |
| { | |
| // TODO: implement via DocumentRepository in next step | |
| return []; | |
| } | |
| } | |
| class User | |
| { | |
| /** | |
| * Fix strpos bug: "!strpos(...)" fails when 'senior' is at position 0. | |
| * Use stripos(...) === false for a case-insensitive "contains" check. | |
| */ | |
| public function makeNewDocument(string $name): Document | |
| { | |
| if (stripos($name, 'senior') === false) { | |
| throw new InvalidArgumentException('The name should contain "senior".'); | |
| } | |
| return new Document($name, $this); | |
| } | |
| public function getMyDocuments(?array $allDocs = null): array | |
| { | |
| $allDocs ??= Document::getAllDocuments(); | |
| $mine = []; | |
| foreach ($allDocs as $doc) { | |
| if ($doc instanceof Document && $doc->getUser() === $this) { | |
| $mine[] = $doc; | |
| } | |
| } | |
| return $mine; | |
| } | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment