Kategorien
PHP PHPUnit

PhpUnit 10 Call to undefined method PHPUnit\Framework\MockObject\MockBuilder::setMethods()

In PHPUnit 10 wurde setMethods entfernt, als Teil eines Bestrebens, die Verwendung von Mocks zu vereinfachen und zu modernisieren. Die Empfehlung ist nun, stattdessen die Methoden onlyMethods oder addMethods zu verwenden, je nachdem, was in Ihrem spezifischen Fall benötigt wird.

  • Verwenden von onlyMethods: Wenn Sie onlyMethods verwenden, mocken Sie nur die Methoden, die Sie benötigen, und alle anderen Methoden der ursprünglichen Klasse werden nicht gemockt und führen ihre normalen Operationen aus. In 99% aller Fälle kann setMethods durch onlyMethods ersetzt werden.
  • Verwenden von addMethods: addMethods wird verwendet, wenn Sie neue Methoden zu Ihrem Mock hinzufügen möchten, die in der ursprünglichen Klasse nicht existieren.

Beispiel, in dem nur die Methode sentMail gemockt werden soll:

PhpUnit < 10:

$mailsMock = $this->getMockBuilder(Mail::class)
    ->setMethods(['sentMail'])
    ->disableOriginalConstructor()
    ->getMock();

PhpUnit >= 10:

$mailsMock = $this->getMockBuilder(Mail::class)
    ->onlyMethods(['sentMail'])
    ->disableOriginalConstructor()
    ->getMock();

Diese Änderung soll dazu beitragen, dass der Zweck der Mocks klarer wird und Missverständnisse bei der Verwendung vermieden werden. Es ist auch ein Schritt in Richtung einer strengeren und präziseren Mocking-Praxis, die besser definiert, welche Teile eines Objekts für den Test relevant sind.

Kategorien
Angular PHP Security

XSRF Schutz in Angular

Cross-Site Request Forgery (XSRF), auch als Session Riding bekannt, ist ein weit verbreitetes Sicherheitsproblem, das Webanwendungen betrifft, einschließlich Angular-Anwendungen. XSRF-Attacken zielen darauf ab, die Identität und Berechtigungen eines unwissenden Nutzers zu missbrauchen, um bösartige Aktivitäten durchzuführen. Angular bietet jedoch eingebaute Mechanismen zur Bekämpfung solcher Angriffe. In diesem Artikel werden wir XSRF in Angular eingehender betrachten und darüber diskutieren, wie wir uns davor schützen können.

Ablauf einer XSRF Attacke

Bei einer XSRF-Attacke sendet der Angreifer eine betrügerische Anfrage von einer Seite, der das Opfer vertraut. Der Angreifer nutzt die Tatsache aus, dass Cookies beim Senden von Anfragen an eine Site automatisch inkludiert werden, auch wenn die Anfrage von einer dritten Site ausgelöst wird. Wenn das Opfer zu diesem Zeitpunkt bei der betroffenen Anwendung angemeldet ist, wird die Anfrage als legitime Anfrage des Opfers angesehen und ausgeführt. Dies könnten zum Beispiel ein Request an einen Bank Server sein, der eine Überweisung auslöst.

Implementierung des XSRF-Schutzes in Angular

Hier sind die Schritte, die Sie befolgen sollten, um XSRF-Schutz in Ihrer Angular-Anwendung zu implementieren:

  1. HttpClientModule importieren: Sie müssen das HttpClientModule importieren in der src/app/app.module.ts und für die Requests verwenden.
import {HttpClientModule} from "@angular/common/http";
...
imports: [
  HttpClientModule,
]
  1. XSRF-Token setzen: Der Server sollte das XSRF-Token in einem Cookie setzen. Das Standardcookie, das Angular sucht, heißt XSRF-TOKEN.
  2. XSRF-Tokens in HTTP-Anfragen verwenden: Der HttpXsrfInterceptor fügt automatisch das X-XSRF-TOKEN-Header mit dem XSRF-Token aus dem Cookie hinzu. Wenn der Server das Header erhält, vergleicht er das Token mit dem, was er hat. Wenn es nicht übereinstimmt, wird die Anfrage abgelehnt.

Es ist wichtig zu beachten, dass XSRF-Schutz in Angular darauf beruht, dass der Server korrekt konfiguriert ist, um das XSRF-Token in einem Cookie zu setzen und dieses bei eingehenden Anfragen zu überprüfen.

Wenn der Backend Server einen anderen Cookie oder Header für den XRSF Token verwendet, kann dies in Angular folgender Maßen konfiguriert werden:

imports: [
  HttpClientModule,
  HttpClientXsrfModule.withOptions({
    cookieName: 'My-Xsrf-Cookie',
    headerName: 'My-Xsrf-Header',
  }),
],

Angular fügt das XSRF-Token standardmäßig nur zu den PUT, POST, DELETE und PATCH HTTP-Anfragen hinzu, da diese Operationen im Allgemeinen den Zustand des Servers ändern können und daher als potenziell unsicher angesehen werden.

GET– und HEAD-Anfragen dagegen sind idempotent und lesen nur Daten, sie führen keine Aktionen aus, die den Zustand des Servers verändern, daher fügt Angular diesen Anfragen standardmäßig kein XSRF-Token hinzu. Das ist eine gängige Praxis, um die Menge der übertragenen Daten zu reduzieren und da XSRF-Angriffe typischerweise auf Zustandsänderungen abzielen.

Beispiel Implementierung im Backend mit PHP

Hier ist ein einfaches Beispiel in PHP für das Setzen und Überprüfen eines XSRF-Tokens.

Token setzen

Wenn ein Benutzer eine Sitzung startet, erstellen und speichert man ein XSRF-Token in einem Cookie und überträgt ihn damit an das Frontend.

<?php
session_start();

if (empty($_SESSION['token'])) {
    $token = bin2hex(random_bytes(32));
    $_SESSION['token'] = $token;
    setcookie("XSRF-TOKEN", $token, time() + 3600, "/");
}

Token überprüfen

Bei eingehenden POST, PUT, DELETE oder PATCH Anfragen vom Frontend, überprüft man das XSRF-Token im Backend.

<?php
session_start();

$method = $_SERVER['REQUEST_METHOD'];
$serverToken = $_SESSION['token'];
$clientToken = $_SERVER['HTTP_X_XSRF_TOKEN'];

if (in_array($method, array('POST', 'PUT', 'DELETE', 'PATCH'))) {
    if (!isset($clientToken)) {
        die("Unauthorized request (missing X-XSRF-TOKEN header)");
    } elseif (!hash_equals($serverToken, $clientToken)) {
        die("Unauthorized request (X-XSRF-TOKEN does not match)");
    }
}
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
PHP

OpenAI ChatGPT-3 PHP Client

Mit dem folgenden Code kann man per PHP auf GPT-3 via API zugreifen. Vorraussetzung ist ein valider API Key.

<?php


// Setzen Sie Ihren OpenAI API-Zugriffsschlüssel
$api_key = '';

// API-Anfrage senden
$response = send_api_request("Wer war Pablo Picasso?", $api_key);

print_r($response);

// Funktion zum Senden einer API-Anfrage
function send_api_request($prompt, $api_key)
{
    $data = array(
        'messages' => [
            [
                "role" => 'user',
                "content" => $prompt,
            ],
        ],
        'max_tokens' => 100,
        'temperature' => 0.7,
        'model' => 'gpt-3.5-turbo'
    );

    $headers = array(
        'Content-Type: application/json',
        'Authorization: Bearer ' . $api_key
    );

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, 'https://api.openai.com/v1/chat/completions');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

    $response = curl_exec($ch);
    curl_close($ch);

    return $response;
}
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

composer ohne Memory Limit ausführen

COMPOSER_MEMORY_LIMIT=-1 composer update