Monday, October 12, 2015

PHP algorithm problem

Since I started to study PHP I focused on OOP, good practices, tools (Composer, Git), frameworks, integrating with other technologies etc. I was looking over a job listing and in order to be able to send your CV they were asking to solve a problem:

"Function f(n) counts how many times  character "1" appears in numbers from 1 to n.
 Example:  f(1)=1, f(2)=1, f(12)=5.
Question: What is the next "n" value for  which  f(n)=n?"

I've learned a lot about algorithms in high school and university using Pascal and C to implement them, but I never used PHP for this kind of task.

I wrote a small program and as usually opened the browser to see the results.

 <?php

function f($n)
{
    $count=0;
    for($i=1;$i<=$n;$i++)
    {
        $count+=substr_count((string)$i,'1');
    }
    return $count;
}
$n=2;
while(f($n)!=$n)
{   
    echo "n=". $n ." f(n)= " .f($n) ."<br>";
    $n++; 
}

 
The code was generating correctly the results for f(n) but after 200s  stopped without finding the number I was looking for:

n=6149 f(n)= 2885
n=6150 f(n)= 2886
n=6151 f(n)= 2888
n=6152 f(n)= 2889
n=6153 f(n)= 2890
Fatal error: Maximum execution time of 200 seconds exceeded in C:\Program Files (x86)\EasyPHP-DevServer-14.1VC11\data\localweb\test\index3.php on line 8

 ( I had set the maximum execution time to 200 seconds some times ago when I was doing a script which was downloading RSS feeds ).

OK, I will modify the script to save the output on a text file and I  will execute it from command line: C:\mypath\php script.php

$myfile = fopen("f(n).txt", "a") or die("Unable to open file!");
function f($n)
{
    $count=0;
    for($i=1;$i<=$n;$i++)
    {
        $count+=substr_count((string)$i,'1');
    }
    return $count;
}
$n=2;
while(f($n)!=$n)
{   

    fwrite($myfile,"n=". $n ." f(n)= " .f($n) .PHP_EOL );
    $n++;
   
}
fclose($myfile);


After I while I checked and the script was still runnig!!!  I started to think it is a trick question, maybe there is no number which solves the equation.

Maybe recursion will be faster??!! ... I did a recursive version of the script and using microtime() function I compared the speed for n=100  with the iterative version:

<?php
$time_start = microtime(true);

function f($n)
{
    if($n==1) {
        return 1;
    } else {
        return substr_count((string)$n,'1') + f($n-1);
    }
}
$n=2;

while(f($n)!=$n)
{   
    echo "n=". $n ."  f(n)=" . f($n) ."<br>";
    $n++;
    if($n==100) break;
}

$time_end = microtime(true);
$execution_time = ($time_end - $time_start);
echo "execution time=". $execution_time . "<br>";


Result for recursive:
     n=99 f(n)=20
     execution time=0.15200901031494


Result for iteration:

    n=99 f(n)=20
    execution time=0.054003000259399


So recursivity  is not the answer ...I need to rethink the way how I compute f(n) using the values already obtained and not taking it from scratch at each iteration.
Below is the solution, I used saving to file and execute  the script from command line because I was thinking that sill will take a lot of time, but no, it was lightning fast:

<?php
$myfile = fopen("rapid.txt", "a") or die("Unable to open file!");
$time_start = microtime(true);
$fn=1;
$n=2;
while($fn!=$n)
{   
    $n++;
    $fn=$fn + substr_count((string)$n,'1');
    fwrite($myfile,"n=". $n ." f(n)= " .$fn .PHP_EOL );
}

$execution_time = (microtime(true) - $time_start);
fwrite($myfile, $execution_time);
fclose($myfile);


Result:
    n=199981 f(n)= 199981
    3.6452090740204


I am not sure how relevant is this script for the activity at the respective job, but it was nice to  revisit algorithms.

Friday, October 9, 2015

SOLID principles

From Wikipedia:   In computer programming, SOLID (Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion) is a mnemonic acronym introduced by Michael Feathers for the "first five principles" named by Robert C. Martin in the early 2000s.


Initial Stands for
(acronym)
Concept
S SRP [4]
Single responsibility principle
a class should have only a single responsibility (i.e. only one potential change in the software's specification should be able to affect the specification of the class)
O OCP [5]
Open/closed principle
“software entities … should be open for extension, but closed for modification.”
L LSP [6]
Liskov substitution principle
“objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.” See also design by contract.
I ISP [7]
Interface segregation principle
“many client-specific interfaces are better than one general-purpose interface.”[8]
D DIP [9]
Dependency inversion principle
one should “Depend upon Abstractions. Do not depend upon concretions.”[8]
And going more in detail with Dependency Inversion, the principle states:
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend on details. Details should depend on abstractions.

Abstraction dependency

The presence of abstractions to accomplish DIP have other design implications in an Object Oriented program:
  • All concrete class packages must connect only through interface/abstract classes packages.
  • No class should derive from a concrete class.
  • No method should override an implemented method.[5]
  • All variable instantiation requires the implementation of a Creational pattern as the Factory Method or the Factory pattern, or the more complex use of a Dependency Injection framework.

Thursday, October 8, 2015

Back to basics - Ajax autocomplete search with PHP, JQuery and MySQL

Working with Symfony is hiding some details from what happens under the hood. I decided to revisit some topics without frameworks.

I found this very nice tutorial about creating an AJAX autocomplete search:

http://markonphp.com/autocomplete-php-jquery-mysql-part1/

The only things that I would add to this tutorial is that you can see each request and eventual errors using Firebug



Tuesday, September 29, 2015

BasicBlogBundle - Functional Testing with PHPUnit

Reading about testing controllers in Symfony I found this post on stackoverflow which brings arguments that controller shouldn't be unit tested:  http://stackoverflow.com/questions/10126826/how-can-i-unit-test-a-symfony2-controller

It makes sense, I will than write functional tests for my controllers.Working with PHPUnit and Symfony (how I did):

1. Install PHPUnit

Install PHPUnit globally on your machine following the instructions found here:
https://phpunit.de/manual/current/en/installation.html

2. Configuration
In "app" directory  there is the file phpunit.xml.dist
Make a copy of it and rename it to phpunit.xml
Do any changes you consider necessary inside of it (I didn't changed anything).
Under "\src\CPANA\BasicBlogBundle\Tests\Controller" Symfony adds an example test class: DefaultControllerTest.php. I've edited that file to test by BlogController class:
"BlogControllerTest.php"

3. Write your test class
The test will verify if the the requested page contains the word "blog" which is in the title of the page expected.

class BlogControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = static::createClient();
        $client->request('GET', '/blog');
        //var_dump($client->getResponse()->getContent());
       
        $crawler = $client->request('GET', '/blog');
        $this->assertGreaterThan(
            0,
            $crawler->filter('html:contains("Blog")')->count()
        );
    }
}


4.Run PHPUnit
Open command prompt, navigate to the folder where Symfony is installed. Run command:
    phpunit -c app "src/CPANA/BasicBlogBundle/"> "phpunitLog.txt"

this command will log the output in a text file. Review the log file found in the root path of Symfony.
If you are lucky the output will look like this:

PHPUnit 4.8.8 by Sebastian Bergmann and contributors.

.Time: 4.89 seconds, Memory: 24.75Mb

OK (1 test, 1 assertion)


Next challenge should be to test aspects that depend on the interaction with the database:
http://symfony.com/doc/current/cookbook/testing/database.html

Friday, September 25, 2015

cpana/basicblogbundle now on GitHub and Packagist

https://github.com/cristianpana86/BasicBlogBundle
https://packagist.org/packages/cpana/basicblogbundle
I've worked previously with GitHub, the new thing was to have the package on Packagist.com so it can be installed easily via Composer.
After creating my repository on GitHub I went on Packagist and created an account.
Following the instructions found on this article: http://www.sitepoint.com/listing-packages-on-packagist-for-composer/ I've created my composer.json document:

{
    "name":        "cpana/basicblogbundle",
    "type":        "symfony-bundle",
    "description": "Symfony Basic Blog Bundle",
    "keywords":    ["blog","symfony"],
    "homepage":    "https://github.com/cristianpana86/BasicBlogBundle",
    "license":     "MIT",
    "authors": [
        {
            "name": "Cristian Pana",
            "email": "cristianpana86@yahoo.com"
        }
   ],
   "require": {
       "php": ">=5.5.0",
       "symfony/symfony": "2.7.*",
        "doctrine/orm": "~2.2,>=2.2.3,<2.5",
        "doctrine/dbal": "<2.5",
        "doctrine/doctrine-bundle": "~1.4",
        "symfony/assetic-bundle": "~2.3",
        "symfony/swiftmailer-bundle": "~2.3",
        "symfony/monolog-bundle": "~2.4",
        "sensio/distribution-bundle": "~4.0",
        "sensio/framework-extra-bundle": "~3.0,>=3.0.2"
    },
    "minimum-stability": "dev",
    "autoload" : {
        "psr-4" : {
            "CPANA\\BasicBlogBundle\\" : ""
        }
    }
}

Some of the values are from the Symblog composer.json and may be outdated, it's something I have to look over.
I've added also the installation instructions on my GitHub repository and from there are automatically listed on Packagist.com:

Install using Composer:
    composer require cpana/basicblogbundle:dev-master
 
Register the bundle in AppKernel.php by adding:
    new CPANA\BasicBlogBundle\CPANABasicBlogBundle(),
 
Import paths in app/config/routing.yml by adding:
    CPANABasicBlogBundle:
    resource: "@CPANABasicBlogBundle/Resources/config/routing.yml"
 
Make sure to have configured your database in app/config/parameters.yml Generate you schema using console:
    php app/console cache:clear
    php app/console doctrine:schema:update --force

Thursday, September 24, 2015

CPANABasicBlogBundle for Symfony2

Having as a starter point the http://tutorial.symblog.co.uk/ ( about which I wrote here ) I developed blog bundle having the following main features:

Frontside
- view all blog posts with pagination
- view individual blog posts
- add comments

Admin
- view a list of all blog posts with Edit and Delete options.
- view the list of comments with options to Approve/Unapprove or Delete.

Adding comments form


In the Symblog tutorial there was a add comment functionality but for me didn't worked throwing an error like "Catchable Fatal Error: Object of class XYZ could not be converted to string in Doctrine\DBAL\Statement.php" ( see this post about it ). To solve the issue I've added to Entity\Blog.php the following function:

    public function __toString()
    {
        return strval($this->id);
    }

Also I modified the CommentType.php to make all the comments unapproved initially:

// \src\CPANA\BasicBlogBundle\Form\CommentType.php

public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('user')
            ->add('comment')
            ->add('approved','hidden', array('data' => '0',))
         ;
    }

Uploading photos

What I want to implement: upload photo, copy the file in a specific path  and save the name of the file on the database

Make sure you are pointing to the right path in the template. In src\CPANA\BasicBlogBundle\Resources\views\Blog\show.html.twig:

<img src="{{ asset(['bundles/basicblogbundle/images/', blog.image]|join) }}" alt="{{ blog.title }} image not found"  />

At this path: 'bundles/basicblogbundle/images/' I should upload the files in the controller.

Make sure to indicate that the "file" field is not mapped to the Entity.

->add('image', 'file', array('mapped'=>false))

Upload photo and save in database only the name
------------------------------------------------
if ($form->isValid()) {
            $newFilename = $form['attachment']->getData()->getClientOriginalName();
            // Handle picture upload process
            $uploadDir=dirname($this->container->getParameter('kernel.root_dir')) . '/web/bundles/basicblogbundle/images/';
            $form['image']->getData()->move($uploadDir,$newFilename);
            // End of upload
           
            $blog->setImage($newFilename);
            $em = $this->getDoctrine()->getManager();
            $em->persist($blog);
            $em->flush();


This works fine but we should make sure the file names are unique so they do not conflict when you try to upload a file with same name.

We should add random string function in a class. I do no think this should be considered a service, so I will not register it as a service in the Dependency Injection container.
http://symfony.com/doc/current/best_practices/business-logic.html

Create an Utils folder. Place there RandomString.php, add a static function: randomStr

namespace CPANA\BasicBlogBundle\Utils;

class RandomString
{

    public static function randomStr($length = 10)
    {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
   
    }



After this include the randomStr in the fileName:

$newFilename =RandomString::randomStr() . $form['image']->getData()->getClientOriginalName();
---------------------------
when testing the new feature I noticed that the photo is not available when browsing the blog, reason: the name included in the template is trimmed. I looked in the database , in the "image" column, same there. Checking the type of column I noticed the column is set to     varchar(20)!!!! that's the problem right there.

Solution:
modify in Entity\Blog.php the ORM annotation for $image from
@ORM\Column(type="string", length=20)
to
@ORM\Column(type="string", length=255)

After this update the DB schema using Symfony 2 command line:

php app/console cache:clear
php app/console doctrine:schema:update --force

 Pagination

In the home view of the blog I should list all the blog posts. I want to limit to 3 per page an have buttons to navigate though posts.

The path should have a parameter "currentPage" which can be optional, and the default value is 1:

CPANABasicBlogBundle_homepage:
    pattern:  /blog/{currentPage}
    defaults: { _controller: CPANABasicBlogBundle:Blog:blogHome, currentPage: 1 }
    requirements:
        _method:  GET
        currentPage: \d+


--------------
Modify the repository as seen here - http://anil.io/post/41/symfony-2-and-doctrine-pagination-with-twig:

        public function getAllPosts($currentPage = 1)
        {
            // Create our query
            $query = $this->createQueryBuilder('p')
                ->orderBy('p.created', 'DESC')
                ->getQuery();

            $paginator = $this->paginate($query, $currentPage);

            return $paginator;
        }


        public function paginate($dql, $page = 1, $limit = 3)
        {
            $paginator = new Paginator($dql);
           
            $paginator->getQuery()
                ->setFirstResult($limit * ($page - 1)) // Offset
                ->setMaxResults($limit); // Limit

            return $paginator;
        }

----------------------------------------------------
In controller retrieve posts and pass them to view

    public function blogHomeAction($currentPage=1)
    {
        $em = $this->getDoctrine()
            ->getEntityManager();

        $posts = $em->getRepository('CPANABasicBlogBundle:Blog')
            ->getAllPosts($currentPage);
       
        $iterator=$posts->getIterator();
        $limit = 3;
        $maxPages = ceil($posts->count()/$limit);
        $thisPage = $currentPage;
       
        return $this->render(
            'CPANABasicBlogBundle:Blog:home.html.twig', array(
            'blogs' => $iterator,
            'maxPages'=>$maxPages,
            'thisPage' => $thisPage,
            )
        );


In template showing the posts is the same we just need to add pagination buttons:
This will look nice with some css :)

{% if maxPages > 1 %}
    <ul>
        {%if thisPage > 1 %}
        <li >
                <a href="{{ path('CPANABasicBlogBundle_homepage', {currentPage: thisPage-1 < 1 ? 1 : thisPage-1}) }}">«</a>
        </li>
        {% endif %}
       
        {# Render each page number #}
        {% for i in 1..maxPages %}
        <li>
            <a href="{{ path('CPANABasicBlogBundle_homepage', {currentPage: i}) }}">{{ i }}</a>
        </li>
        {% endfor %}

        {# `»` arrow #}
        {%if thisPage < maxPages %}
        <li>
            <a href="{{ path('CPANABasicBlogBundle_homepage', {currentPage: thisPage+1 <= maxPages ? thisPage+1 : thisPage}) }}">»</a>
        </li>
        {% endif %}
    </ul>
    {% endif %}


The photo should not be a mandatory information. I had to do the following modifications to implement that:

Add in controller to the form builder 'required' => false :
 ->add('image', 'file', array('mapped'=>false,'required' => false,))

Also in controller handle new uploaded file name and path only if it was any file selected in the form:

if (!is_null($form['image']->getData())) {....}
 

modify the Entity/Blog.php to accept NULL values for image field.

 * @ORM\Column(type="string", length=255, nullable=true)
Update database using console command.
        php app/console cache:clear
        php app/console doctrine:schema:update --force

Fine tuning - allow deleting blog posts even if they have comments

While browsing happily and testing the functionality I discovered I cannot delete an article having comments, receiving some nasty error: "SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails ..."

I believe it should be good that an admin to be able to delete an article even if it has comments added to it. So I will modify the Database structure to allow this behavior.

There are two kinds of cascades in Doctrine:

1) ORM level - uses cascade={"remove"} in the association - this is a calculation that is done in the UnitOfWork and does not affect the database structure. When you remove an object, the UnitOfWork will iterate over all objects in the association and remove them.

2) Database level - uses onDelete="CASCADE" on the association's joinColumn - this will add On Delete Cascade to the foreign key column in the database:

@ORM\JoinColumn(name="father_id", referencedColumnName="id", onDelete="CASCADE")


Update the Entity/Comment.php
    /**
     * @ORM\ManyToOne(targetEntity="Blog", inversedBy="comments")
     * @ORM\JoinColumn(name="blog_id", referencedColumnName="id",  onDelete="CASCADE")
     */
    protected $blog;


Update the database using console:
    php app/console cache:clear
    php app/console doctrine:schema:update --force


I will soon add the BasicBlogBundle on Github and Packagist.