phpffcms/ffcms-core

View on GitHub
src/Arch/Model.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php

namespace Ffcms\Core\Arch;

use Dflydev\DotAccessData\Data as DotData;
use Ffcms\Core\App;
use Ffcms\Core\Exception\SyntaxException;
use Ffcms\Core\Helper\Type\Any;
use Ffcms\Core\Helper\Type\Str;
use Ffcms\Core\Interfaces\iModel;
use Ffcms\Core\Traits\ModelValidator;
use Ffcms\Templex\Helper\Html\Form\ModelInterface;
use Ffcms\Templex\Helper\Html\Form\Model as TemplexModel;

/**
 * Class Model. Classic constructor of models in MVC architecture with algorithm of passing attributes from user input data.
 * @package Ffcms\Core\Arch
 */
abstract class Model extends TemplexModel implements iModel, ModelInterface
{
    use ModelValidator {
        ModelValidator::initialize as private validatorConstructor;
    }

    public $_csrf_token;

    /**
     * Model constructor. Initialize before() method for extended objects and run validator initialization
     * @param bool $csrf
     */
    public function __construct($csrf = false)
    {
        parent::__construct();
        $this->before();
        $this->validatorConstructor($csrf);
    }

    /**
     * Make any things before model is initialized
     */
    public function before()
    {
    }

    /**
     * Set attribute labels for model variables
     * @return array
     */
    public function labels(): array
    {
        return [];
    }

    /**
     * Set of model validation rules
     * @return array
     */
    public function rules(): array
    {
        return [];
    }

    /**
     * Set model data sources for input data
     * Allowed sources: get, post, file
     * @return array
     */
    public function sources(): array
    {
        return [];
    }

    /**
     * Set model property types to advanced filtering
     * Allowed types: text, html, !secure
     * Ex: ['property1' => 'text', 'property2' => 'html']
     * @return array
     */
    public function types(): array
    {
        return [];
    }

    /**
     * Validate defined rules in app
     * @return bool
     * @throws SyntaxException
     */
    final public function validate(): bool
    {
        // validate csrf token if required
        if ($this->_tokenRequired && !$this->_tokenOk) {
            App::$Session->getFlashBag()->add('warning', __('Hack attention: security token is wrong!'));
            return false;
        }
        // get all rules as array from method rules()
        $rules = $this->rules();
        // get default values of attributes
        $defaultAttr = $this->getAllProperties();

        // start validation: on this step class attribute values will be changed to input data if it valid
        $success = $this->runValidate($rules);

        // get not-passed validation fields as array
        $badAttributes = $this->getBadAttributes();
        // prevent warnings
        if (Any::isArray($badAttributes) && count($badAttributes) > 0) {
            foreach ($badAttributes as $attr) {
                if (Str::contains('.', $attr)) { // sounds like dot-separated array attr
                    $attrName = strstr($attr, '.', true); // get attr name
                    $attrArray = trim(strstr($attr, '.'), '.'); // get dot-based array path

                    $defaultValue = new DotData($defaultAttr); // load default attr

                    $dotData = new DotData($this->{$attrName}); // load local attr variable
                    $dotData->set($attrArray, $defaultValue->get($attr)); // set to local prop. variable default value

                    $this->{$attrName} = $dotData->export(); // export to model
                } else {
                    $this->{$attr} = $defaultAttr[$attr]; // just set ;)
                }
                // add message about wrong attribute to session holder, later display it
                $attrLabel = $attr;
                if ($this->getLabel($attr) !== null) {
                    $attrLabel = $this->getLabel($attr);
                }

                App::$Session->getFlashBag()->add('warning', __('Field "%field%" is incorrect', ['field' => $attrLabel]));
            }
        }

        return $success;
    }

    /**
     * Get all properties for current model in key=>value array
     * @return array|null
     */
    public function getAllProperties(): ?array
    {
        $properties = null;
        // list all properties here, array_walk sucks on performance!
        foreach ($this as $property => $value) {
            if (Str::startsWith('_', $property)) {
                continue;
            }

            $properties[$property] = $value;
        }
        return $properties;
    }

    /**
     * Cleanup all public model properties
     * @return void
     */
    public function clearProperties(): void
    {
        foreach ($this as $property => $value) {
            if (!Str::startsWith('_', $property)) {
                $this->{$property} = null;
            }
        }
    }

    /**
     * Get validation rules for field
     * @param string $field
     * @return array
     */
    final public function getValidationRule($field): array
    {
        $rules = $this->rules();
        $response = [];

        foreach ($rules as $rule) {
            if (Any::isArray($rule[0])) { // 2 or more rules [['field1', 'field2'], 'filter', 'filter_argv']
                foreach ($rule[0] as $tfield) {
                    if ($tfield == $field) {
                        $response[$rule[1]] = $rule[2];
                    } // ['min_length' => 1, 'required' => null]
                }
            } else { // 1 rule ['field1', 'filter', 'filter_argv']
                if ($rule[0] === $field) {
                    $response[$rule[1]] = $rule[2];
                }
            }
        }

        return $response;
    }
}