File: D:/HostingSpaces/SBogers10/blije-gasten.komma.pro/app/Komma/Kms/Core/Tree/TreeService.php
<?php namespace App\Komma\Kms\Core\Tree;
use App\Komma\Kms\Core\AbstractModelHandler;
use App\Komma\Kms\Core\Attributes\Attribute;
use App\Komma\Kms\Core\Tree\NestedSets\Nodes\AbstractTranslatableTreeModel;
use App\Komma\Kms\Core\Tree\NestedSets\Nodes\TreeModelInterface;
use App\Komma\Sites\SiteServiceInterface;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Schema;
use Closure;
/**
*
*
* @author Komma <info@komma.pro>
* @copyright (c) 2012-2016, Komma
*/
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 makeRootModelmodelClassNameIfNeeded(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
{
if($attributes === null) return $model;
$attributes->each(function(Attribute $attribute) use($model) {
// $valueFrom = $attribute->getsValueFrom();
$valueReference = $attribute->getsValueFromReference();
$value = $attribute->getValue();
if($valueReference == 'parent_id' && is_a($model,AbstractTranslatableTreeModel::class)) {
/** @var AbstractTranslatableTreeModel $model */
$parent = get_class($model)::find($value);
$currentParent = ($model->lft && $model->lft !== null) ? $model->getParent() : null;
if(!$currentParent || $currentParent->id !== $parent->id) {
$model->makeLastChildOf($parent);
}
}
});
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
{
if($attributes === null) return new Collection();
return $attributes->map(function(Attribute $attribute) use(&$model) {
// $valueFrom = $attribute->getsValueFrom();
$valueReference = $attribute->getsValueFromReference();
if($valueReference == 'parent_id' && is_a($model, AbstractTranslatableTreeModel::class))
{
/** @var AbstractTranslatableTreeModel $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
*/
public function destroyForModel(Model $model)
{
throw new \RuntimeException('Not implemented');
}
}