Skip to content

Instantly share code, notes, and snippets.

@ahsanatiq
Last active July 29, 2019 08:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ahsanatiq/5339e4b0df7fe77ad6bc519adb230dcf to your computer and use it in GitHub Desktop.
Save ahsanatiq/5339e4b0df7fe77ad6bc519adb230dcf to your computer and use it in GitHub Desktop.
PHP Developer Coding Answers For Rebilly

PHP Developer Coding Exemplar Anwers

Answer for question 1

The reason that the exception message was thrown in the case presented by QA team is when "superman" term came at the start of string, strpos gives 0 bcoz it founded it at the start position, hence given 0 to the if condition with ! (not) operator makes it true.

Solution:

<?php

if (strpos(strtolower($str), 'superman') === false) {
    throw new Exception;
}

When strpos does not found any needle string in the haysack string it returns false (boolean) result. So, its always better to use the === operator with the false bool to make sure the result is returning boolean false as expected.

Answer for question 2

By looking at the Explain query result, the column type=ALL shows that it scanned the entire table to find the matching row, hence no index was used.

Solution:

CREATE INDEX idx_email ON users(email)
OR
CREATE UNIQUE INDEX idx_email ON users(email);  

Implementing/creating an index on the table will make our query significantly faster as it does not have to scan or go thru all records of the table, it will only go thru the index tree.

Answer for question 3

Starting from PHP5.5, it started providing native hashing API/functions which is a nice wrapper around some password hashing algorithms, and its definitely easier to use than crypt() functions

To hash the password:

<?php
$hashedPassword = password_hash($password, PASSWORD_BCRYPT);

To re-check/verify the hashed password:

<?php
if (password_verify($password, $hashedPassword)) {
    // Success!
}
else {
    // Invalid credentials
}

For projects prior to PHP5.5:

Use openwall's (phpass)[https://www.openwall.com/phpass/] library, its 0.4 version supports way old PHP3 to PHP5.

Answer for question 4

Keeping the performance in mind usually I follow the principle, if the array is not sorted then use built-in array_search / in_array, if sorted then use binary-search, if I have to search the same array multiple times say 20-30 times then use combination of array_flip() & isset(). Few years ago someone asked me why php don't have built-in binary-search, I have no answers then, still today I couldn't find any-built-in function. so, here is my implementation below:

function binarySearch($array, $value)
{
    $left = 0;
    $right = count($array) - 1;
 
    while ($left <= $right) {
        $center = (int) floor(($left + $right) / 2);

        if ($array[$center] < $value) {
            $left = $center + 1;
        } elseif ($array[$center] > $value) {
            $right = $center - 1;
        } else {
            return $center;
        }
    }
    return false;
}

Solution explanation: The idea is, when searching of our value, we are cutting the array in half. If the center value is less than our searching value then it might be in the second half, if center value is bigger than the searched value then it might be in the first half, then repeat the steps until we find the value. Cutting the array in half everytime gives us benefit of saving CPU cycles. Hence, it gives us algorithm complexity of O(log n).

Answer for question 5

The issue that you are using more memory than its allowed, is because fetchAll is retrieving everything from the database tables and putting it in the memory location $results, which is clearly not an optimized solution when dealing with big tables. Below is my solution:

<?php

$stmt = $pdo->prepare('SELECT * FROM largeTable');
$stmt->execute();
while ($result = $stmt->fetch(PDO::FETCH_ASSOC)) {
   // manipulate the data here
}

Solution explanation:

We can replace fetchAll() with fetch(), which only retrieves one row at a time, hence we are not hitting the memory limit and can also traverse the entire table.

Answer for question 6

Solution:

<?php

function phoneNumberFormat($number, $delimeter = '-', $format = [3,3,4]) {
    // Strip out everything except numbers
    $formattedNumber = preg_replace("/[^0-9]/", "", $number);
    
    // throw exception if number is not in right length
    if(strlen($formattedNumber) !== array_sum($format)) {
        throw new \Exception('Number digits are not in expected length');
    }
    
    // Add back with delimeter in the given format
    $result = [];
    $position = 0;
    foreach ($format as $key => $item){
        $result[] = substr($formattedNumber, $position, $item);
        $position += $item;
    }
    return implode($delimeter, $result);
}

Answer for question 7

First we create an interface for the cache classes with the two methods for storing and retrieving

<?php

namespace Acme;
interface CacheInterface
{
    public function get($key);
    public function set($key, $value);
}

Create the different cache classes that implement the above interface

<?php

namespace Acme;
class MemCache implements CacheInterface
{
    private $cache;
    public function __construct()
    {
        $this->cache = new Memcache();
        $this->cache->connect('localhost', 11211);
    }
    public function get($key)
    {
        return $this->cache->get($key);
    }
    public function set($key, $value, $ttl = '0')
    {
        return $this->cache->set($key, $value, 0, $ttl);
    }
}

class ApcCache implements CacheInterface
{
    public function get($key)
    {
        return apc_fetch($key);
    }
    public function set($key, $value, $ttl = '0')
    {
        return apc_store($key, $value, 0, $ttl);
    }
}

class DummyCache implements CacheInterface
{
    public function get($key)
    {
        return false;
    }
    public function set($key, $value)
    {
        return true;
    }
}

Then we can inject the cache class as a dependency to any class that wanted to use Cache service.

<?php

class Users
{
    private $cache;
    private $db;
    public function __contruct(CacheInterface $cache, Database $db)
    {
        $this->cache = $cache;
        $this->db = $db;
    }
    
    public function getList()
    {
        if ($usersList = $this->cache->get('USERS_LIST')===false) {
            $usersList = $this->db->query('select * from users');
            $this->cache->set('USERS_LIST', $usersList);
        }
        return $usersList;
    }
}

Notice the dependencies of the class are injected through the constructor method. The $cache object is type-hinted with CacheInterface, so it could accept any cache class that implements that interface.

Lastly, we can initialize the cache class according the environment being used, and pass it to the Users object initialization.

<?php

# config.php (production)
// $cache = new MemCache;
# config.php (staging)
// $cache = new ApcCache;
# config.php (dev)   
$cache = new DummyCache;

$users = new Users($cache, $db);
$users->getList();

An alternate way could be, normally when we are using php frameworks or any dependency injection libraries like "php-di"/"pimple", we use containers at the application level in which we bind our "abstract" classes with "concrete" implementations, then our containers will inject those concrete implementations to the required classes for us. Hence, achieving the IoC inversion of control. So as an example, using Laravel's container our implementation will look like:

<?php

$container = Illuminate\container::getInstance();

// Bind CacheInterface
if ($environment == 'production')
    $container->bind('Acme\CacheInterface', 'Acme\MemCache');
elseif ($environment == 'staging')
    $container->bind('Acme\CacheInterface', 'Acme\ApcCache');
else
    $container->bind('Acme\CacheInterface', 'Acme\DummyCache');
    
// Bind Models
$container->bind('Acme\Users');
    
// Initiate Users
$users = $container->make('Users');    

When we ask the container to initiate/make the Users class, it will automatically inject and resolve all the dependencies of the class.

Answer for question 8

PHPUnit tests for the provided FizzBuzz function

<?php

namespace Tests;

use InvalidArgumentException;
use PHPUnit\Framework\TestCase;

class FizzBuzzTest extends TestCase
{
    /**
     * @test
     */
    public function input_shouldBeExceptionIfStartIsLessThan0()
    {
        $this->expectException(InvalidArgumentException::class);
        fizzBuzz(-1,10);
    }

    /**
     * @test
     */
    public function input_shouldBeExceptionIfStopIsLessThan0()
    {
        $this->expectException(InvalidArgumentException::class);
        fizzBuzz(1,-1);
    }

    /**
     * @test
     */
    public function input_shouldBeExceptionIfStopIsLessStart()
    {
        $this->expectException(InvalidArgumentException::class);
        fizzBuzz(10, 5);
    }

    /**
     * @test
     */
    public function output_shouldBeFizzBuzzIfStartStopIsZero()
    {
        $output = fizzBuzz(0, 0);
        $this->assertEquals('FizzBuzz', $output);
    }

    /**
     * @test
     */
    public function output_shouldBeFizzWhenDivisibleBy3()
    {
        $output = fizzBuzz(1,100);
        $this->stringOutputTester($output, 1, 100, 'Fizz');
    }

    /**
     * @test
     */
    public function output_shouldBeBuzzWhenDivisibleBy5()
    {
        $output = fizzBuzz(1,100);
        $this->stringOutputTester($output, 1, 100, 'Buzz');
    }

    /**
     * @test
     */
    public function output_shouldBeFizzBuzzWhenDivisibleBy3And5()
    {
        $output = fizzBuzz(1,100);
        $this->stringOutputTester($output, 1, 100, 'FizzBuzz');
    }

    /**
     * @test
     */
    public function output_shouldBeNumberWhenNotDivisibleBy3And5()
    {
        $output = fizzBuzz(1,100);
        $this->stringOutputTester($output, 1, 100, 'Number');
    }

    protected function stringOutputTester($output, $start, $stop, $assertCase)
    {
        $pieces = [];
        foreach(range($start, $stop) as $step) {
            if ($this->isDivisibleBy($step, 3) && $this->isDivisibleBy($step, 5)) {
                $expect = 'FizzBuzz';
            }
            if ($this->isDivisibleBy($step, 3) && !$this->isDivisibleBy($step, 5)) {
                $expect = 'Fizz';
            }
            if (!$this->isDivisibleBy($step, 3) && $this->isDivisibleBy($step, 5)) {
                $expect = 'Buzz';
            }
            if (!$this->isDivisibleBy($step, 3) && !$this->isDivisibleBy($step, 5)) {
                $expect = $step;
            }

            $piece = substr($output, 0, strlen($expect));
            if ($assertCase == $expect) {
                $this->assertEquals($expect, $piece);
            }
            if ($assertCase == 'Number' && is_numeric($expect)) {
                $this->assertEquals($expect, $piece);
                $this->assertIsNumeric(filter_var($piece, FILTER_VALIDATE_INT));
            }
            $pieces[] = $piece;
            $output = substr($output, strlen($piece));
        }
        $this->assertCount(($stop-($start-1)), $pieces, 'Something is wrong pieces in string is not matching expected steps');
    }

    protected function isDivisibleBy($number, $divisor)
    {
        return $number % $divisor === 0;
    }

}

Answer for question 9

If I would be able to look at the Select class then I would be more confident about my answer, but my guess is that PDO execute statement is expecting string so it could bind the values to the params in the sql statement, in this case you are providing object pdo didn't know how to convert that object into string. The solution is, you can tailor how your Select object is represented as a string by implementing a __toString() method in your class, so that when your object is type cast as a string (explicit type cast $str = (string) $myObject;, or automatic echo $myObject) you can control what is included and the string format.

Example:

<?php

class Select 
{
    
    protected $name;
    
    public function __toString() {
        return $this->name;
    }
}
$select = new Select();
PDO::execute((string)$select);

Answer for question 10

Without nested loops, another way to do is using regular expression. First, convert the $files list to the string, then convert all the $exclude list to the regular expressions, so it can be used to replace/remove the files from the string. An example implementation is given below:

<?php

// convert files list to string
$fileStr = implode(PHP_EOL,$files);
// convert exclude list to the regular expression
$excludedPatterns = array_map(function ($excludedFile) {
    $isfile = (stripos($excludedFile,'.')!==false);
    return '/'. $excludedFile .($isfile ? '' : '\/.*').'$/m';
}, str_replace('/', '\/', $exclude));
// replace/remove the files
$fileStr = preg_replace($excludedPatterns, '', $fileStr);
// put the files string back together
$files = array_filter(explode(PHP_EOL, $fileStr));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment