Welcome to Abdul Malik Ikhsan's Blog

Publish Test Coverage to Codecov from Github Actions

Posted in testing by samsonasik on February 26, 2020

Code Coverage

Github Actions is one of ways to run Continues Integration. For Coverage report, we can use Codecov to publish the coverage result after running and generating test coverage.

For example, you have a Github Repository. You can open https://codecov.io/login and choose “Github”:

After you logged in, you can choose repository, or directly access https://codecov.io/gh/{your github user}/{your github repo}, for example, I use “samsonasik” as user, and “mezzio-authentication-with-authorization” as repository name:

https://codecov.io/gh/samsonasik/mezzio-authentication-with-authorization

On very first, we need to activate Webhook by open https://codecov.io/gh/gh/{your github user}/{your github repo}/settings, for example:

https://codecov.io/gh/samsonasik/mezzio-authentication-with-authorization/settings

Then, we click “Create new webhook” under Github Webhook:

After it done, we can copy “Repository Upload Token”:

by click “Copy” after then token, and back to Github, and save to Secrets section under Your Github Repository Settings with click “Add a new secret”, with eg: named: CODECOV_TOKEN, fill the value with your copied token, and click “Add secret” to save it to be like as follow:

The preparation is done. Now, time to add the github workflow, eg: “.github/workflows/ci-build.yml” at your repository, eg for php package/project and use phpunit, the workflow can be like the following:

name: "ci build"

on:
  pull_request:
  push:
    branches:
      - "master"

jobs:
  build:
    name: PHP ${{ matrix.php-versions }}
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        php-versions: ['7.2', '7.3', '7.4']
    steps:
      - name: Setup PHP Action
        uses: shivammathur/setup-php@1.8.2
        with:
          extensions: intl
          php-version: "${{ matrix.php-versions }}"
          coverage: pcov
      - name: Checkout
        uses: actions/checkout@v2
      - name: "Validate composer.json and composer.lock"
        run: "composer validate"
      - name: "Install dependencies"
        run: "composer install --prefer-dist --no-progress --no-suggest && composer development-enable"
      - name: "Run test suite"
        run: "vendor/bin/phpunit --coverage-clover=coverage.xml"
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          file: ./coverage.xml
          flags: tests
          name: codecov-umbrella
          yml: ./codecov.yml
          fail_ci_if_error: true

That’s it. To trigger the Continues Integration, we can push a commit to the repository.

When its succeed (ci build green), we can then display coverage badge, eg at README.md like the following:

[![Code Coverage](https://codecov.io/gh/{your github user}/{your github repo}/branch/master/graph/badge.svg)](https://codecov.io/gh/{your github user}/{your github repo})

that will show:

Code Coverage

Create isGranted View Helper for Mezzio Application with mezzio-authorization-acl component

Posted in Mezzio by samsonasik on February 15, 2020

This post is inspired by @KiwiJuicer question at Laminas’s Slack, about how to verify granted access on resource in the layout or view part, eg:

<?php if ($this->isGranted('admin.add')) : ?>
    <button name="add">Add</button>
<?php endif; ?>

OR

<?php if ($this->isGranted('admin.edit', ['id' => 1])) : ?>
    <button name="edit">Edit</button>
<?php endif; ?>

We can use Mezzio\Authorization\LaminasAcl service, but it requires a Psr\Http\Message\ServerRequestInterface instance to be passed at 2nd parameter as signature:

public function isGranted(string $role, ServerRequestInterface $request) : bool
{

}

We can pull the role from session, but how about Psr\Http\Message\ServerRequestInterface instance? Well, we don’t need middleware to fill that! We can create a RouteResult from Mezzio\Router\LaminasRouter service with utilize Mezzio\LaminasView\UrlHelper to get path from route name as resource name, with use Laminas\Diactoros\ServerRequestFactory to create Laminas\Diactoros\ServerRequest instance, so, the view helper can be as follow:

<?php

// src/App/View/Helper/IsGranted.php

declare(strict_types=1);

namespace App\View\Helper;

use Laminas\Diactoros\ServerRequestFactory;
use Laminas\Diactoros\Uri;
use Laminas\View\Helper\AbstractHelper;
use Mezzio\Authorization\Acl\LaminasAcl;
use Mezzio\LaminasView\UrlHelper;
use Mezzio\Router\LaminasRouter;
use Mezzio\Router\RouteResult;

class IsGranted extends AbstractHelper
{
    private $acl;
    private $getRole;
    private $url;
    private $router;

    public function __construct(
        LaminasAcl    $acl,
        GetRole       $getRole, 
        UrlHelper     $url, 
        LaminasRouter $router
    ) {
        $this->acl     = $acl;
        $this->getRole = $getRole;
        $this->url     = $url;
        $this->router  = $router;
    }

    public function __invoke(
        string $resource,
        array $routeParams = [], 
        array $queryParams = []
    ): bool {
        $request = ServerRequestFactory::fromGlobals();
        $request = $request->withUri(
            new Uri(($this->url)($resource, $routeParams, $queryParams))
        );

        $request = $request->withAttribute(
            RouteResult::class,
            $this->router->match($request)
        );

        return $this->acl->isGranted(($this->getRole)(), $request);
    }
}

Above, I assume that you already have another view helper or service to get role, eg, named App\View\Helper\GetRole so the factory for the isGranted view helper can be as follow:

<?php

// src/App/View/Helper/IsGrantedFactory.php

declare(strict_types=1);

namespace App\View\Helper;

use Laminas\View\HelperPluginManager;
use Mezzio\Authorization\Acl\LaminasAcl;
use Mezzio\Router\LaminasRouter;
use Psr\Container\ContainerInterface;

class IsGrantedFactory
{
    public function __invoke(ContainerInterface $container): IsGranted
    {
        $acl                 = $container->get(LaminasAcl::class);
        $helperPluginManager = $container->get(HelperPluginManager::class);
        $getRole             = $helperPluginManager->get('getRole');
        $url                 = $helperPluginManager->get('url');
        $router              = $container->get(LaminasRouter::class);

        return new IsGranted($acl, $getRole, $url, $router);
    }
}

We can register the view helper to App\ConfigProvider class:

<?php

// src/App/ConfigProvider.php

declare(strict_types=1);

namespace App;

class ConfigProvider
{
    public function __invoke(): array
    {
        return [
            'dependencies' => $this->getDependencies(),
            'templates'    => $this->getTemplates(),
            'view_helpers' => [
                'invokables' => [
                    'getRole' => View\Helper\GetRole::class,
                ],
                'factories'  => [
                    'isGranted' => View\Helper\IsGrantedFactory::class,
                ],
            ],
        ];
    }

    public function getDependencies(): array
    { /* */ }

    public function getTemplates(): array
    { /* */ }
}

Now, we can check use “$this->isGranted($resource)” or “$this->isGranted($resource, $routeParams, $queryParams)” check in the layout or view.

This source code example can be found at samsonasik/mezzio-authentication-with-authorization repository that you can try yourself 😉