1

I have an array that is actually a tree array:

array:2 [▼
  0 => array:7 [▼
    "id" => 36
    "attribute_key" => "amount"
    "attribute_value" => "Amount"
    "input_type_id" => 3
    "is_required" => 1
    "parent_id" => null
  ]
  1 => array:8 [▼
    "id" => 37
    "attribute_key" => "products"
    "attribute_value" => "Products"
    "input_type_id" => 7
    "is_required" => 1
    "parent_id" => null
    "event" => null
    "children" => array:2 [▼
      0 => array:7 [▼
        "id" => 38
        "attribute_key" => "product_name"
        "attribute_value" => "Product Name"
        "input_type_id" => 1
        "is_required" => 1
        "parent_id" => 37
      ]
      1 => array:7 [▼
        "id" => 39
        "attribute_key" => "price"
        "attribute_value" => "Price"
        "input_type_id" => 3
        "is_required" => 1
        "parent_id" => 37
      ]
    ]
  ]
]

and I would like to get output something like this:

[
     'amount' => 'required',
     'products.*.product_name' => 'required',
     'products.*.price' => 'required|numeric',
]

My data is highly dynamic and I'd like to create validation rules for Laravel.

Here is what do I have:

class EventRules
{

    protected $rules = [];

    public function rules(array $attributes) : array
    {
        foreach ($attributes as $attribute) {
            $this->addRules($attribute);
        }

        return $this->rules;
    }

    public function addRules($attribute) : void
    {
          if (isset($attribute['children'])) {
            $this->rules($attribute['children']);
            return;
        }

        $attributeKey = $attribute['attribute_key'];

        $rule = '';

        $rule .= $this->addRequiredRule($attribute);
        $rule .= $this->addFieldTypeRule($attribute);

        $this->rules[$attributeKey] = $rule;
    }

    protected function addRequiredRule($attribute) : string
    {
        $rule = '';

        if ($attribute['is_required'] === 1) {
            $rule .= 'required|';
        }

        // The rest will be here..


        return $rule;
    }

    protected function addFieldTypeRule($attribute) : string
    {
        $rule = [
            InputTypeEnum::STRING_TYPE => 'string',
            InputTypeEnum::INTEGER_TYPE => 'integer',
            InputTypeEnum::DECIMAL_TYPE => 'numeric',
            InputTypeEnum::DATE_TYPE => 'date',
        ];

        return $rule[$attribute['input_type_id']];
    }
}

Anyway, I stuck with creating a rule key (with *). I am aware I need a recursion which is something I use, but still not sure how to handle the rest.

Thanks.

1 Answer 1

1

I figure it out and make some starting point:

// Enum class for field types
final class InputTypeEnum {
    const STRING_TYPE = 1;
    const INTEGER_TYPE = 2;
    const DECIMAL_TYPE = 3;
    const DATE_TYPE = 4;
    const DATE_EMAIL = 7;
}

// Here is class to flat the rules.

class RulesFlattener {

    const ATTRIBUTE_KEY = 'attribute_key';
    const CHILD_KEY = 'children';

    private $input;
    private $output = [];

    // This array keeps map to translate rules
    private $availableRules = [
        'is_required' => 'required',
        'input_type_id' => [
            InputTypeEnum::STRING_TYPE => 'string',
            InputTypeEnum::INTEGER_TYPE => 'integer',
            InputTypeEnum::DECIMAL_TYPE => 'numeric',
            InputTypeEnum::DATE_TYPE => 'date',
            InputTypeEnum::DATE_EMAIL => 'email',
        ]
    ];

    public function __construct($input) {
        $this->input = $input;
    }

    private function extractRules($row) {
        $rules = [];

        foreach($row as $k => $v) {
            if(isset($this->availableRules[$k])) {

                $mappedRule = $this->availableRules[$k];

                if(is_array($mappedRule)) {
                    $rule = $mappedRule[$v] ?? null;
                } else {
                    $rule = $mappedRule;
                }

                $rules[] = $rule;
            }
        }

        return array_unique($rules);
    }

    public function parse() {
        return $this->parseRow($this->input);
    }

    private function parseRow($input, $parentKey = null) {
        $output = [];
        foreach ($input as $row) {

            // Keep name of current attribute (for recursion)
            $currentAttribute = $row[self::ATTRIBUTE_KEY] ?? null;

            // If you want get more nested rules like product.*.photos.*.url use this:
            // $currentAttribute = ( $parentKey ? $parentKey.'.*.':'') . $row[self::ATTRIBUTE_KEY] ?? null;

            foreach($row as $k => $v) {
                switch($k) {

                    case self::ATTRIBUTE_KEY:
                        $rules = $this->extractRules($row);
                        $output[($parentKey?$parentKey.'.*.':'').$v] = implode('|', $rules);
                        break;

                    case self::CHILD_KEY:
                        $output = array_merge($output, $this->parseRow($row[$k], $currentAttribute));
                        break;

                }
            }
        }
        return $output;
    }

}


Now You can use it as:

$dataIn = [
    [
        "id" => 36,
        "attribute_key" => "amount",
        "attribute_value" => "Amount",
        "input_type_id" => 3,
        "is_required" => 1,
        "parent_id" => null,
    ],
    [
        "id" => 37,
        "attribute_key" => "products",
        "attribute_value" => "Products",
        "input_type_id" => 7,
        "is_required" => 1,
        "parent_id" => null,
        "event" => null,
        "children" => [
            [
                "id" => 38,
                "attribute_key" => "product_name",
                "attribute_value" => "Product Name",
                "input_type_id" => 1,
                "is_required" => 1,
                "parent_id" => 37,
            ],
            [
                "id" => 39,
                "attribute_key" => "price",
                "attribute_value" => "Price",
                "input_type_id" => 3,
                "is_required" => 1,
                "parent_id" => 37,
            ]
        ]
    ]
];

$flat = new RulesFlattener($dataIn);

$rules = $flat->parse();

and I get this output:

Array
(
    [amount] => numeric|required
    [products] => email|required
    [products.*.product_name] => string|required
    [products.*.price] => numeric|required
)
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.