File: D:/HostingSpaces/SBogers10/reiskick.komma.nl/vendor/komma/kms/src/Core/Tree/TreeService.php
<?php namespace Komma\KMS\Core\Tree;
use Komma\KMS\Core\AbstractModelHandler;
use Komma\KMS\Core\Attributes\Attribute;
use Komma\KMS\Core\Tree\NestedSets\Nodes\TreeModelInterface;
use Komma\KMS\Sites\SiteServiceInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Closure;
class TreeService extends AbstractModelHandler implements TreeServiceInterface
{
/** @var SiteServiceInterface $siteService */
protected $siteService;
/** @var string $classModelName */
protected $classModelName;
/**
* @param string $className
*/
public function setClassModelName(string $className)
{
$this->classModelName = $className;
}
/**
* Return the models represented by modelClassName as a TreeResource.
* Can be converted to json for api calls
*
* @param TreeModelInterface $rootModel
* @return TreeResource
*/
public function getStructure(TreeModelInterface $rootModel)
{
$resource = new TreeResource($rootModel);
return $resource;
}
/**
* @param $dataStructure
*/
public function setStructure($dataStructure) //TODO Type hint argument
{
$this->makeArrayChildrenChildOfParent($dataStructure['children'], $dataStructure);
}
/**
* Make sure the classModelName variable was set to a Model child
*
* @return mixed|void
*/
public function checkClassModelNameSet()
{
if (!is_a($this->classModelName, Model::class, true)) {
throw new \RuntimeException('The class model name must of type "' . Model::class . '". Please set it first using the setClassModelName method');
}
}
/**
* Loop over a dataStructure that looks like this...
*
* [
* id = 1
* routes = []
* status = 1
* thumbnail = false
* title = 'My model'
* children [ dataStructure, dataStructure ...]
* ]
*
* ... and re-parent every matching modelClassName instance so that it matches the dataStructure
*
* @param array $childrenDataStructures
* @param array $parentDataStructure
* @return bool Return
*/
private function makeArrayChildrenChildOfParent(array $childrenDataStructures, array $parentDataStructure)
{
$this->checkClassModelNameSet();
$parentTreeModel = $this->classModelName::find($parentDataStructure['id']);
if (!$parentTreeModel) {
\Log::error('Could not find parent model with id "' . $parentDataStructure['id'] . '" for model with class ' . $this->classModelName);
return false;
}
if (!is_a($parentTreeModel, TreeModelInterface::class)) {
throw new \RuntimeException('Can not set the children of a model that is not a ' . TreeModelInterface::class . ' but is a ' . get_class($parentTreeModel));
}
/** @var TreeModelInterface $parentTreeModel */
foreach ($childrenDataStructures as $childDataStructure) {
$childTreeModel = $this->classModelName::find($childDataStructure['id']);
if (!$childTreeModel) {
\Log::error('Could not find child model with id "' . $childDataStructure['id'] . '" for model with class ' . $this->classModelName);
continue;
}
if (!is_a($childTreeModel, TreeModelInterface::class)) {
throw new \RuntimeException('Can not set the parentTreeModel of a model that is not a ' . TreeModelInterface::class . ' but is a ' . get_class($childTreeModel));
}
/** @var TreeModelInterface $childTreeModel */
$childTreeModel->makeLastChildOf($parentTreeModel);
if (count($childDataStructure['children']) > 0) {
$this->makeArrayChildrenChildOfParent($childDataStructure['children'], $childDataStructure);
}
}
return true;
}
/**
* Creates a root model in the database for the for model fully qualified class name if it isn't there yet.
* But only if it implements the TreeModelInterface
* And returns the TreeModelInterface
*
* @param string $FQCN
* @param array|null $extraData Extra attributes with their values to fill in the model when the root model does not exist and has to be created
* @param Closure|null $existsQueryClosure An optional closure that must accept a query builder instance for constraining the query to check if the root model exists
* @return TreeModelInterface|Null
*/
public function makeRootModelIfNeeded(string $FQCN, array $extraData = null, Closure $existsQueryClosure = null): ? TreeModelInterface {
if (!class_exists($FQCN)) {
throw new \RuntimeException('The given FQCN is no instantiatable fully qualified class name');
}
$instance = new $FQCN;
if (!is_a($instance, Model::class)) {
throw new \RuntimeException('The given FQCN must resolve to a ' . Model::class);
}
/** @var $instance Model|TreeModelInterface */
if (!is_a($instance, TreeModelInterface::class)) {
return null;
} //Only handle treeModelInterface instances. Ignore the rest
$table = $instance->getTable();
$columns = Schema::getColumnListing($table);
$requiredColumns = ['lft', 'rgt'];
if (count(array_intersect($columns, $requiredColumns)) != count($requiredColumns)) {
throw new \RuntimeException('Make sure the table ' . $table . ' for model ' . $FQCN . ' contains the columns ' . implode(', ',
$requiredColumns));
}
$query = $FQCN::where('lft', '=', '1');
if ($existsQueryClosure) {
$query = $existsQueryClosure->call($this, $query);
if (!$query) {
throw new \RuntimeException('The $existsQueryClosure must return the query builder it got as a parameter');
}
}
$exists = $query->count() === 1;
if (!$exists) {
$instance->makeRoot();
if ($extraData) {
$instance->fill($extraData);
$instance->save();
}
return $instance;
}
return null;
}
/**
* Puts the values of attributes in an Eloquent model. And then saves that model.
*
* @param Model $model
* @param Collection $attributes
* @return Model
*/
public function save(Model $model, Collection $attributes = null): Model
{
//Only process the model if attributes where given
if($attributes === null) {
$this->debug('Skipping saving model "'.get_class($model).'" in a tree. Because it did not receive any attributes.');
return $model;
}
$this->checkContainsAttributes($attributes);
//Skip the saving for the model if it does not implement the interface
if(!is_a($model, TreeModelInterface::class)) {
$this->debug('Skipping saving model "'.get_class($model).'" in a tree, Because it does not extend "'.TreeModelInterface::class.'"');
return $model;
}
$attributes->each(function(Attribute $attribute) use($model) {
// $valueFrom = $attribute->getsValueFrom();
$valueReference = $attribute->getsValueFromReference();
$value = $attribute->getValue();
if($valueReference == 'parent_id') {
/** @var TreeModelInterface $model */
$parent = get_class($model)::find($value);
$currentParent = ($model->lft && $model->lft !== null) ? $model->getParent() : null;
if($parent && (!$currentParent || $currentParent->id !== $parent->id)) {
$model->makeLastChildOf($parent);
} elseif (!$parent) {
$rootModel = $model::allRoot()->first();
$model->makeLastChildOf($rootModel);
}
}
});
return $model;
}
/**
* Gets the values of an Eloquent model and passes them to a collection of attributes
*
* @param Model $model
* @param Collection $attributes
* @return Collection
*/
public function load(Model $model, Collection $attributes = null): Collection
{
//Only process the model if attributes where given
if($attributes === null) {
$this->debug('Skipping loading model "'.get_class($model).'" from a tree. Because it did not receive any attributes.');
return new Collection();
}
$this->checkContainsAttributes($attributes);
//Skip for the model if it does not implement the interface
if(!is_a($model, TreeModelInterface::class)) {
$this->debug('Skipping loading model "'.get_class($model).'" from a tree, Because it does not extend "'.TreeModelInterface::class.'"');
return $attributes;
}
return $attributes->map(function(Attribute $attribute) use(&$model) {
// $valueFrom = $attribute->getsValueFrom();
$valueReference = $attribute->getsValueFromReference();
if($valueReference == 'parent_id' && is_a($model, TreeModelInterface::class))
{
/** @var TreeModelInterface $model */
$parent = $model->getParent();
$attribute->setValue((string) $parent->id);
}
return $attribute;
});
}
/**
* Destroys the appropriate related models for a given model.
* Those related models must be the responsibility of this service
*
* @param Model $model
* @return Model $model
*/
public function destroyForModel(Model $model)
{
return $model;
}
/**
* @param Closure $beforeCallback
* @param Model $model
* @return Model $model
*/
public function destroyForModelAndExecuteCallbackForeach(Model $model, \Closure $beforeCallback)
{
if(is_a($model, TreeModelInterface::class)) {
/** @var TreeModelInterface $model */
$children = $model->findChildren();
foreach($children as $child)
{
/** @var TreeModelInterface $child */
$child = $beforeCallback($child);
if(!$child) continue;
$child = $this->destroyForModelAndExecuteCallbackForeach($child, $beforeCallback);
$child->delete();
}
}
return $model;
}
}