Kategorien
Symfony

Sonata 4 Admin Variablen im Template

In Sonata 4 stehen verschiedene Sonata-spezifische Variablen in den Templates zur Verfügung. Hier sind einige der häufig verwendeten Variablen:

  1. admin: Diese Variable repräsentiert das aktuelle Admin-Objekt und ermöglicht den Zugriff auf verschiedene Admin-spezifische Informationen und Methoden. Sie kann verwendet werden, um beispielsweise den Admin-Titel oder die Routen zu erhalten.
  2. object: Diese Variable repräsentiert das aktuelle Objekt oder die aktuelle Entität, mit der das Template arbeitet. Sie ermöglicht den Zugriff auf Eigenschaften und Methoden des Objekts, um dynamische Inhalte im Template zu generieren.
  3. id: Diese Variable enthält die ID des aktuellen Objekts. Sie ist besonders nützlich in Templates, die auf einzelne Objekte angewendet werden, um spezifische Daten abzurufen oder Aktionen durchzuführen.
  4. form: Wenn das Template ein Formular darstellt, steht die form Variable zur Verfügung. Diese Variable ermöglicht den Zugriff auf das Formularobjekt und dessen Felder, um beispielsweise Validierungen durchzuführen oder Daten zu verarbeiten.
  5. base_template: Diese Variable definiert das Basis-Template, das von einem spezifischen Sonata Template erweitert wird. Sie ermöglicht es Entwicklern, das Standardverhalten von Sonata anzupassen, indem sie ein benutzerdefiniertes Basis-Template festlegen.
  6. admin_pool: Diese Variable stellt den Admin-Pool dar und bietet Zugriff auf den gesamten verfügbaren Admin-Bereich. Sie ermöglicht den Zugriff auf Informationen über alle registrierten Admin-Klassen und kann zum Beispiel verwendet werden, um eine Liste der verfügbaren Admin-Objekte anzuzeigen.

In Sonata 4 stehen neben den zuvor genannten Variablen auch die value-Variable zur Verfügung. Diese Variable repräsentiert den aktuellen Wert eines bestimmten Feldes oder einer Eigenschaft des Objekts.

Hier ist ein Beispiel, wie die value-Variable im Template array.html.twig verwendet werden kann:

{% extends '@SonataAdmin/CRUD/base_show_field.html.twig' %}

{% block field %}
    {% if value is not empty %}
        {{ value | join(', ') }}
    {% endif %}
{% endblock %}
protected function configureShowFields(ShowMapper $show): void
{
	$show->add('my_attrbute', null, [
		'label' => 'Array Attribute Example',
		'template' => 'array.html.twig',
		]
	)
}
Kategorien
PHP Symfony

SQL vs. Doctrine Migrations

In Symfony und Doctrine stehen Ihnen zwei Optionen zur Verfügung, um Datenbankmigrationen zu verwalten: SQL und Doctrine-Objekte.

  1. SQL-Migrationen: Mit SQL-Migrationen schreiben Sie die Datenbankänderungen als SQL-Skripte. Dies gibt Ihnen die volle Kontrolle über den genauen SQL-Code, der ausgeführt wird. Sie können spezifische Datenbankfunktionen und -funktionen nutzen, die möglicherweise nicht direkt von Doctrine unterstützt werden. SQL-Migrationen können auch einfacher sein, wenn Sie bereits über umfangreiche SQL-Kenntnisse verfügen.

Einige Vorteile der SQL-Migrationen sind:

  • Direkte Kontrolle über den SQL-Code.
  • Nutzung spezifischer Datenbankfunktionen.
  • Einfacher für Entwickler mit umfangreichen SQL-Kenntnissen.

Einige Nachteile der SQL-Migrationen sind:

  • Möglicherweise weniger portabel, da der SQL-Code an eine bestimmte Datenbank-Engine gebunden sein kann.
  • Weniger objektorientiert und weniger Integration mit dem Doctrine ORM.
  1. Doctrine-Objekt-Migrationen: Mit Doctrine-Objekt-Migrationen verwenden Sie Doctrine-Migrationsklassen, um Ihre Datenbankänderungen zu definieren. Dies ermöglicht eine objektorientierte Herangehensweise an die Migrationen und eine bessere Integration mit dem Doctrine ORM. Sie können auf die Funktionen von Doctrine zugreifen und beispielsweise Änderungen an Ihren Datenbanktabellen über die Objektmodelle vornehmen.

Einige Vorteile der Doctrine-Objekt-Migrationen sind:

  • Objektorientierte Herangehensweise an Migrationen.
  • Bessere Integration mit dem Doctrine ORM.
  • Portabler, da Doctrine den generierten SQL-Code an die spezifische Datenbank-Engine anpasst.

Der entscheidende Nachteil von Doctrine-Objekt-Migrationen

Ein wichtiger Nachteil bei der Verwendung von Doctrine-Objekt-Migrationen besteht darin, dass bei der Erstellung der Entitäten ein Fehler auftritt, wenn ein Attribut in der Datenbank nicht vorhanden ist. Dies kann insbesondere dann zum Problem werden, wenn Sie vorhandene Datenbanktabellen haben und Änderungen an den Entitäten vornehmen, die zu Spalten hinzufügen oder entfernen.

Wenn Sie beispielsweise eine Entität mit einem bestimmten Attribut definieren und versuchen, die Datenbanktabellen basierend auf dieser Entität zu erstellen, wird Doctrine einen Fehler werfen, wenn das entsprechende Attribut in der Datenbanktabelle nicht vorhanden ist. Dies liegt daran, dass Doctrine die Entitäten und die Datenbanktabellen synchron halten möchte.

Aufgrund meiner Erfahrungen mit Migrationen würde ich immer empfehlen SQL-Migrationen zu verwenden, weil beim hinzufügen von Attributen ansonsten immer Fehler geworfen werden.

Kategorien
Symfony

GuzzleHttp loggen aller Requests nach Monolog

Um alle Requests und Responses zu loggen, die mittels Guzzle in einer Symfony Applikation getätigt werden, kann man Guzzle folger Maßen konfigurieren:

    guzzle.handler_stack_log:
      class: 'GuzzleHttp\HandlerStack'
      factory: [ GuzzleHttp\HandlerStack, create ]
      calls:
        - [ push, [ '@guzzle.middleware_log', 'log_default' ] ]

    guzzle.middleware_log:
      class: callback
      factory: [ GuzzleHttp\Middleware, log ]
      arguments:
        - '@monolog.logger.my_guzzle_log'
        - '@GuzzleHttp\MessageFormatter'

    GuzzleHttp\MessageFormatter:
      - "Request:\n{request}\nDuration: {req_time}\nResponse:\n{response}\nError:\n{error}"

Damit werden alle Logs nach zum Monolog Service monolog.logger.my_guzzle_log weitergeleitet.

Der MessageFormatter unterstützt verschiedene Formatzeichenfolgen, die Sie in Ihren Protokollen verwenden können. Hier sind fünf nützliche GuzzleHttp\MessageFormatter-Formate:

  1. Log-Eintrag, der die URL, Methode, und Status-Code umfasst: Format: "Request: {method} {uri}, Response: {code}"

Beispiel Output: "Request: GET http://example.com, Response: 200"

  1. Log-Eintrag, der die Anforderungsdauer und den Antwortstatus beinhaltet: Format: "Duration: {req_time}, Response: {code}"

Beispiel Output: "Duration: 1.123, Response: 200"

  1. Log-Eintrag, der die Anfrage, Antwort und etwaige Fehler anzeigt: Format: "Request:\n{request}\nResponse:\n{response}\nError:\n{error}"

Beispiel Output:

Request:
GET / HTTP/1.1
Host: example.com

Response:
HTTP/1.1 200 OK
Content-Length: 348
Content-Type: text/html

Error:
None

Der GuzzleHttp\MessageFormatter unterstützt eine Vielzahl von Platzhaltern, die in den Formatstrings verwendet werden können. Hier ist eine Liste einiger der verfügbaren Platzhalter:

ParameterBeschreibungBeispiel Output
{request}Vollständige HTTP-Anforderungsnachricht„GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n“
{response}Vollständige HTTP-Antwortnachricht„HTTP/1.1 200 OK\r\nContent-Length: 348\r\nContent-Type: text/html\r\n“
{ts}Timestamp„1519211809.123456“
{date_iso_8601}Aktuelles Datum im ISO 8601 Format„2023-06-03T14:30:00Z“
{date_common_log}Aktuelles Datum im Common Log Format„03/Jun/2023:14:30:00 +0000“
{host}Der Host der Anforderung„example.com“
{method}HTTP-Methode der Anforderung„GET“
{uri}URI der Anforderung„http://example.com“
{version}HTTP-Version„1.1“
{target}Anforderungsziel (Pfad + Abfragestring der URI)„/path?query=example“
{hostname}Der Hostname des lokalen Rechners„localhost“
{code}Statuscode der Antwort„200“
{phrase}Statusphrase der Antwort„OK“
{error}Error message„Error: Connection refused“
{req_header_User-Agent}Einzelner Request-Header„GuzzleHttp/6.3.3 curl/7.54.0 PHP/7.2.7“
{res_header_Content-Type}Einzelner Response-Header„text/html“
{req_headers}Kopfzeilen der Anforderung„Host: example.com\r\nUser-Agent: GuzzleHttp/6.3.3 curl/7.54.0 PHP/7.2.7\r\n“
{res_headers}Kopfzeilen der Antwort„Content-Type: text/html\r\nContent-Length: 348\r\n“
{req_body}Anforderungskörper„name=John+Doe“
{res_body}Antwortkörper„Hello, John Doe!“
{req_body_size}Größe des Anforderungskörpers„13“
{res_body_size}Größe des Antwortkörpers„15“
{curl_err}Fehlermeldung von cURL (falls vorhanden)„Couldn’t resolve host name“
{curl_info}Information von cURL (falls vorhanden)„Array ( [url] => http://example.com [content_type] => text/html [http_code] => 200 [header_size] => 228 [request_size] => 78…)“
{curl_version}cURL-Version (falls verwendet)„7.54.0“
{handler_stats}Handler spezifische Statistiken (falls verfügbar)„Array ( [total_time] => 0.152404 [namelookup_time] => 0.02914 [connect_time] => 0.049718 )“

Kategorien
Symfony

Symfony monolog Logs auf der Konsole anzeigen

Um die Monolog-Logs in der Symfony-Konsole anzuzeigen, können Sie Folgendes tun:

  1. Sie müssen zuerst sicherstellen, dass Monolog so konfiguriert ist, dass es auf der Konsole loggt. Dazu müssen Sie die config/packages/dev/monolog.yaml-Datei bearbeiten und sicherstellen, dass eine Stream-Handler-Konfiguration für die Konsole vorhanden ist.
monolog:
    handlers:
        console:
            type:   stream
            path:   "php://stdout"
            level:  debug
            channels: ["!event"]
  1. Es empfiehlt ich auch beim ausführen der Tests den Consolen Logger zu aktivieren in config/packages/test/monolog.yaml. Damit erhält man wertvolle Loginformationen beim Ausführen seiner Tests.
  1. In Ihrem Konsolenbefehl können Sie dann das Logger-Interface injizieren und verwenden, um Meldungen zu loggen. Hier ist ein einfacher Konsolenbefehl, der dies demonstriert:
namespace App\Command;

use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class YourCommand extends Command
{
    private $logger;

    public function __construct(LoggerInterface $logger)
    {
        $this->logger = $logger;

        parent::__construct();
    }

    protected function configure()
    {
        $this
            ->setName('app:your-command')
            ->setDescription('Description of your command.');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $this->logger->info('This is an info log entry.');

        // ...

        return Command::SUCCESS;
    }
}
  1. Starten Sie dann Ihren Befehl von der Konsole aus, und Sie sollten die Protokolle auf der Konsole sehen:
$ php bin/console app:your-command
[2023-06-02T12:34:56.789Z] info: This is an info log entry.

Bitte beachten Sie, dass die obige Konfiguration die Logs auf allen Kanälen außer „event“ auf „php://stdout“ ausgibt. Sie können die Kanäle an Ihre Bedürfnisse anpassen.

Ebenfalls beachten Sie bitte, dass die Protokolle nur ausgegeben werden, wenn der Log-Level Ihrer Meldung dem in der Monolog-Konfiguration festgelegten Level entspricht oder darüber liegt. Im obigen Beispiel wird alles auf „debug“-Level und darüber protokolliert.

Kategorien
Symfony

Symfony Logging Verzeichnis anpassen

Um das Verzeichnis, in dem die Logs von Symfony gespeichert werden, zu ändern, kann man die Konfigurationsoption kernel.logs_dir verwenden. Diese Option definiert standardmäßig das Verzeichnis var/log.

Um das Log-Verzeichnis zu ändern, kann man den folgenden Code in der Konfigurationsdatei hinzufügen:

parameters:
    kernel.logs_dir: '/foo/log'

In diesem Beispiel wird das Verzeichnis /foo/log als Log-Verzeichnis verwendet.

Wenn man kernel.logs_dir in der config/services.yaml überschreibt, fügt man den oben genannten Code in den parameters-Abschnitt der Konfigurationsdatei ein.

Nach der Änderung von kernel.logs_dir muss man den Cache löschen, damit die Änderungen wirksam werden:

php bin/console cache:clear

Kategorien
Symfony

Symfony mögliche Konfigurationsparameter anzeigen

Um eine alle möglichen Config Parameter eines Packetes anzuzeigen, kann man folgenden Konsolenbefehl verwenden:

php bin/console config:dump-reference doctrine_migrations

Ausgabe:

# Default configuration for extension with alias: "doctrine_migrations"
doctrine_migrations:

    # A list of namespace/path pairs where to look for migrations.
    migrations_paths:

        # Prototype
        namespace:            ~

    # A set of services to pass to the underlying doctrine/migrations library, allowing to change its behaviour.
    services:

        # Prototype
        service:              ~

    # A set of callables to pass to the underlying doctrine/migrations library as services, allowing to change its behaviour.
    factories:

        # Prototype
        factory:              ~

    # Storage to use for migration status metadata.
    storage:

        # The default metadata storage, implemented as a table in the database.
        table_storage:
            table_name:           null
            version_column_name:  null
            version_column_length: null
            executed_at_column_name: null
            execution_time_column_name: null

    # A list of migrations to load in addition to the one discovered via "migrations_paths".
    migrations:           []

    # Connection name to use for the migrations database.
    connection:           null

    # Entity manager name to use for the migrations database (available when doctrine/orm is installed).
    em:                   null

    # Run all migrations in a transaction.
    all_or_nothing:       false

    # Adds an extra check in the generated migrations to allow execution only on the same platform as they were initially generated on.
    check_database_platform: true

    # Custom template path for generated migration classes.
    custom_template:      null

    # Organize migrations mode. Possible values are: "BY_YEAR", "BY_YEAR_AND_MONTH", false
    organize_migrations:  false

    # Use profiler to calculate and visualize migration status.
    enable_profiler:      false

    # Whether or not to wrap migrations in a single transaction.
    transactional:        true
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 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
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.