The Starting Point

Debugging PHP Scripts With Xdebug

This is just a small post meant for all those poor souls like me who couldn’t get xdebug configured as easy as it should be. The steps are quite straight forward but still I got cought up in “the set up” for nearly 3hours.

Environment

  • Mac OS X Sierra
  • PHP 7.1
  • PHPStorm

I am assuming that you have homebrew installed on your mac.

Step 1: Run brew doctor in the terminal and follow the onscreen instructions to fix issues with homebrew if any.

Step 2: To install xdebug, run brew install phpXX-xdebug where XX stands for the PHP version that you are using. For example, if you are using PHP 7.1, run brew install php71-xdebug and if you are using PHP 7.0, run brew install php70-xdebug.

Step 3: Navigate to /usr/local/etc/php/7.1/conf.d and add the following lines to ext-xdebug.ini.

/usr/local/etc/php/7.1/conf.d/ext-xdebug.ini
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
      [xdebug]
      zend_extension="/usr/local/opt/php71-xdebug/xdebug.so"
      xdebug.remote_connect_back = 1
      xdebug.idekey = PHPSTORM
      xdebug.default_enable = 1
      xdebug.remote_mode=req
      xdebug.remote_host = 127.0.0.1
      xdebug.remote_enable = 1
      xdebug.remote_port = 9333
      xdebug.remote_handler = dbgp
      xdebug.profiler_enable=0
      xdebug.profiler_enable_trigger=1
      xdebug.remote_autostart=1
      xdebug.idekey=PHPSTORM
      ; xdebug.remote_log=/usr/local/etc/php/7.1/xdebug.log

Step 4: The first line in the file zend_extension="/usr/local/opt/php71-xdebug/xdebug.so" will be already present in the file. Please add this if not already present.

Step 5: By default, xdebug runns on port 9000. I have changed it to 9333 as I have other stuff that runs on port 9000

Step 6: You can monitor the xdebug logs by uncommenting this line ; xdebug.remote_log=/usr/local/etc/php/7.1/xdebug.log. Pleasee note that this file can get really really big over the time. So, once you have got xdebug up and running, it is a good idea to stop the log.

Step 7: In PHP storm, under preferences, navigate to Languages & Frameworks > PHP > Debug and set the following parameters

External connections
  • Detect path mappings from deployment configurations
  • Break at first line in PHP Scripts
    Xdebug
  • Debug port 9333(the same port nummber that we give in step 5)
  • Check ‘Can accept external connections’
  • Check ‘Force break at the first line when no path mapping specified’
  • Check ‘Force break at the first line when a script is outside the project’ Step 8: In PHP storm, under preferences, navigate to *Languages & Frameworks > PHP > Servers * create a configuration for the local server and add xdebug as the debugger.

Step 9: Once you have configured the server, you would need to create a debug configuration. For this navigate to Edit configurations under Run menu and add a new configuration based on PHP Remote debug. For this, click on the + icon on the top left corner and select the server created in step 8 as the server and give the value of xdebug.idekey in step 3 as the Ide Key and give a suitable name to the configuration

Step 10: Now, select the debug configuration and start listening for the debug connections by clicking on the receiver icon in PHPStorm. Once you have done this, the debug window will open inside php when your script is run.

Laravel Polymorphic Relations

Let’s see how we can customize Eloquent’s polymorphic relations. The default documentation is not quite clear about how you can use regular column names instead of weird column names like “commentable”, “postable” etc.

Problem statement

We have two Models for storing the Address and Customer details of a firm. An Address may be associated with a person or a company as these two are subsets of the firm’s customers. A Customer can have more than one address associated with them. Since the customer can be either a company or a person, we can represent this scenario using a polymorphic relation between Customer, Company and Address.

On Address model we will have a column to store the customer’s id and another column to represent whether the customer is a company or a person.

Let’s consider the following schema:

Basic Schema
1
2
3
 id
 customer_type
 customer_id
On Person Model
Person.php
1
2
3
4
public function address()
{
    return $this->morphMany('App\Models\Address',null,'customer_type','customer_id','id');
}
On Company Model
Company.php
1
2
3
4
public function address()
{
    return $this->morphMany('App\Models\Address',null,'customer_type','customer_id','id');
}
On Address Model
Address.php
1
2
3
4
public function customer()
{
    return $this->morphTo('customer','customer_type');
}

The arguments passed to the morphTo method has to match the column name for the foreign key on the model that is common to the other two models. Conside the following case,

Address.php
1
$this->morphTo('xxxx','yyyyy');

In this case, there should be a column on the Address modal with name xxxx_id to represent the foreign key of the related model on the shared model table and there should be a column named yyyyy on the shared model to represnt the type. That is column yyyyy on address table should represnt whether the id on the xxxx_id column represents a company or a customer.

Saving relations – Adding a new address to a customer

Saving
1
2
3
4
$user = User::find(1);
$address = new Address();
$address->address = 'Test';
$user->address()->save($address);

Saving relations – Associating a customer with an address

Saving
1
2
3
4
5
$address = new Address();
$address = User::find(1);
$address->address = "Test";
$address->customer()->associate($user);
$address->save();

OAuth in Symfony 2

Setting up HWIOAuth Bundle

1. Add the HWI OAuth bundle to the project

Download the OAuthBundle from the official git repository and copy the contents to ProjectRoot/vendor/bundles/HWI/Bundle/OAuthBundle

2. Enable the bundle in AppKernel.php

AppKernel
1
2
3
4
5
6
7
8
9
10
11
public function registerBundles()
    {
        $bundles = array(
            .............................
            new HWI\Bundle\OAuthBundle\HWIOAuthBundle(),
        );

        .................................

        return $bundles;
    }

HWI OAuth bundle needs Sensio Buzz bundle for proper functioning. So, add the buzz bundle to your project if it is not already added. You may download it from here. If it is not added, you may get an error message like this

Error
1
 Fatal error: Class 'Buzz\Client\Curl' not found in /home/xxxx/my-projects/OAuthProject/app/cache/dev/appDevDebugProjectContainer.php on line 2110 Call Stack: 0.0001 321828 1

To avoid this, copy the contents of buzz.zip atached along with this to PROJECT_ROOT/vendor

Also, add the namespace for the OAuth bundle and the Buzz bundle to autoload.php

Autoload.php
1
2
3
4
 $loader->registerNamespaces(array(
   'Buzz'=>__DIR__.'/../vendor/buzz/lib',
    'HWI'=>__DIR__.'/../vendor/bundles',
 ));

Register the buzz bundle namespace before HWI bundle’s namespace to avoid this error

Error
1
 "Fatal error: Class 'Buzz\Client\Curl' not found"

3. Import the routing file of HWI bundle to the projects routing.yml

Routing.yml
1
2
3
 hwi_oauth_redirect:
    resource: "@HWIOAuthBundle/Resources/config/routing/redirect.xml"
    prefix:   /connect

4. Import the security_factory.xml to security section of security.yml

security.yml
1
2
3
security:
    factories:
        - "%kernel.root_dir%/../vendor/bundles/HWI/Bundle/OAuthBundle/Resources/config/security_factory.xml"

5.Add the resource owners in the config.yml. For this, we need to register our app in Google and Facebook and obtain the client_id and the client_secret. In addition to that, we have to provide a redirect URL while configuring the app. The response after authenticating the user by the service providers is sent to this URL.

For Google, this redirect URL is www.yourdomain.com/login/check-google. This address should be provide against the field labelled ‘Redirect URIs’. Please see the screenshot below. google api

For Facebook, once you register the app, click on ‘Add platform’ button and register your app as a web app. The redirect URL for Facebook is www.yourdomain.com/login/check-facebook. Enter this URL in ‘Site URL’ field and ‘Mobile Site URL ’ field under ‘Website’. Please see the screenshot below. facebook api

Then, provide the client_id and client_secret for Facebook and Google in the config.yml file

config.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 hwi_oauth:
     #name of the firewall in which this bundle is active, this setting MUST be set

    firewall_name: main(What ever firewall name that you have specified in security.yml)
    target_path_parameter: /
    resource_owners:
        facebook:
            type:                facebook
            client_id:           48000000093708
            client_secret:       cda9csdsdsds8dc4dc0699448385c9016c7
            scope:               "email"

        google:
            type:                google
            client_id:           872410845454.apps.googleusercontent.com
            client_secret:       _rFBqWyAGHFvbdN-EgUs52uGK2Z
            scope:               "https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"


6.Create a User Provider Service
The bundle needs a service that is able to load users based on the user response of the oauth endpoint and the service should implement
HWI\Bundle\OAuthBundle\Security\Core\User\OAuthAwareUserProviderInterface.
For this, first create a class caller XYZOAuthUserProvider .

OAuthUserProvider.php
1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace XYZ\CoreBundle\Services;

use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUserProvider;


class XYZOAuthUserProvider extends OAuthUserProvider
{


}

Then, register it as a service in services.yml file

services.yml
1
2
3
4
5
xyz.oauth.user_provider:
    class: XYZ\CoreBundle\Services\XYZOAuthUserProvider
    tags:
      - { name: user.provider }
    arguments: ["@service_container"]

7. Modifications in the security.yml file

1. Under Providers, add the service created in the previous step
security.yml
1
2
3
4
providers:
    main:
        entity: { class: XYZCoreBundle:User }
    my_custom_hwi_provider: { id: tsz.oauth.user_provider }
2. Under Firewalls/main, add
security.yml
1
2
3
4
5
6
7
8
9
oauth:
        resource_owners:
            facebook:           "/login/check-facebook"
            google:             "/login/check-google"
        login_path:        /connect/facebook
        failure_path:      /login
        default_target_path: /login
        oauth_user_provider:
            service: xyz.oauth.user_provider

8. Import the login check routes corresponding to each resource owner in the routing.yml file

routing.yml
1
2
3
4
5
   facebook_login:
        pattern: /login/check-facebook

    google_login:
        pattern: /login/check-google

Checking whether the OAuth user exists in the local database

By default, a user who logs in through facebook or google is given ROLE_USER, and ROLE_OAUTH_USER. In this case, even though all the details required for a registered user are not available from Google or Facebook, the user still have all the privileges of ROLE_USER. This should not be the case. The user should not be granted all the provileges unless he completes his profile. So, for a user who logs in through Google or Facebook is given only ROLE_OAUTH_USER if he is not present in the database. If he is present in the database, he is granted ROLE_USER when he logs in through Google or Facebook. So, there should be a mechanism to check whether the OAUth user is present in the database or not.

Step 1
Edit vendor/bundles/HWI/Bundle/OAuthBundle/Security/Core/User/OAuthUser.php and modify the getRoles() function to return only ROLE_OAUTH_USER as role.

Original function:

Original function
1
2
3
4
public function getRoles()
    {
        return array('ROLE_USER', 'ROLE_OAUTH_USER');
    }

Modified function:

Modified function
1
2
3
4
public function getRoles()
    {
        return array('ROLE_OAUTH_USER');
    }

Step 2

Next, edit the OAuthUserProvider.php file

  • Import the namespace of the bundle containing the User entity to OAuthUserProvider.php so that we can get the container inside OAuthUserProvider.php.
OAuthUserProvider.php
1
use Venom\XYZBundle\XYZBundle ;
  • In the original configuration loadUserByOAuthUserResponse() function calls the loadUserByUsername() function with nickname as the arguement. The nickname is obtained from the server response. In our entity user provider, we have defined email as a unique field. So, to check whether user exists in the database, we can use email. For this, we need to obtain the email from the response.

Although UserResponseInterface class has a getEmail() method, this won’t return the email address for all the service providers as the response for each service provider is different

A var_dump() for the response object for Facebook and Google are given below

Facebook:

Facebook Response Dump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
["paths":protected]=>
  array(5) {
["identifier"]=>
string(2) "id"
["nickname"]=>
string(8) "username"
["realname"]=>
string(4) "name"
["email"]=>
NULL
["profilepicture"]=>
NULL
  }
  ["response":protected]=>
  array(13) {
["id"]=>
string(15) "1XXXX5898097313"
["name"]=>
string(12) "XXXXXX"
["first_name"]=>
string(9) "XXXXX"
["last_name"]=>
string(2) "YY"
["link"]=>
string(40) "https://www.facebook.com/xxxx.yyy"
["username"]=>
string(15) "xxxx.yyy"
["birthday"]=>
string(10) "04/14/1900"
["gender"]=>
string(4) "male"
["email"]=>
string(20) "xxxx@yyyy.com"
["timezone"]=>
float(5.5)
["locale"]=>
string(5) "en_US"
["verified"]=>
bool(true)
["updated_time"]=>
string(24) "2013-07-19T09:59:22+0000"
  }

Google:

Google response dump
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  ["paths":protected]=>
  array(5) {
    ["identifier"]=>
    string(2) "id"
    ["nickname"]=>
    string(4) "name"
    ["realname"]=>
    string(4) "name"
    ["email"]=>
    string(5) "email"
    ["profilepicture"]=>
    string(7) "picture"
  }
  ["response":protected]=>
  array(8) {
    ["id"]=>
    string(21) "10493450YYYY981597842"
    ["email"]=>
    string(28) "praveesh@abc.in"
    ["verified_email"]=>
    bool(true)
    ["name"]=>
    string(10) "Praveesh A"
    ["given_name"]=>
    string(8) "Praveesh"
    ["family_name"]=>
    string(1) "A"
    ["locale"]=>
    string(2) "en"
    ["hd"]=>
    string(19) "abc.in"
  }

From the variable dump, we can see that the email address can be obtained from the response by

1
2
 $user_details= $response->getResponse();
 $email = $user_details['email'];

`

  • Edit loadUserByOAuthUserResponse() function as given below
1
2
3
4
5
6
7
8
9
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
    $user_details= $response->getResponse();
    $email = $user_details['email'];

    return $this->loadUserByUsername($email);


    }

3. To check whether the user exists in the database, edit loadUserByUsername($username) function as given below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public function loadUserByUsername($username)
{
    //get the entity manager
    $em = CMSBundle::getContainer()->get('doctrine')->getEntityManager();

    //load the user from DB
    $entity = $em->getRepository('XYZBundle:User')->loadUserByUsername($username);

    if(!$entity){
        //if the user does not exist, then return a new OAuth user
        $user = new OAuthUser($username);
     } else {
         // if the user exists, return that user
        $user = $entity;
    }
    return $user;

}

4. Then, hide or show options in the twig file according to the role of the user.

Twig
1
2
3
4
5
6
7
8
9
    <div class="well">
        {% if is_granted('ROLE_USER') %}
            OPTIONS THAT REQUIRE ROLE_USER PRIVILEGE
        {% else %}
            PROMPT TO REGISTER TO AVAIL ALL FEATURES

        {% endif %}

    </div>

Symfony 2 Validation Groups

When more than one forms are associated with the same entity, the form validator will validate all the properties of that entity for all forms associated with that entity. This would result in form errors as all the forms may not have fields for all the properties of that entity. For example, consider a User entity with username, email address, password, name and phone number as properties. The user needs to enter all these properties when he is initially registering. When the user needs to edit his profile, there he can’t edit username and e mail address as these two properties are non-editable. So, the form for editing the profile won’t have fields for username and e mail. If the we are using the normal validation, when the user tries to update his profile, the form validation would fail as Symfony would validate the form for username and e mail constraints too. To prevent this, we create validation groups. By creating validation groups, we define separate set of validation constraints for different forms.
Steps:
1. When writing a validation constraint, specify the validation group against it

Validations.yml
1
2
3
4
5
6
7
8
9
10
11
username:                      
    - NotBlank: ~
    - MinLength: { limit: 5, groups: [ userRegistration] }
address:
    - MinLength: { limit: 10, groups: [ userUpdate] } 
phoneNumber:
    - NotBlank: ~
    - MinLength: { limit: 10 }
    - Type
        type: integer
        message: phone number should contain only numbers 

2.Specify the validation group in the form class in the getDefaultOptions function

FormType
1
2
3
4
5
6
7
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Venom\BlogBundle\Entity\User',
        'validation_groups' => array('userUpdate', 'userRegistration')
    ));
}

3.Pass the validation group when form is created

For the user registration action, we create the form with ‘userRegistration’ validations:

Registration Action
1
2
$form = $this->createForm(new UserType(), $entity,array(
                            'validation_groups' => array('userRegistration')));

For the user edit action, we create the form with ‘userUpdate’ validations:

Edit Action
1
2
$form = $this->createForm(new UserType(), $entity,array(
                            'validation_groups' => array('userUpdate')));

4.If the validation group is not specified when the form is created, the form will be validated for all the default validation constraints(The constraints for which a validation group is not specified) That is, in our case, a form created with no validation group would get validated only for the phone number field.

Setting Up Octopress

Step by step instruction

  1. clone Source branch to praveesh4u@github.com

     $ git clone -b source git@github.com:praveesh4u/praveesh4u.github.com.git
    
  2. clone Master branch to praveesh4u@github.com/ _deploy

     $ cd praveesh4u@github.com
    
     $ git clone  git@github.com:praveesh4u/praveesh4u.github.com.git _deploy
    
  3. If these steps are already done, do the following

     $ cd praveesh4u@github.com  
     $ git pull origin source
     $ cd praveesh4u@github.com/_deploy 
     $ git pull origin master
    
  4. Next, run

     $ gem install bundler
     $ bundle install
     $ rake setup_github_pages
    

    When prompted, enter the repository’s address-

       git@github.com:praveesh4u/praveesh4u@github.com
    
  5. Configure git global parameters like username and email address using

     $ git config --global user.name "Praveesh A- Linux" 
     $ git config --global user.email "praveesh4u@gmail.com"
    
  6. Add/Edit posts
  7. Generate the post

     $ rake generate
    
  8. Preview the generated post using

     $ rake preview 
    

    To view the preview, go to localhost:4000

  9. Commit the changes

     $ git add .
     $ git commit -a -m "Commit message"
     $ git pull origin source (to sync with the server)
     $ git push origin source
    
  10. Publish the post

    $ rake deploy
    
  11. Done!!

Copyright © 2017 - praveesh4u@gmail.com - Powered by Octopress