rhosocial/yii2-base-models

View on GitHub
traits/SubsidiaryTrait.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

/**
 *   _   __ __ _____ _____ ___  ____  _____
 *  | | / // // ___//_  _//   ||  __||_   _|
 *  | |/ // /(__  )  / / / /| || |     | |
 *  |___//_//____/  /_/ /_/ |_||_|     |_|
 * @link https://vistart.me/
 * @copyright Copyright (c) 2016 - 2023 vistart
 * @license https://vistart.me/license/
 */

namespace rhosocial\base\models\traits;

use yii\base\InvalidConfigException;

/**
 * SubsidiaryTrait.
 * The Trait is used to help the model manage its subsidiary models.
 *
 * For example:
 * ```php
 * $user->addSubsidiaryClass("email", ["class" => Email::class]);
 * $email = $user->createEmail(['content' => 'i@vistart.me']);
 * $email->save();
 * ```
 * @version 2.0
 * @since 1.0
 * @author vistart <i@vistart.me>
 */
trait SubsidiaryTrait
{
    /**
     * @var string[] Subsidiary map.
     * Array key represents class alias,
     * array value represents the full qualified class name corresponds to the alias.
     *
     * For example:
```php
public $subsidiaryMap = [
    'Profile' => [
        'class' => 'app\models\user\Profile',
    ],
];
```
     *
     * The other elements will be taken if subsidiary configuration does not specify.
     * If you want to create subsidiary model and the class is not found, the array elements will be taken.
     */
    public array $subsidiaryMap = [];

    /**
     * Add subsidiary class to map.
     * @param ?string $name Subsidiary name, case-insensitive.
     * @param array|string|null $config If this parameter is string, it will be regarded as class name.
     * If this parameter is array, you should specify `class`, and the class should be existed.
     * @return boolean True if the class added.
     * @throws InvalidConfigException throws if subsidiary name is not specified or class is not
     * specified.
     */
    public function addSubsidiaryClass(?string $name, array|string|null $config): bool
    {
        if (empty($name)) {
            throw new InvalidConfigException('Subsidiary name not specified.');
        }
        $name = strtolower($name);
        if (!is_array($config)) {
            if (is_string($config) && !empty($config)) {
                $this->subsidiaryMap[$name] = ['class' => $config];
            } else {
                throw new InvalidConfigException('Subsidiary class not specified.');
            }
        } else {
            if (isset($config['class']) && class_exists($config['class'])) {
                $this->subsidiaryMap[$name] = $config;
            } else {
                throw new InvalidConfigException('Subsidiary class not specified.');
            }
        }
        return true;
    }

    /**
     * Remove subsidiary.
     * @param string $name Subsidiary name, case-insensitive.
     * @return boolean
     */
    public function removeSubsidiary(string $name): bool
    {
        $name = strtolower($name);
        if (array_key_exists($name, $this->subsidiaryMap)) {
            unset($this->subsidiaryMap[$name]);
            return true;
        }
        return false;
    }

    /**
     * Get subsidiary class according name.
     * @param string $name Subsidiary name, case-insensitive.
     * @return string|null
     */
    public function getSubsidiaryClass(string $name): ?string
    {
        $name = strtolower($name);
        if (array_key_exists($name, $this->subsidiaryMap) && array_key_exists('class', (array)$this->subsidiaryMap[$name])) {
            return class_exists($this->subsidiaryMap[$name]['class']) ? $this->subsidiaryMap[$name]['class'] : null;
        }
        return null;
    }

    /**
     * Check whether the user has a subsidiary model.
     * @param string $name Subsidiary name, case insensitive.
     * @return bool
     */
    public function hasSubsidiary(string $name): bool
    {
        $class = $this->getSubsidiaryClass($name);
        if (empty($class)) {
            return false;
        }
        $query = $class::find();
        if (!method_exists($query, 'createdBy')) {
            return false;
        }
        return $query->createdBy($this)->exists();
    }

    /**
     * Get subsidiaries.
     * @param string $name Subsidiary name, case-insensitive.
     * @param string $limit
     * @param int $page
     * @return ?array
     */
    public function getSubsidiaries(string $name, string $limit = 'all', int $page = 0): ?array
    {
        $class = $this->getSubsidiaryClass($name);
        if (empty($class)) {
            return null;
        }
        $query = $class::find();
        if (!method_exists($query, 'createdBy')) {
            return null;
        }
        return $query->createdBy($this)->page($limit, $page)->all();
    }

    /**
     * @param $name
     * @param $arguments
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        if (str_starts_with(strtolower($name), "create")) {
            $class = strtolower(substr($name, 6));
            $config = (isset($arguments) && isset($arguments[0])) ? $arguments[0] : [];
            return $this->createSubsidiary($class, $config);
        }
        return parent::__call($name, $arguments);
    }

    /**
     * Find existed, or create new model.
     * If model to be found doesn't exist, and $config is null, the parameter
     * `$condition` will be regarded as properties of new model.
     * If you want to know whether the returned model is new model, please check
     * the return value of `getIsNewRecord()` method.
     * @param string $className Full qualified class name.
     * @param array $condition Search condition, or properties if not found and
     * $config is null.
     * @param array $config new model's configuration array. If you specify this
     * parameter, the $condition will be skipped when created one.
     * @return [[$className]] the existed model, or new model created by specified
     * condition or configuration.
     */
    public function findOneOrCreate($className, $condition = [], $config = null)
    {
        $entity = new $className(['skipInit' => true]);
        if (!isset($condition[$entity->createdByAttribute])) {
            $condition[$entity->createdByAttribute] = $this->getGUID();
        }
        $model = $className::findOne($condition);
        if (!$model) {
            if (!is_array($config)) {
                $config = $condition;
            }
            $model = $this->create($className, $config);
        }
        return $model;
    }

    /**
     * Create new entity model associated with current user. The model to be created
     * must be extended from [[BaseBlameableModel]], [[BaseMongoBlameableModel]],
     * [[BaseRedisBlameableModel]], or any other classes used [[BlameableTrait]].
     * if $config does not specify `hostClass` property, self will be assigned to.
     * @param string $className Full qualified class name.
     * @param array $config name-value pairs that will be used to initialize
     * the object properties.
     * @param boolean $loadDefault Determines whether loading default values
     * after entity model created.
     * Notice! The [[\yii\mongodb\ActiveRecord]] and [[\yii\redis\ActiveRecord]]
     * does not support loading default value. If you want to assign properties
     * with default values, please define the `default` rule(s) for properties in
     * `rules()` method and return them by yourself if you don't specified them in config param.
     * @param boolean $skipIfSet whether existing value should be preserved.
     * This will only set defaults for attributes that are `null`.
     * @return [[$className]] new model created with specified configuration.
     */
    public function create($className, $config = [], $loadDefault = true, $skipIfSet = true)
    {
        if (!isset($config['hostClass'])) {
            $config['hostClass'] = static::class;
        }
        if (isset($config['class'])) {
            unset($config['class']);
        }
        $entity = new $className($config);
        $entity->setHost($this);
        if ($loadDefault && method_exists($entity, 'loadDefaultValues')) {
            $entity->loadDefaultValues($skipIfSet);
        }
        return $entity;
    }

    /**
     * Create subsidiary model.
     * @param string $name Subsidiary name, case-insensitive.
     * @param array $config Subsidiary model configuration array.
     * @return mixed
     */
    public function createSubsidiary(string $name, array $config): mixed
    {
        if (empty($name)) {
            return null;
        }
        $className = '';
        if (class_exists($name)) {
            $className = $name;
        } elseif (array_key_exists($name, $this->subsidiaryMap)) {
            $className = $this->getSubsidiaryClass($name);
        } else {
            return null;
        }
        return $this->create($className, $config);
    }
}