处理多对多更新

@param \Imi\Model\Model $model
@param string $propertyName
@param \Imi\Model\Annotation\Relation\ManyToMany $annotation
@return void
484493
public static function parseByManyToMany($model, $propertyName, $annotation)
    {
        $className = BeanFactory::getObjectClass($model);

        $struct = new ManyToMany($className, $propertyName, $annotation);
        $middleModel = $struct->getMiddleModel();
        $middleLeftField = $struct->getMiddleLeftField();
        $middleRightField = $struct->getMiddleRightField();
        $leftField = $struct->getLeftField();
        $rightField = $struct->getRightField();

        $autoUpdate = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoUpdate::class)[0] ?? null;
        $autoSave = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoSave::class)[0] ?? null;
        // 是否删除无关数据
        if($autoUpdate)
        {
            $orphanRemoval = $autoUpdate->orphanRemoval;
        }
        else if($autoSave)
        {
            $orphanRemoval = $autoSave->orphanRemoval;
        }
        else
        {
            $orphanRemoval = false;
        }

        if($orphanRemoval)
        {
            // 删除无关联数据
            $oldRightIDs = $middleModel::query()->where($middleLeftField, '=', $model->$leftField)->field($middleRightField)->select()->getColumn();

            $updateIDs = [];
            foreach($model->$propertyName as $row)
            {
                if(null !== $row->$middleRightField)
                {
                    $updateIDs[] = $row->$middleRightField;
                }
                $row->$middleLeftField = $model->$leftField;
                $row->save();
            }

            $deleteIDs = array_diff($oldRightIDs, $updateIDs);

            if($deleteIDs)
            {
                // 批量删除
                $middleModel::deleteBatch(function(IQuery $query) use($middleLeftField, $middleRightField, $leftField, $model, $deleteIDs){
                    $query->where($middleLeftField, '=', $model->$leftField)->whereIn($middleRightField, $deleteIDs);
                });
            }
        }
        else
        {
            // 直接更新
            foreach($model->$propertyName as $row)
            {
                $row->$middleLeftField = $model->$leftField;
                $row->save();
            }
        }
    }
536769
public static function collect(
        string $className,
        $objectAnnotation = null,
        string $propertyName = '',
        string $methodName = '',
        $propertyValue = null
    ) {
        if ($objectAnnotation instanceof Command) {
            self::collectCommand($className, $objectAnnotation);
        } elseif ($objectAnnotation instanceof Mapping) {
            self::collectMapping($className, $objectAnnotation, $methodName);
        } elseif ($objectAnnotation === null && isset(self::$commandMapping[$className])) {
            self::collectWithoutAnnotation($className, $methodName);
        }
    }
955334
public static function hasUpdateRelation($className, $propertyName = null)
    {
        $relations = AnnotationManager::getPropertiesAnnotations($className, RelationBase::class);

        if(empty($relations))
        {
            return false;
        }

        if(null === $propertyName)
        {
            foreach($relations as $name => $annotations)
            {
                $annotation = $annotations[0];
                $autoUpdate = AnnotationManager::getPropertyAnnotations($className, $name, AutoUpdate::class)[0] ?? null;
                $autoSave = AnnotationManager::getPropertyAnnotations($className, $name, AutoSave::class)[0] ?? null;
        
                if($autoUpdate)
                {
                    if(!$autoUpdate->status)
                    {
                        continue;
                    }
                }
                else if(!$autoSave || !$autoSave->status)
                {
                    continue;
                }
            }
        }
        else
        {
            $autoUpdate = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoUpdate::class)[0] ?? null;
            $autoSave = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoSave::class)[0] ?? null;
    
            if($autoUpdate)
            {
                if(!$autoUpdate->status)
                {
                    return false;
                }
            }
            else if(!$autoSave || !$autoSave->status)
            {
                return false;
            }
            return true;
        }
        return false;
    }
274295
public static function init($model, $propertyName, $annotation, $forceInit = false)
    {
        $className = BeanFactory::getObjectClass($model);

        if(!$forceInit)
        {
            $autoSelect = AnnotationManager::getPropertyAnnotations($className, $propertyName, AutoSelect::class)[0] ?? null;
            if($autoSelect && !$autoSelect->status)
            {
                return;
            }
        }

        if(is_array($annotation))
        {
            $firstAnnotation = reset($annotation);
            if($firstAnnotation instanceof \Imi\Model\Annotation\Relation\PolymorphicToOne)
            {
                static::initByPolymorphicToOne($model, $propertyName, $annotation);
            }
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\OneToOne)
        {
            static::initByOneToOne($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\OneToMany)
        {
            static::initByOneToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\ManyToMany)
        {
            static::initByManyToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicOneToOne)
        {
            static::initByPolymorphicOneToOne($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicOneToMany)
        {
            static::initByPolymorphicOneToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicManyToMany)
        {
            static::initByPolymorphicManyToMany($model, $propertyName, $annotation);
        }
        else if($annotation instanceof \Imi\Model\Annotation\Relation\PolymorphicToMany)
        {
            static::initByPolymorphicToMany($model, $propertyName, $annotation);
        }
    }
731895
public static function getPropertyAnnotation($object, $propertyName, $annotationClass)
    {
        return AnnotationManager::getPropertyAnnotations(BeanFactory::getObjectClass($object), $propertyName, $annotationClass)[0] ?? null;
    }
47973
public function setProperty(&$object, $propertyName, $propertyValue)
    {
        $property = $this->getPropertyInstance($object, $propertyName);
        $property->setAccessible(true);
        $property->setValue($object, $propertyValue);
    }
105790
final protected function setModelValue($propertyName, $value)
    {
        try {
            $oldValue = (isset($this->modelData[$propertyName])) ? $this->modelData[$propertyName]: null;
        } catch (\Exception $ex) {
            // Catch any exceptions thrown when trying to retrieve the old value for the sake
            // of comparison to trigger the property changed handlers.
            $oldValue = null;
        }

        $this->modelData[$propertyName] = $value;

        if ($value instanceof ModelState) {
            $this->attachChangeListenerToModelProperty($propertyName, $value);
        }

        if ($oldValue != $value) {
            if (!$this->propertyChangeEventsDisabled) {
                // Don't fire changes if they are disabled.
                $this->raisePropertyChangedCallbacks($propertyName, $value, $oldValue);
                $this->traitRaiseEvent("AfterChange", $this);
            }
        }
    }
1037741
public function updateProperty(\ReflectionProperty $reflectionProperty, Schema $property)
    {
        $annotations = $this->annotationsReader->getPropertyAnnotations($reflectionProperty);

        foreach ($annotations as $annotation) {
            if ($annotation instanceof Assert\NotBlank || $annotation instanceof Assert\NotNull) {
                // The field is required
                if (null === $this->schema) {
                    continue;
                }

                $propertyName = $this->getSchemaPropertyName($property);
                if (null === $propertyName) {
                    continue;
                }

                $existingRequiredFields = $this->schema->getRequired() ?? [];
                $existingRequiredFields[] = $propertyName;

                $this->schema->setRequired(array_values(array_unique($existingRequiredFields)));
            } elseif ($annotation instanceof Assert\Length) {
                $property->setMinLength($annotation->min);
                $property->setMaxLength($annotation->max);
            } elseif ($annotation instanceof Assert\Regex) {
                $this->appendPattern($property, $annotation->getHtmlPattern());
            } elseif ($annotation instanceof Assert\Count) {
                $property->setMinItems($annotation->min);
                $property->setMaxItems($annotation->max);
            } elseif ($annotation instanceof Assert\Choice) {
                $property->setEnum($annotation->callback ? call_user_func(is_array($annotation->callback) ? $annotation->callback : [$reflectionProperty->class, $annotation->callback]) : $annotation->choices);
            } elseif ($annotation instanceof Assert\Expression) {
                $this->appendPattern($property, $annotation->message);
            } elseif ($annotation instanceof Assert\Range) {
                $property->setMinimum($annotation->min);
                $property->setMaximum($annotation->max);
            } elseif ($annotation instanceof Assert\LessThan) {
                $property->setExclusiveMaximum($annotation->value);
            } elseif ($annotation instanceof Assert\LessThanOrEqual) {
                $property->setMaximum($annotation->value);
            }
        }
    }
749088
public function setProperty($propertyName, $value)
    {
        if ($value !== $this->getProperty($propertyName)) {
            $this->setMeta(static::IS_CHANGED, true);
            $this->setPropertyRaw($propertyName, $value);
        }
    }
418493
public static function initByPolymorphicToOne($model, $propertyName, $annotation)
    {
        foreach($annotation as $annotationItem)
        {
            if($model->{$annotationItem->type} == $annotationItem->typeValue)
            {
                $leftField = $annotationItem->modelField;
                $rightField = $annotationItem->field;
                if(class_exists($annotationItem->model))
                {
                    $modelClass = $annotationItem->model;
                }
                else
                {
                    $modelClass = Imi::getClassNamespace($className) . '\\' . $annotationItem->model;
                }
                if(null === $model->$rightField)
                {
                    $leftModel = $modelClass::newInstance();
                }
                else
                {
                    $leftModel = $modelClass::query()->where($leftField, '=', $model->$rightField)->select()->get();
                    if(null === $leftModel)
                    {
                        $leftModel = $modelClass::newInstance();
                    }
                }
                $model->$propertyName = $leftModel;
                break;
            }
        }
    }
344163
protected function initProperty( $propertyName, $propertyClass){
        if( property_exists( static::class, $propertyName) && ! empty( $this->$propertyName)){
            $this->$propertyName = new $propertyClass( $this->$propertyName);
        }
    }
684005
public function setModelRelationship(&$model, Field $field)
    {
        //if this is a belongsToMany, we want to sort our initial values
        $relatedItems   = $this->getModelRelatedItems($model, $field);
        $name           = $field->getOption('field_name');
        $multipleValues = $field->getOption('multiple_values');
        $nameField      = $field->getOption('name_field');
        $autocomplete   = $field->getOption('autocomplete');
        $options        = $field->getOption('options');

        //get all existing values for this relationship
        if ($relatedItems) {
            //the array that holds all the ids of the currently-related items
            $relationsArray = array();

            //the id-indexed array that holds all of the select option data for a relation.
            //this holds the currently-related items and all of the available options
            $autocompleteArray = array();

            //iterate over the items
            foreach ($relatedItems as $item) {
                $keyName = $item->getKeyName();

                //if this is a mutliple-value type (i.e. HasMany, BelongsToMany), make sure this is an array
                if ($multipleValues) {
                    $relationsArray[] = $item->{$keyName};
                } else {
                    $model->setAttribute($name, $item->{$keyName});
                }

                //if this is an autocomplete field, we'll need to provide an array of arrays with 'id' and 'text' indexes
                if ($autocomplete) {
                    $autocompleteArray[$item->{$keyName}] = array('id' => $item->{$keyName}, 'text' => $item->{$nameField});
                }
            }

            //if this is a BTM, set the relations array to the property that matches the relationship name
            if ($multipleValues) {
                $model->{$name} = $relationsArray;
            }

            //set the options attribute
            $model->setAttribute($name.'_options', $options);

            //unset the relationships so we only get back what we need
            $model->relationships = array();

            //set the autocomplete array
            if ($autocomplete) {
                $model->setAttribute($name.'_autocomplete', $autocompleteArray);
            }
        }
        //if there are no values, then just set an empty array
        else {
            $model->{$name} = array();
        }
    }
128104
public static function collect(
        string $className,
        $objectAnnotation = null,
        string $propertyName = '',
        string $methodName = '',
        $propertyValue = null
    ) {
        if ($objectAnnotation instanceof Column) {
            self::collectColumn($objectAnnotation, $className, $propertyName, $propertyValue);
        } elseif ($objectAnnotation instanceof Entity) {
            self::collectEntity($objectAnnotation, $className);
        } elseif ($objectAnnotation instanceof Id) {
            self::collectId($className, $propertyName);
        } elseif ($objectAnnotation instanceof Required) {
            self::collectRequired($className, $propertyName);
        } elseif ($objectAnnotation instanceof Table) {
            self::collectTable($objectAnnotation, $className);
        }
    }
1355556
public function extend($annotation)
    {
        $params = get_object_vars($annotation);
        foreach ($params as $property => $value) {
            if (property_exists($this, $property) && !in_array($property, ['name', 'route'])) {
                if (!$this->$property) {
                    $this->$property = $value;
                }
            }
        }
    }
219080
public static function collect(
        string $className,
        $objectAnnotation = null,
        string $propertyName = '',
        string $methodName = '',
        $propertyValue = null
    ) {
        if ($objectAnnotation instanceof Statement) {
            $driver                    = $objectAnnotation->getDriver();
            self::$statements[$driver] = $className;
        }
    }
461692
public function setProperty($propName, $value)
    {
        if (self::isAttribute($propName)) {
            $this->setAttribute($propName, $value);
        } else {
            if ($setterName = $this->setterExists($propName)) {
                $this->$setterName($value);
            } elseif (self::hasPublicProperty($propName)) {
                $this->$propName = $value;
            } else {
                throw new Exception\RuntimeException(
                    sprintf("Can't write unknown property '%s' for model %s", $propName, get_called_class())
                );
            }
        }
    }
421989
public function handleM2MField(Model $model, Field $field)
    {
        $vals = [];
        /** @var $field ManyToManyField */
        if ($field->relation->through->getMeta()->autoCreated) {
            //todo handle natuaral keys
            $m2mValues = function (Model $model) {
                return $model->getPkValue();
            };

            foreach ($model->{$field->getName()}->all() as $item) {
                $vals[] = $m2mValues($item);
            }
        }
        $this->_fields[$field->getName()] = $vals;
    }