Categories
Software Development Web Development

PHP Enumeration classes vs fake enumerations what to use.

I am growing to HATE PHP ENUMS. Why? Because if you forget to call ->value you get BS errors about cannot convert object to string. It is just another un-needed step. Enums sounded cool at first but fuq the BS with calling ->value, it is entirely too easy to forget.

Enumeration classes in PHP are a new feature as of version 8.1 Fake enumerations were what we did before this new feature. A fake enumeration was just a class with constants, the syntax to refer to them was the same minus ->value. See my other article if you don’t know what fake PHP enumerations are Php Backed Enums don’t forget to call value

TLDR

Use plain classes with constants if any of the values will repeat. Use Enumerations when each value is always unique. Keep reading to find out WTF I am talking about.

WTF are you talking about?

The story

The new Enumeration classes have a limitation on them that each “case” must have an unique value, it is just how it works.
This is only a problem when you are using Enumerations more like the old fake constant enumerations, because you can’t give two cases the same value. If you are using ints you run into this issue more quickly.

Let me show an example.

I have often used this pattern to help eliminate bugs from typing, it also helps you remember what values are possible for something. Below is the values allowed for an image for some sort of upload.

class ImageDataEnum
{
    const HEIGHT = 'height';
    const SIZE_STRING = 'size';
    const IMAGE_URL = 'image_url';
    const WIDTH = 'width';
}

This way I know what values are allowed in the upload and I don’t get errors from typing.This would convert to an Enumeration perfectly.

I have dysgraphia and dyslexia so this is something I fight constantly, it is also why I hate Linux command line. Basically my brain gets the letters out of order sometimes when I type or write or leaves entire letters out or adds them and my eyes don’t see it for some odd reason sometimes. I couldn’t take notes in school because it turned into jibberish word soup.

But I also like to use them to store info that won’t change, like maximum image size. This is a use case that won’t work with PHP Enumerations, probably one of the only ones I can think of.

Bad code

You made a real mess of that code fella

This kind of code won’t work.

enum ImageSizeEnum: int
{

    case BLOG_IMAGE_HEIGHT = 600;
    case BLOG_IMAGE_WIDTH = 600;
    case TWITTER_LARGE_IMAGE_HEIGHT = 600;
    case TWITTER_LARGE_IMAGE_WIDTH = 1200;
    case PROFILE_PICTURE_HEIGHT = 180;
    case PROFILE_PICTURE_WIDTH = 180;
    case PROFILE_THUMB_HEIGHT = 55;
    case PROFILE_THUMB_WIDTH = 55;
    case USER_THUMB_HEIGHT = 150;
    case USER_THUMB_WIDTH = 150;
    case USER_IMAGE_HEIGHT = 600;
    case USER_IMAGE_WIDTH = 600;

}

Notice that while each case has a unique name, they do not have unique values. The code above will give you an error because the values for the cases is repeated. If you are using PHPStorm then it is screaming at you right now with red underlines and error warnings etc. LOLOL

PHP Storm is all like…

The docs say the following

In PHP, Enums are a special kind of object. The Enum itself is a class, and its possible cases are all single-instance objects of that class. That means Enum cases are valid objects and may be used anywhere an object may be used, including type checks

In order to do what is intended in the code above (a list of constants to be used in code later) this has to be converted back to a class with constants. It looks so similar it is crazy.

Just convert really quickly

Ok code

This code will work.

class ImageSizeEnum
{

    const BLOG_IMAGE_HEIGHT = 600;
    const BLOG_IMAGE_WIDTH = 600;
    const TWITTER_LARGE_IMAGE_HEIGHT = 600;
    const TWITTER_LARGE_IMAGE_WIDTH = 1200;
    const PROFILE_PICTURE_HEIGHT = 180;
    const PROFILE_PICTURE_WIDTH = 180;
    const PROFILE_THUMB_HEIGHT = 55;
    const PROFILE_THUMB_WIDTH = 55;
    const USER_THUMB_HEIGHT = 150;
    const USER_THUMB_WIDTH = 150;
    const USER_IMAGE_HEIGHT = 600;
    const USER_IMAGE_WIDTH = 600;

}

With the fake enum you leave off the ->value part when you need the value as noted in this article. Php Backed Enums don’t forget to call value

Now I can have constants that have the same value and PHP won’t barf errors on me.

kitten frew up meme
I make PHP barf.

You can also do something very similar in Javascript if you are interested check out this article. Faking Enumerations with Vanilla javascript

Summary

So basically if you need to use the same value with a different name, then you need the old fashioned PHP fake enumerations. If your use case has it so that every named case has a unique value then use Enumerations.

 

Categories
Web Development Web Security

Faking Enumerations with Vanilla javascript

What is an Enumeration?

An Enumeration is a way to create a limited list of options to choose from.This is useful for keeping a list of field names for a form so you can use javascript to animate something for example.

Having a limited list of options is helpful so that you can eliminate bugs due to misspellings (very common in Javascript UI programming).

A limited list also helps so that you can just type and your IDE gives you suggestions to jog your memory of the available options so you don’t have to dive into code.

See the limitations section.

Javascript has no Enums yet

Javascript has no such concept as an enumerated class… yet( the keyword enum is reserved so maybe in the future). Heck it is 2021 and PHP just got Enum classes.  While it does allow class level variables they are defined in the most funky way inside the constructor with this keyword.

Uhm, wait… what?

I say funky because with most other languages you define variables at the class level, then instantiate them ( give them a starting value ) inside the constructor for example. Just be glad you don’t have to use the old syntax What does prototypical Javascript look like?

So to define class level variables in Javascript you need to do so inside the constructor using the this keyword. The reason for this is how the Javascript prototype system works.

class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
Before Javascript classes… remembering this

Now anywhere inside the class you can get or set the value by this.height or this.width. You can’t set any constants like this though. Constants have to be defined outside the class if you want to use them inside a class, in all methods/functions. You can define a constant inside a function ( aka constructor ) but it is limited to the function in scope just like the let keyword.

But what if you want something like a list of constants or values that can be used? For example I like to keep my form field id’s inside an Enum to be able to easily refer to the field I need, but how can I do this with Javascript?

The answer

The easiest way I have come up with is to NOT USE a class at all. Instead I just use a simple file with a constant set to an object with a list of values. Sure you could just make a list of constants, but there are downsides to that. For one you would need to export them in order to import and use them.

I prefer to create a constant set to a literal object value inside of a single file, like this.

const MEDIA_FORM_FIELDS_ENUM = {
    ALLOW_COMMENTS: 'allow_comments',
    COLLECTION: 'collection',
    CONTENT_RATING: 'content_rating',
    DESCRIPTION: 'description',
    HASHTAGS: 'hashtags',
    PUBLISHED: 'published',
    REUSE_TYPE: 'reuse_type',
    TITLE: 'title'
};

export {MEDIA_FORM_FIELDS_ENUM};

Note the export.

Then I use it like this in my form or form fragment in this case.

Note the import.

import {MEDIA_FORM_FIELDS_ENUM as fields} from "../enums/MediaFormFieldsEnum";

class MediaOptions {
    static getMediaOptions(mediaType) {
        let collection = fields.COLLECTION;
        let comments = fields.ALLOW_COMMENTS;
        let description = fields.DESCRIPTION;
        let hashtags = fields.HASHTAGS;
        let published = fields.PUBLISHED;
        let reuse = fields.REUSE_TYPE;
        let title = fields.TITLE;

        return `
        <div id="media-options" class="container">
            
            <div class="form-group">
             <label for="content-rating" >${mediaType} rating</label>
               <select name="content-rating" id="content-rating" class="form-control" >
                  <option selected value="rating-everyone" id="rating-everyone" >Everyone</option>
                  <option  value="rating-mature" id="rating-mature">Mature</option>
                  <option value="rating-xrated" id="rating-xrated" >Adult rated-x</option>
               </select>
            </div>
         
          <div class="form-group">
            <label for="${title}">${mediaType} Title</label>
            <input type="text" class="form-control" id="${title}" name="${title}">
          </div>
          
          <div class="form-group">
            <label for="${description}" >${mediaType} Description</label>
            <textarea rows="5" id="${description}" name="${description}" 
            placeholder="describe the image in 200 characters" class="form-control" ></textarea>
          </div>
          
          <div class="form-group">
                <label for="${hashtags}" >${mediaType} Hashtags</label>
                <textarea rows="2" id="${hashtags}" name="${hashtags}"
                 placeholder="separate hashtags with space" class="form-control" ></textarea>
          </div>
             
          <div class="form-group">
            <label for="${collection}">${mediaType} Collection</label>
            <input type="text" class="form-control" id="${collection}" name="${collection}">
          </div>
          
          <div class="form-row">Published/visible status</div>
          <div class="form-check">
            <input class="form-check-input" type="radio" name="${published}"
             id="${published}" value="published" checked>
            <label class="form-check-label" for="${published}">
             Published ( visible to others )
            </label>
          </div>
          <div class="form-check">
            <input class="form-check-input" type="radio" name="${published}"
             id="unpublished" value="unpublished">
            <label class="form-check-label" for="unpublished">
             Un-Published ( visible to only you )
            </label>
          </div>
          
          <div class="form-group">
             <label for="${comments}">Allow comments</label>
               <select name="${comments}" id="${comments}" class="form-control" >
                  <option value="followers" >Buyers only/no one/private</option>
                  <option selected value="everyone" >Everyone & Buyers</option>
                  <option value="followers" >Followers & Buyers</option>
               </select>
           </div>
             
          <div class="form-group">
             <label for="${reuse}" >Allow reuse</label>
               <select name="${reuse}" id="${reuse}" class="form-control" >
                  <option selected value="none" id="reuse-none" >None/private (me only)</option>
                  <option  value="free" id="reuse-free">Free</option>
                  <option value="credits" id="reuse-credits" >Credits</option>
               </select>
            </div>
           
        </div>
        `;
    }
}

export {MediaOptions}

That is a lot of code. Note it is HTML inside of a Javascript Literal. I’ll write another article about creating templates with Javascript literals later. For now note how I imported it and used it. I could have just called the fields.OPTIONS but that is longer than a variable name.

I use the above code by importing it into yet another file that builds a whole form but only when called. Like I said I’ll have to write an article about the Javascript Literals, because wow they are handy.

Vanilla javascript might be a little more work, but in the end when something doesn’t work you know exactly why and exactly where to look. And if it is a bug… IT IS YOUR BUG and you can quickly fix it and move right along.

Limitations

Javascript is a real screwy language with lots of limitations and quirks. Many don’t make a lick of sense, unless you have the unlimited free time to dig deeply into the internet to dig deeply into how the Interpreter works.

The above was working fine… until I tried to use it in an Object. When I try to use something like

let stupidObj = { ImageDataEnum.ALLOW_CONTENT_RATING_CHANGE : 'no', 'test' : 'value' };

But this does not work. You will get an error of some type or another. The error I got was something like ” , found expecting ; ” and then a lot of lines of barfarony.

Javascript interpreter was all like…

baby threw up meme
The limitations are real.

So I figured ok maybe store it in a variable and then try that.

const tester = ImageDataEnum.ALLOW_CONTENT_RATING_CHANGE;
let stupidObj = { tester : 'no', 'test' : 'value' };

That doesn’t work either. The interpreter doesn’t interpret the const tester as the tester I wanted to use as the object key so it outputs something different.
The idea here is I want to store all of the key names in a constant and use them later to build an object. This way I for sure know I will spell the key names correctly and I don’t have to remember them all, my IDE can show me the list.  This way I can request some data with AJAX and set the values for an object, then use the object to manipulate the values or use them.

Using the fake enums like this works perfectly fine.


import {JsCollection} from "./JsCollection";
import {ImageDataEnum} from "../enums/ImageDataEnum";
import {Utils} from "./Utils";

class ImageData {

    constructor(imageData) {
        this.imageData = new JsCollection();
        this.setValues(imageData);
    }

    getAllowComments(){
        return this.imageData.getElementValue(ImageDataEnum.ALLOW_COMMENTS);
    }

    getAllowResale(){
        return this.imageData.getElementValue(ImageDataEnum.ALLOW_RESALE);
    }

    getAllowContentRatingChange(){
        return this.imageData.getElementValue(ImageDataEnum.ALLOW_CONTENT_RATING_CHANGE);
    }

    getAltText(){
        return this.imageData.getElementValue(ImageDataEnum.ALT_TEXT);
    }

    getCollectionName(){
        return this.imageData.getElementValue(ImageDataEnum.COLLECTION_NAME);
    }

    getContentRating(){
        return this.imageData.getElementValue(ImageDataEnum.CONTENT_RATING);
    }

    getCreationDatetime(){
        return this.imageData.getElementValue(ImageDataEnum.CREATION_DATETIME);
    }

    getDescription(){
        return this.imageData.getElementValue(ImageDataEnum.DESCRIPTION);
    }

    getFileUrl(){
        return this.imageData.getElementValue(ImageDataEnum.IMAGE_FILE_URL);
    }

    getHashTags(){
        return this.imageData.getElementValue(ImageDataEnum.HASHTAGS);
    }

    getImageFileData(){
        return this.imageData.getElementValue(ImageDataEnum.IMAGE_DATA_FILE);
    }

    getImageId(){
        return this.imageData.getElementValue(ImageDataEnum.IMAGE_ID);
    }

    getLanguageCode(){
        return this.imageData.getElementValue(ImageDataEnum.LANGUAGE_CODE);
    }

    getLastEditDatetime(){
        return this.imageData.getElementValue(ImageDataEnum.LAST_EDIT_TIMESTAMP);
    }

    getPublishedStatus(){
        return this.imageData.getElementValue(ImageDataEnum.PUBLISHED_STATUS);
    }

    getTitle(){
        return this.imageData.getElementValue(ImageDataEnum.TITLE);
    }

    getVisibility(){
        return this.imageData.getElementValue(ImageDataEnum.VISIBILITY);
    }

    setAllowComments(allowComments){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.ALLOW_COMMENTS, allowComments);
    }

    setAllowResale(allowResale){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.ALLOW_RESALE, allowResale);
    }

    setAltText(altText){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.ALT_TEXT, altText);
    }

    setCollectionName(collectionName){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.COLLECTION_NAME, collectionName);
    }

    setContentRating(contentRating){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.CONTENT_RATING, contentRating);
    }

    setCreationDatetime(datetime){

    }
    setDescription(description){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.DESCRIPTION, description);
    }

    setHashtags(hashtags){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.HASHTAGS, hashtags);
    }

    setLastEditDatetime(datetime){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.LAST_EDIT_TIMESTAMP, datetime);
    }
    setImageId(id){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.IMAGE_ID, id);
    }

    setImageFileData(fileData){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.IMAGE_DATA_FILE, fileData);
    }

    setImageFileUrl(fileUrl){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.IMAGE_FILE_URL, fileUrl);
    }

    setLanguageCode(languageCode){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.LANGUAGE_CODE, languageCode);
    }

    setPublishedStatus(publishedStatus){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.PUBLISHED_STATUS, publishedStatus);
    }

    setTitle(title){
        this.imageData.addOverrideNamedProperty(ImageDataEnum.TITLE, title);
    }

    /**
     * used internally to set the imageData values, but JS sucks and has no
     * idea what private or protected is
     * @param imageData
     */
    setValues(imageData){

        this.setAllowComments(Utils.getArrayValue(imageData, ImageDataEnum.ALLOW_COMMENTS, ''));
        this.setAllowResale(Utils.getArrayValue(imageData, ImageDataEnum.ALLOW_RESALE, ''));
        this.setAltText(Utils.getArrayValue(imageData, ImageDataEnum.ALT_TEXT, ''));
        this.setCollectionName(Utils.getArrayValue(imageData, ImageDataEnum.COLLECTION_NAME));
        this.setContentRating(Utils.getArrayValue(imageData, ImageDataEnum.CONTENT_RATING, ''));
        this.setCreationDatetime(Utils.getArrayValue(imageData, ImageDataEnum.CREATION_DATETIME, ''));
        this.setDescription(Utils.getArrayValue(imageData, ImageDataEnum.DESCRIPTION, ''));
        this.setHashtags(Utils.getArrayValue(imageData, ImageDataEnum.HASHTAGS, ''));
        this.setImageId(Utils.getArrayValue(imageData, ImageDataEnum.IMAGE_ID, ''));
        this.setImageFileData(Utils.getArrayValue(imageData, ImageDataEnum.IMAGE_DATA_FILE, ''));
        this.setImageFileUrl(Utils.getArrayValue(imageData,ImageDataEnum.IMAGE_FILE_URL, ''));
        this.setLanguageCode(Utils.getArrayValue(imageData, ImageDataEnum.LANGUAGE_CODE, ''));
        this.setLastEditDatetime(Utils.getArrayValue(imageData, ImageDataEnum.LAST_EDIT_TIMESTAMP, ''));
        this.setPublishedStatus(Utils.getArrayValue(imageData, ImageDataEnum.PUBLISHED_STATUS, ''));
        this.setTitle(Utils.getArrayValue(imageData, ImageDataEnum.TITLE, ''));
    }

}

export {ImageData};

Using the above I can request an images data from the server using AJAX. Then I pass the JSON Object, which was a PHP multidimensional array with keys and values converted to JSON, to this setValues() method.

I can then call the getters to get a value, it will either be a value or an empty string if nothing existed.

With this setup I can set default values when none exist. I also have enums which hold the values I will add as defaults to this later.

So as you can see this is highly useful. This way I can use the Enum anywhere I need a string and I don’t have to worry about spelling. If I do misspell something, I change it one time in one location and I am done. Otherwise I’d have to hunt down all the locations in the text etc. where I had hard coded a value.

But the limitation is Javascript is like WTF is this when you try to use them in Object literals. This is the only place I have found that they don’t work so far.

Dictating like…

It seems like the Javascript interpreter is trying to call a function when it sees the Enum constant reference inside an object literal.  But the rest of the time it works 100% fine. I only wanted this for testing so I’ll move on and not try using it inside object literals for testing LOL.

The work around

So I kept digging until I found a work around, a way to keep using my Fake enums and be able to build an object using the enums as the key names.

You need to use Object.defineProperty() to set a propertyusing the Enum and you get it back using Object.getOwnProperty(). I’ll update this article later when I have the time .

Links

Mozilla Developer Network Javascript class info

Mozilla Developer Network Javascript const info

Mozilla Developer Network javascript literal info

While working on this article I found this excellent article about using Enums in Javascript.

Categories
Software Development Web Development

Php Backed Enums don’t forget to call value

The one thing I don’t like about new PHP enums is, if you forget to call ->value you get exceptions “object can’t be converted to string” It is entirely too easy to forget to call ->value.

This means in places where I refactor code I have to remember to call ->value. Hence the article title “Php Backed Enums don’t forget to call value”

It is really easy to forget to call ->value when using these new Enums.

Well thanks to my IDE PhpStorm, I caught this error before it happened to me… in most places… most times. Nah not really I forget to call ->value all the time.

I like the concept of having an Enum class as up until version PHP 8.1 you had to create class constants and pretend they were real Enums.

Old php enums

Old PHP Enums Example

Here is how we used to do PHP Enums for forever until version 8.1

class ImageDataEnum
{
    const HEIGHT = 'height';
    const SIZE_STRING = 'size';
    const IMAGE_URL = 'image_url';
    const WIDTH = 'width';
}

And to use that in any code you simply did the following where you needed a value.

$height = ImageDataEnum::HEIGHT;

And inside $height would be the string “height” you could use this to make sure a value exists without having to spell it out every time, reducing the likelihood of bugs. This is very straight forward and easy. You can still add constants to Enum classes and use them, but it feels better using case instead.

New Enums

A backed enum looks like this. Note const is now case, class is now enum, but the rest is about the same.

enum ImageDataEnum: string
{
    case HEIGHT = 'height';
    case SIZE_STRING = 'size';
    case IMAGE_URL = 'image_url';
    case WIDTH = 'width';
}

Notice the word “string” you can use int or string but not a combination of both. Backed Enums Docs here.

Now to use the new Enums like the code above you do like this

$height = ImageDataEnum::HEIGHT->value;

Otherwise $height will be an object, one that contains  handy built in methods try() and tryFrom(). See the doc links for more info on that. You can also define your own methods.

But if you fail to call ->value and you try to use this for a string comparison you will get oopsies. You can use the IDE to hunt down all cases of the old class type enums.

//this won't work
if('height' === ImageDataEnum::HEIGHT ){
 //code to do stuff in here
}

The above will result in an error telling you the comparison is not possible. You can’t compare a string to an object.

//this will work
if('height' === ImageDataEnum::HEIGHT->value ){
 //code to do stuff in here
}

You can also call ImageDataEnum::HEIGHT->name which will return HEIGHT. So you can get the name and value using those methods.

Another nice thing about the new Enum classes is they are full on classes, you can add methods to them if you want. Like checking if a value matches any of the case values or whatever your use case is.

Enums are really handy for limiting what values can be entered by users and checking against them. Another good use I have found is creating a list of options for a Database table column.

Here is an example of a column in one of my tables that stores a medias content rating type. The system later uses this in many places to make sure that the media is of this type or that the user wants to see this type of media.

enum ContentRatingsEnum: string
{
    case EVERYONE = 'everyone';
    case MATURE = 'mature';
    case RATED_X = 'rated-x';
}

This column in a media table can only contain these values and users can only select from these values as their content preference type. This is helpful because I don’t have to type those strings in 100,000 places and when I need to change one I simply refactor with my IDE features.

Here is an excellent video that just came out about PHP ENUM’s the start of the video is anyways.