5
votes

PHP - existe-t-il un moyen de différencier entre unset et null?

Considérez le code suivant:

$Test = new Test();
var_dump(isset($Test->definedButNotSet)); // false

$Test->definedButNotSet = null;
var_dump(isset($Test->definedButNotSet)); // true expected here but php returns false

Il me semble que PHP définit implicitement une variable définie sur null. Existe-t-il un moyen de contourner cela et de faire la différence entre une variable explicitement définie sur null et une variable uniquement définie, mais non définie sur une valeur?

MISE À JOUR :

Ce que je veux essentiellement voir si pendant l'exécution, la variable definedButNotSet été mise à jour ou non. Donc, mes résultats attendus pour le code suivant sont:

class Test {
    public $definedButNotSet;
}

$Test = new Test();

var_dump($Test->definedButNotSet=== null); // true
var_dump(isset($Test->definedButNotSet)); // false

$Test->definedButNotSet = null;
var_dump(isset($Test->definedButNotSet)); // false

Un cas d'utilisation réel où la différence compte en effet, et en gros, ce serait aussi mon cas d'utilisation: lors de la mise à jour des lignes d'une base de données, je voudrais mettre à jour les lignes d'une table, que l'utilisateur n'a changé que lorsqu'une méthode de mise à jour est appelée. Pour cela, je dois savoir si l'utilisateur a modifié implicitement une variable de la classe représentant la ligne du tableau ou non.

J'exécute un ORM personnalisé, qui pour le moment, échoue, si j'insère une ligne dans une base de données avec une colonne qui a une méthode default_timestamp définie comme valeur par défaut, et dans le même temps d'exécution, j'essaye à nouveau de mettre à jour la même ligne , comme la valeur de la base de données n'est pas reflétée dans mon instance de classe, ainsi lors de la mise à jour PHP lui envoie que sa valeur est nulle, ce qui n'est pas autorisé.

php

10 commentaires

essayez celui-ci: var_dump($Test->definedButNotSet);


@Orhan il renvoie toujours les mêmes valeurs ...


La réponse courte est NON. La valeur par défaut sera NULL si elle a été définie.


3v4l.org/l4faY


@AbraCadaver, il dit toujours que ma variable est nulle pour les deux cas de test. J'ai mis à jour ma question.


Je sais, la valeur DEFAULT est NULL avant même d'instancier l'objet 3v4l.org/FlrTE c'est juste comme ça.


@AbraCadaver revenons à ma question d'origine, y a-t-il un moyen de différencier une variable implicitement ou explicitement définie?


Sauf si vous le suivez vous-même, non.


des pointeurs sur la façon dont je pourrais suivre cela, tout en maintenant le support intellisense pour un ORM comme un objet de classe?


unset supprime complètement la variable de la mémoire, mais la valeur null, définit la valeur de la variable comme null


3 Réponses :


0
votes

Dans le cas des objets, vous pouvez utiliser property_exists () .

<?php
class Test {
    public $definedButNotSet;
}

$test = new Test();

var_dump(property_exists($test, 'definedButNotSet')); // bool(true)


4 commentaires

ce n'est pas là le problème. dans votre cas, vous parlez d'une variable non définie et non définie. Ce dont je parle, ce sont des variables d'objet qui sont "définies" (j'ai besoin de les définir, donc mon intellisense entre en action), mais pas définies.


@AdamBaranyai C'est toujours similaire. Je mettrai à jour ma réponse.


ce que j'essaie de faire la différence, c'est que la variable $definedButNotSet été modifiée de quelque manière que ce soit pendant l'exécution. s'il était seulement defined mais pas défini sur une valeur (y compris la valeur null ), alors je voudrais faire quelque chose avec, sinon, je voudrais faire autre chose avec lui.


Vous devrez faire quelque chose de spécial, par exemple remplacer la méthode magique __get() et garder une trace de cette propriété spécifique. Lors de l'initialisation de Test , la propriété definedButNotSet automatiquement la valeur null .



0
votes

Je me réfère à cela comme une commutation nulle et je le prédicat sur une carte de propriété interne. Par exemple:

class Test {
    public $define; // null
    public $redefine; // null

    public function define(?bool $something = null): ?bool {
        return $this->define = $this->define ?? $something;
    }

    public function redefine(?bool $something = null): ?bool {
        return $this->redefine = $something ?? $this->redefine;
    }
}

$test = function(?bool $value) {
    var_export($value ?? 'Not set!' ?: 'False!');
    echo PHP_EOL;
};

$foo = new Test();

$test($foo->define());
$test($foo->define(true));
$test($foo->define());
$test($foo->define(false));
$test($foo->define());

$test($foo->redefine());
$test($foo->redefine(true));
$test($foo->redefine());
$test($foo->redefine(false));
$test($foo->redefine());

https://3v4l.org/PFXqi


0 commentaires

1
votes

Pour contester légèrement le cadrage du problème, ce que vous souhaitez faire est de distinguer deux types d'écriture sur une même propriété: celles déclenchées automatiquement par votre ORM, et celles déclenchées manuellement par l'utilisateur. Les valeurs qui seront fournies par défaut lors de l'insertion en sont un sous-ensemble, mais les valeurs extraites de la base de données peuvent également devoir être gérées différemment de celles fournies par l'utilisateur (par exemple pour savoir si une mise à jour est nécessaire).

La façon de faire est de rendre la propriété privée et de fournir des getters et des setters, ou de simuler des accesseurs de propriété à l'aide des __get magiques __get et __set (et des annotations @property à utiliser par les IDE). Ensuite, vous pouvez stocker une carte des propriétés qui ont été écrites en dehors de votre code d'initialisation.

Une implémentation simple et probablement boguée pour montrer l'idée générale:

/**
 * This doc-block will be read by IDEs and provide auto-complete
 * @property int|null $id
 * @property string|null $name
 */
class Test {
    private ?int $id = null;
    private ?string $name = null;

    private array $updatedFields = [];

    /**
     * @internal Only to be called by the ORM
     */
    public function construct(?int $id, ?string $name) {
        $this->id = $id;
        $this->name = $name;
    }

    public function __get(string $property) {
        if ( property_exists($property, $this) ) {
             return $this->$property;
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }

    public function __set(string $property, $newValue) {
        if ( property_exists($property, $this) ) {
             $this->$property = $newValue;
             $this->updatedFields[ $property ] = true;
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }

    /**
     * Standard meaning of isset() for external code 
     */
    public function __isset(string $property) {
        if ( property_exists($property, $this) ) {
             return isset($this->$property);
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }

    /**
     * Special function for ORM code to determine which fields have changed
     */
    public function hasBeenWritten(string $property): bool {
        if ( property_exists($property, $this) ) {
             return $this->updatedFields[ $property ] ?? false;
        }
        else {
             throw new LogicError("No such property '$property'");
        }
    }
}


2 commentaires

Mon problème avec cette approche est que j'écris toutes mes fonctions setter et getter manuellement pour tous les champs d'une table, ou si j'utilise des méthodes magiques, je n'aurai pas de support intellisense: | mais sur la base de ce que j'ai recherché depuis que j'ai posé ma question, je pense qu'il n'y a pas d'autre moyen d'y parvenir en PHP, ce qui est dommage ...: (une alternative serait d'écrire un constructeur de classe, mais comme c'est un petit projet, je ne pense pas que le temps qu'il faudra le justifiera, et nous sommes également trop tard dans la phase de développement pour échanger notre ORM personnalisé avec quelque chose de plus robuste: |


Oh, je viens de remarquer le docblock en haut de votre code. Intéressant, je ne savais pas que cela fonctionnera de cette façon. Je pense que nous arrivons à une solution à mon problème, merci! J'accepterai cela comme la bonne réponse