Kategorien
PHP Symfony

PHP Anwendungen automatisiert modernisieren mit rector

Das Tool rector ist eine Konsolenanwendung für automatisiertes Refactoring von PHP Anwendungen.

Damit lassen sich schnell einfache Altlasten entfernen aus Legacy Projekten.

Mit dem folgenden Rector Script (./rector.php) können die gängigsten Refactorings durchgeführt werden:

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
use Rector\Set\ValueObject\SetList;
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;

// run with: vendor/bin/rector process --debug --dry-run
return static function (RectorConfig $rectorConfig): void {
    $setLists = [
        /*SetList::PHP_52,
        SetList::PHP_53,
        SetList::PHP_54,
        SetList::PHP_55,
        SetList::PHP_56,
        SetList::PHP_70,
        SetList::PHP_71,
        SetList::PHP_72,
        SetList::PHP_73,
        SetList::PHP_74,
        SetList::PHP_80,
        SetList::PHP_81,*/

        //SetList::CODE_QUALITY,
        //SetList::CODING_STYLE,
        //SetList::DEAD_CODE,
        SetList::RECTOR_PRESET,
        /*SetList::INSTANCEOF,
        SetList::EARLY_RETURN,
        SetList::NAMING,
        SetList::PRIVATIZATION,
        SetList::STRICT_BOOLEANS,
        SetList::STRICT_BOOLEANS,*/
    ];

    foreach ($setLists as $setList) {
        $rectorConfig->import(
            $setList
        );
    }

    $rectorConfig->paths([
        __DIR__.'/src',
        __DIR__.'/tests',
    ]);

    $rectorConfig->skip([
        // do not make all constructor args readonly for mocking and unit tests
        ReadOnlyPropertyRector::class,
        // using empty() is fine
        DisallowedEmptyRuleFixerRector::class,
    ]);
};


ausführbar im Dry mode ohne Änderungen am Code mit:

vendor/bin/rector process --debug --dry-run

Kategorien
Docker Symfony

SSO Saml Login mit Symfony

Single-Sign-On ist das Thema der Stunde, es verbessert die Sicherheit und auch die User Verwaltung. Um Saml SSO in Symfony einzubauen gibt es das OneloginSaml Bundle, ein Symfony Wrapper für das php-saml Packet.

Der Vorteil für die Benutzer von SSO liegt darin, dass diese sich morgens einmal authentifizieren müssen und danach den ganzen Tag alle Anwendungen verwenden können ohne sich nochmals einloggen zu müssen.

Der Vorteil für das Unternehmen liegt darin, dass es vorgeben kann, dass die Authentifierung nur mit 2 Faktor Authentifizierung stattfinden kann (z.B. Handy App und Passwort), um mehr Sicherheit zu gewährleisten. Dies kannn in der Zukunft den steigendenden Sicherheitsbedürfnissen angepasst werden. Außerdem können zentral Rechte vergeben werden für User und nach dem Austritt aus dem Unternehmen einfach wieder entzogen werden.

Wenn das Bundle installiert und korrekt konfiguriert ist, benötigt man eine Dev-Umgebung mit einem Identity Provider (IdP). Dafür bietet sich docker-simplesamlphp an:

docker-compose.yml:

version: '3.7'
services:
	idp:
		image: kenchan0130/simplesamlphp
		container_name: idp
		ports:
			- "8080:8080"
		environment:
			SIMPLESAMLPHP_SP_ENTITY_ID: http://localhost/saml/metadata
			SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE: http://localhost/saml/acs
			SIMPLESAMLPHP_SP_SINGLE_LOGOUT_SERVICE: http://localhost/saml/logout

Das OneloginSaml Bundle muss folgender Maßen konfiguriert werden:

hslavich_onelogin_saml:
  # Basic settings
  idp:
    # Identifier of the SP entity  (must be a URI)
    entityId: 'http://localhost:8080/simplesaml/saml2/idp/metadata.php'
    singleSignOnService:
      url: 'http://localhost:8080/simplesaml/saml2/idp/SSOService.php'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
    singleLogoutService:
      url: 'http://localhost:8080/simplesaml/saml2/idp/SingleLogoutService.php'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
    x509cert: 'insert certificate here'
  # Service Provider Data that we are deploying.
  sp:
    entityId: 'http://localhost/saml/metadata'
    assertionConsumerService:
      url: 'http://localhost/saml/acs'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
    singleLogoutService:
      url: 'http://localhost/saml/logout'
      binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
    privateKey: ''

Die Informationen kann man hier ablesen, auch die vom Zertifikat:

SAML Metadata: http://localhost:8080/simplesaml/saml2/idp/metadata.php?output=xhtml

Wenn man alles richtig konfiguriert hat, dann landet man beim Aufruf der Symfony Anwendung auf der Login Seite des IdP Providers und kann sich dort einloggen:

Login Seite des IdP Providers

Die Logins lauten:

admin:     secret
user1:     password
user2:     password

Dann sollte man sich mit seiner Symfony Applikation lokal über SAML einloggen und testen können.

Kategorien
PHP

PHP-FFMpeg resize Video

Um mit PHP-FFMpeg ein Video auf eine maximale Größe und Breite zu resizen kann man folgenden Code verwenden:

<?php

namespace App\Core\Media;

use FFMpeg\Coordinate\Dimension;
use FFMpeg\FFMpeg;
use FFMpeg\Filters\Video\ResizeFilter;
use FFMpeg\Format\Video\X264;
use FFMpeg\Media\Video;

class VideoManager
{

    /**
     * @var FFMpeg
     */
    private $ffmpeg;

    /**
     * @var int
     */
    private $videoMaxHeight;

    /**
     * @var int
     */
    private $videoMaxWidth;

    /**
     * @var Video|null
     */
    private $video;

    /**
     * @var string|null
     */
    private $videoPath;

    /**
     * @param FFMpeg $ffmpeg
     * @param int $videoMaxHeight
     * @param int $videoMaxWidth
     */
    public function __construct(
        FFMpeg $ffmpeg,
        int $videoMaxHeight,
        int $videoMaxWidth
    )
    {
        $this->ffmpeg = $ffmpeg;
        $this->videoMaxHeight = $videoMaxHeight;
        $this->videoMaxWidth = $videoMaxWidth;
    }

    /**
     * @param string|null $videoPath
     * @return VideoManager
     */
    public function setVideoPath(?string $videoPath): VideoManager
    {
        $this->videoPath = $videoPath;
        if (!file_exists($videoPath)) {
            throw new \Exception('video does not exists:' . $videoPath);
        }
        $this->video = $this->ffmpeg->open($videoPath);

        return $this;
    }



    /**
     * @return int
     */
    public function getHeight(): int
    {
        return $this->video->getStreams()->first()->getDimensions()->getHeight();
    }

    /**
     * @return int
     */
    public function getWidth(): int
    {
        return $this->video->getStreams()->first()->getDimensions()->getWidth();
    }

    /**
     * @return void
     */
    public function formatH264Codec()
    {
        $format = new X264();

        $tmpDirectory = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'video_format' . DIRECTORY_SEPARATOR;
        if (!is_dir($tmpDirectory)) {
            mkdir($tmpDirectory);
        }

        $tmpFilePath = $tmpDirectory . uniqid(time()) . '.mp4';

        $this->resizeVideo();

        $format
            ->setVideoCodec('libx264')
         ;

        $this->video->save($format, $tmpFilePath);

        // replace with encoded file
        unlink($this->videoPath);
        copy($tmpFilePath, $this->videoPath);

        //reset ffmpeg
        $this->ffmpeg = FFMpeg::create();
        $this->video = $this->ffmpeg->open($this->videoPath);
    }

    /**
     * @return void
     */
    protected function resizeVideo()
    {
        $width  = $this->getWidth();
        $height = $this->getHeight();

        if ($width > $this->videoMaxWidth || $height > $this->videoMaxHeight) {
            $factorWidth = $width / $this->videoMaxWidth;
            $factorHeight = $height / $this->videoMaxHeight;

            if ($factorWidth > $factorHeight) {
                $width = $width / $factorWidth;
                $height = $height / $factorWidth;
                $mode = ResizeFilter::RESIZEMODE_SCALE_WIDTH;
            } else {
                $width = $width / $factorHeight;
                $height = $height / $factorHeight;
                $mode = ResizeFilter::RESIZEMODE_SCALE_HEIGHT;
            }

            //round to the nearest odd number: ffmpeg otherwise throws an error
            $height = $this->roundToNextOddNumber($height);
            $width  = $this->roundToNextOddNumber($width);

            $this->video->filters()->resize(
                new Dimension($width, $height),
                $mode
            );
        }
    }

    /**
     * @param float $number
     * @return int
     */
    private function roundToNextOddNumber(float $number): int
    {
        $number = ceil($number); // Round up decimals to an integer
        if($number % 2 == 1) $number++;

        return $number;
    }
}

Usage:

$videoManager = new VideoManager(FFMpeg\FFMpeg::create(), 200, 300);
$videoManager->setVideoPath('/path/to/video.dat');
$this->videoManager->formatH264Codec();
Kategorien
PHP Symfony Testing

Symfony System-Test für valide Konfiguration der Umgebungen

Es gibt in Symfony die Möglichkeit mehrere Umgebungen (Environments) anzulegen und verschiedenartig zu konfigurieren. Das Problem ist, dass die automatisierten Tests immer gegen die Test-Umgebung und somit auch gegen die Test-Konfiguration gemacht werden.

Wie kann man sicher gehen, dass die produktive Konfiguration valide ist und dies mit einem automatisierten Test absichern?

Die Antwort ist sehr einfach, sollte eine Umgebungs-Konfiguration syntaktisch falsch sein, schlägt der Test fehl:

<?php

namespace App\Tests\System;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class BootKernelTest extends WebTestCase
{

    /**
     * @return array
     */
    public function dataProviderTestBootKernel(): array
    {
        return [
            'dev' => ['dev'],
            'test' => ['test'],
            'prod' => ['prod'],
        ];
    }

    /**
     * the test would fail if the environment config is wrong
     *
     * @dataProvider dataProviderTestBootKernel
     *
     * @param string $environment
     * @return void
     */
    public function testBootKernel(string $environment)
    {
        $kernel = $this->bootKernel(['environment' => $environment]);

        $this->assertNotEmpty($kernel);
    }

}

Kategorien
Amazon AWS WordPress

WordPress Bitnami Banner entfernen auf AWS AMI

Um den Bitnami Banner entfernen zu können, soll man per SSH einen Konsolenbefehl ausführen:

sudo /opt/bitnami/apps/wordpress/bnconfig --disable_banner 1

dies führte bei mir zu einer Fehlermeldung, dass die Datei bnconfig nicht vorhanden ist:

sudo: /opt/bitnami/apps/wordpress/bnconfig: command not found

Die Ursache ist, dass die Datei standradmäßig erst deaktiviert ist, es gibt eine /opt/bitnami/apps/wordpress/bnconfig.disabled

Diese muss aktiviert werden, indem sie umbenannt wird nach bnconfig:

sudo mv /opt/bitnami/apps/wordpress/bnconfig.disabled /opt/bitnami/apps/wordpress/bnconfig

Danach kann der Banner erfolgreich entfernt werden:

sudo /opt/bitnami/apps/wordpress/bnconfig --disable_banner 1
Kategorien
Symfony

Symfony Cache mit Datenbank

Die Symfony Cache Komponente besitzt eine große Zahl von möglichen Adaptern zum Cachen von Informationen.

Zum Cachen in der Datenbank kann man den folgenden PdoAdapter für Doctrine verwenden und folgender Maßen konfigurieren:

#config/packages/framework.yaml
framework:
    cache:
        pools:
            app:
                adapter: AppCache
#config/services.yaml
AppCache:
  class: Symfony\Component\Cache\Adapter\PdoAdapter
  arguments:
    - '@doctrine.dbal.default_connection'

Damit erzeugt Symfony eine Datenbank Tabelle innerhalb der default Datenbank Verbindung für die Cache Einträge.

Dies hat besonders Performancegewinne und ist übersichtlicher im Vergleich zu  der Default-Filesystem-Variante. Besonders ist dies für eine Docker Umgebung zu empfehlen, weil das langsame Dateisystem nicht verwnedet werden muss.

Kategorien
Symfony

Symfony 5 Exception „The annotation was never imported.“

Ein Grund der Fehlermeldung kann sein, dass es tatsächlich eine Annotation ist, die ungültig ist, sich aber im vendor Code befindet. Dann sollte man diese ignorieren.

Fehlermeldung:

Doctrine\Common\Annotations\AnnotationException : [Semantical Error] The annotation "@suppress" in method Monolog\Formatter\LineFormatter::normalizeException() was never imported. Did you maybe forget to add a "use" statement for this annotation?

Sollte man die Annotation ignorieren mit dem statischen Aufruf von in der config/bootstrap.php:

AnnotationReader::addGlobalIgnoredName('suppress');

 

Kategorien
Amazon AWS PHP

PHP eigene Metriken in AWS Cloudwatch erstellen

Um in CloudWatch eigene Metriken anzulegen, um z.B. Alarm auszulösen und Monitoring zu betreiben mit den Daten der Applikation, kann man mit dem aws-php-sdk den folgenden Code verwenden:

 $client = new CloudWatchClient([
        'region' => 'eu-central-1',
        'version' => '2010-08-01',
        'credentials' => [
            'key'    => 'xxx',
            'secret' => 'xxx
        ],
    ]);
}
$result = $this->client->putMetricData(array(

    'Namespace' => 'MyProject',
    'MetricData' => array(
        array(
            'MetricName' => 'MyMetric,
            'Timestamp' => time(),
            'Value' => 1,
            'Unit' => 'Count',
            'Dimensions' => array(
                array(
                    'Name' => 'Environment',
                    'Value' => 'test',
                ),
            ),
        )
    )
));
Kategorien
Amazon AWS Docker PHP 7

DockerFile PHP mit XDebug und Amazon Linux 2

Ein DockerFile für eine PHP 7.2. Umgebung mit XDebug für eine Symfony 4 Umgebung unter Amazon Linux 2:

FROM amazonlinux:2.0.20190823.1

RUN yum install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

# install php
RUN amazon-linux-extras install -y php7.2
RUN yum install -y php-devel.x86_64 php-xml.x86_64 php-pecl-zip.x86_64  php-intl.x86_64

#install xdebug
RUN yum install -y gcc make
RUN yum install -y php-pear.noarch
RUN pecl install xdebug
RUN echo 'zend_extension=/usr/lib64/php/modules/xdebug.so' >> /etc/php.ini
Kategorien
PHP PHP 7 VBA Excel

PHP csv Datei mit Umlauten erstellen für Excel

Um eine csv-Datei zu erstellen, in der die Umlaute richtig kodiert sind, muss man in seinem Projekt, welches im Normalfall UTF-8 kodiert ist, die Umlaute nach UTF-16LE kodieren. Diese Kodierung kann von Windows und Mac angezeigt werden.

$file = fopen('file.csv', 'w');
foreach ($rows as $row) {
 $row = array_map(function($cell){
 return mb_convert_encoding($cell, 'UTF-16LE', 'UTF-8');
 }, $row);
 fputcsv($file, $row, ';');
}
fclose($file);