Saturday, February 20, 2021

Doctrine: Given object is not an instance of the class this property was declared in

 This error confused me a little bit. I was trying to add an EntityType field to my form and I was keep getting this error. 

Executing:

php bin/console doctrine:schema:validate

Was showing that the Doctrine annotations were correctly set, but still the error was there. Trying to manually query all records from that entity in controller showed me the problem:

$companies = $em->getRepository(Company::class)->findAll();

Looking at the list of companies it was a list of objects from User class! Evrika!  I've made the repository class for Company class by copy pasting it from User class and I forgot to change the entityClass parameter in constructor:

parent::__construct($registry, Company::class);

Have a bug free day! 

Tuesday, December 15, 2020

Watch out! XDebug 3 will not work with some older PHPStorm versions

 I lost like half a day till a figure it out why XDebug from my Docker container will not work properly with PHPStorm.

In Preferences > Languages & Frameworks > PHP, add a new CLI Interpreter. Choose the Docker option, and PHPStorm will automatically find the Xdebug image for you.

At the end of this step I would see correct version of PHP, correct version of XDebug but an error message like: Xdebug: [Step Debug] Could not connect to debugging client (tried using xdebug.client_host ..).

It was driving me nuts, the debugger would stop at first line in code and when going to next breakpoint would just idle.

Short term solution: edit dockerfile to install older XDebug version:

# install xdebug
RUN pecl install xdebug-2.6.1
RUN docker-php-ext-enable xdebug

Long term solution: buy a newer PHPStorm version :) 

Tuesday, October 22, 2019

Symfony file upload mysterious error

Uncaught PHP Exception Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException: "The file "" does not exist" at /var/app/current/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php line 116 {"exception":"[object] (Symfony\\Component\\HttpFoundation\\File\\Exception\\FileNotFoundException(code: 0): The file \"\" does not exist at /var/app/current/vendor/symfony/symfony/src/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php:116)"}


If you see this error the odds are high that you are trying to upload a file larger than the max values set in you php.ini config file (upload_max_filesize and post_max_size).

Wednesday, October 2, 2019

Symfony service container: binding arguments by name or type

This cool feature was introduced in Symfony 3.4 (https://symfony.com/blog/new-in-symfony-3-4-local-service-binding) but I didn't use till today.

Example: create a custom Monolog channel and inject the service in controller (Symfony 4).
I was thinking that I will have to create some sort of custom service definition in order to inject the service "monolog.logger.mychannel" into my controller, but no, it was way easier.

In services.yaml  just add:



services:
    # default configuration for services in *this* file
    _defaults:
        bind:
            # pass this service to any $mychannelLogger argument for any
            # service that's defined in this file
            $mychannelLogger: '@monolog.logger.mychannel'

Now is super easy to inject the service in controllers by adding "$mychannelLogger" in the list of function parameters:


/**
     * @Route("/{id}", name="project_update", methods={"POST"})
     */
    public function delete(Request $request, LoggerInterface $mychannelLogger): Response
    {
    


Read more about it in Symfony docs: https://symfony.com/doc/current/service_container.html#services-binding


Wednesday, September 18, 2019

Custom user provider using multiple database connections with Doctrine and Symfony 4

Situation :

I am using Symfony 4.3 and autowiring.

I have 2 Doctrine database connections, a default and "users" connection:



doctrine:
    dbal:
        default_connection: default
        connections:

           .....
    orm:
        auto_generate_proxy_classes: true
        default_entity_manager: default
        entity_managers:
            default:
                connection: default
                mappings:
                    Main:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/Main'
                        prefix: 'App\Entity\Main'
                        alias: Main
            users:
                connection: users
                mappings:
                    Users:
                        is_bundle: false
                        type: annotation
                        dir: '%kernel.project_dir%/src/Entity/UsersManagement'
                        prefix: 'App\Entity\UsersManagement'
                        alias: Users

So my User entity was managed by the secondary connection "users". Checking from command line would show me that everything is mapped correctly:

php bin/console doctrine:mapping:info --em=users

 Found 7 mapped entities:

 [OK]   App\Entity\UsersManagement\User

Also when loading users I am using a customer query by making UserRepository implement the UserLoaderInterface. (https://symfony.com/doc/current/security/user_provider.html).

security:
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        app_user_provider:
            entity:
                class: App\Entity\UsersManagement\User

But when I try to login, ugly surprise this error pops:
 "The class 'App\Entity\UsersManagement\User' was not found in the chain configured namespaces App\Entity\Main" 

 After googling a little bit I get to this life saving comment from stof: https://github.com/symfony/symfony/pull/8187#issuecomment-18910167


Here is how you configure it:

security:
    providers:
        my_provider:
            entity:
                class: Acme\DemoBundle\Entity\User
                manager_name: users  #non default entity manager

So there is a 'secret' parameter "manager_name" that you need to add to security.yml and is not enough that the entity is correctly mapped by Doctrine.

If somebody ever finds this useful please add a comment :D

Thursday, April 20, 2017

Automated API testing using Codeception

I was looking for a tool for testing an API, and I remembered about my old friend Codeception, which I used for some functional testing a while ago. I need now to test an API which is receiving requests and gives responses using JSON format.


Install Codeception via Composer
$ composer require "codeception/codeception" 
Create an alias so you do not have to write the entire path every time you execute a command:
$ alias codecept='./vendor/bin/codecept'
Create an API test suite:
$ codecept generate:suite api
Edit the configuration file  tests/api.suite.yml:


class_name: ApiTester
modules:
    enabled:
        - \Helper\Api
        - REST:
            url: "http://yourwebste.com/"
            depends: PhpBrowser
            part: Json

I will be using "Cest" format:  "Cest combines scenario-driven test approach with OOP design. In case you want to group a few testing scenarios into one you should consider using Cest format."

$ codecept generate:cest api UsersTestCest
This command is creating a PHP file under tests/api/.  
Before starting to write my first tests I need to find a way to pass a username and password to all tests, as it is needed for connecting to the API.
I can add any parameters to the api.suite.yml and access them inside my test classes:
api.suite.yml


class_name: ApiTester
modules:
    enabled:
        - \Helper\Api
        - REST:
            url: "http://yourwebsite.com"
            depends: PhpBrowser
            part: Json
params:
    username: "username"
    password: "password"


 And inside tests/api/UsersTestCest.php

class UsersTestCest
{
    private $username;
    private $password;

    public function _before(ApiTester $I)
}
    {
        $config = \Codeception\Configuration::config();
        $apiSettings = \Codeception\Configuration::suiteSettings('api', $config);
        $this->username = $apiSettings['params']['username'];
        $this->password = $apiSettings['params']['password'];
    }
}


The function "_before()" will be executed as it names says, before actual test functions, and it will  initialize the variables with values from api.suite.yml file.

Let's write the first test function:


public function createUser(ApiTester $I)
    {
        $action = "create_user";
        $I->haveHttpHeader('Content-Type', 'application/json');

        $array=[
            "action"=> $action,
            "username"=> $this->username,
            "key"=> $this->password,
            "user_username"=> "new_username",
            "user_email"=> "email@company.com",
            "user_password"=> "password"
            ]
        ];
        $string = json_encode($array);
        $I->sendPOST('api.php',$string);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK); // 200
        $I->seeResponseIsJson();
        $I->seeResponseMatchesJsonType([
            'operation' => 'string',
            'status' => 'string',
        ]);
    }

The link where the request is sent with the function sendPost() is the concatenation between the URL parameter from api.suite.yml and the first parameter in sentPost() function.
You can check if the response has HTTP status 200, if it is in JSON format and if the JSON has the expected structure.
Even more you can create your own assertions. These custom functions needs to be added to the file "tests/_support/ApiTester.php".
I will add a function to verify if the "status" field from the response is equal to '1'




<?php
namespace Helper;
class will be available in $I
use Codeception\TestInterface;

class Api extends \Codeception\Module
{
    public function checkOperationStatusSuccess()
    {
        $response = $this->getModule('REST')->response;
        $array = json_decode($response, true);
        $this->assertEquals('1',$array['status'],"Operation status should be '1' for success.");
    }
}

Run all tests with this command:
codecept run api -v

Or only tests from certain class:
$ codecept run tests/api/UsersTestCest.php -v
  
The tests are executed in the alphabetic order of the classes, but you can use the annotation "@depends" to indicated that your test function depends on other one(s). 
Enjoy!



Friday, October 7, 2016

Web SSO - part 2 - integration with LDAP

In my previous article I presented how to create a web SSO system using SimpleSAMLphp and Symfony. The users where declared directly in simpleSAMLphp using "exampleauth:UserPass".

In many companies a LDAP server is the source from where information about user authentication is taken. I will install  OpenLDAP and configure my applications to use it.

1. Install OpenLDAP and phpLDAPadmin

For installing OpenLDAP and phpLDAPadmin I followed this tutorials from DigitalOcean:

https://www.digitalocean.com/community/tutorials/how-to-install-and-configure-openldap-and-phpldapadmin-on-an-ubuntu-14-04-server

You will need to edit also the ldap.conf file, see this thread on StackOverflow

Also you may get an error when trying to login with phpLDAPadmin  "Notice: Undefined variable: _SESSION in ..". 
For me this solution from StackOverflow solved the problem:

"Just add the user wich is running Apache2 (or php5-fpm!) to the system group "www-data" (debian) and restart services apache AND if used php5-fpm both.
Get the User apache is running as:

~# sed -rn 's/^User (.+)/\1/p' /etc/apache2/apache2.conf"

Using phpLDAPadmin I've created two groups "admin" and "regular_users" and also I've created some users allocated to these two groups.

2. Modify SimpleLDAPphp to use OpenLDAP


The documentation for using LDAP authentication is found here: https://simplesamlphp.org/docs/stable/ldap:ldap

My settings are:


Select LDAP authentication to be used from   /metadata/saml20-idp-hosted.php

 /* 
         *Authentication source to use. Must be one that is configured in
* 'config/authsources.php'.
*/
'auth' => 'example-ldap',

3. Modify Symfony app

In the current Symfony application I am expecting an attribute roles containing an array of roles. From LDAP I will receive different attributes, one of them is gidNumber, which is a number identifying a group. My current groups: admin and regular_users have gidNumber 500 and 501.
I will be using these gidNumbers to correctly create roles in the Symfony application.

The changes to be made are done in the UserCreator class:


Of course you need to change these mappings to fit your situation.

4. Test

First make sure to delete any sessions and cookies. After that try to access the secure route from consumer1.local, login with any user from LDAP and you should be redirected to secure area. Check in database if the user and user roles were created correctly.