public function attribute($data, $context = null, $overwrite = true, $overwrite_parameter = false)
 {
     $name = $data['Name'];
     if (!$this->Id) {
         throw new InvalidArgumentException("Cowardly refusing to create an empty (universal) attribute");
     }
     /* Convention over configuration */
     $id_name = train_case(get_class($this)) . '_Id';
     if ($id_name == 'Context_Id') {
         $id_name = Context::$idField;
     }
     $parameterClause = ParameterAttribution::where($id_name, '=', $this->Id);
     $activeFields = [snake_case(get_class($this))];
     if ($context !== null) {
         $activeFields[] = 'context';
         $parameterClause = $parameterClause->where(Context::$idContext, '=', $context->Id);
     }
     $parameterAttributions = $parameterClause->whereHas('parameter', function ($q) use($name) {
         $q->where('Name', '=', $name);
     })->get()->filter(function ($pa) use($activeFields) {
         return empty(array_diff(array_values($pa->activeFields()), array_values($activeFields)));
     });
     $parameterAttribution = $parameterAttributions->first();
     $parameterAttributions->each(function ($pa) {
         $pa->delete();
     });
     if (array_key_exists('Value', $data)) {
         $value = $data['Value'];
         unset($data['Value']);
     } else {
         $value = null;
     }
     if (array_key_exists('Widget', $data)) {
         $widget = $data['Widget'];
         unset($data['Widget']);
     } else {
         $widget = null;
     }
     if (array_key_exists('Units', $data)) {
         $units = $data['Units'];
         unset($data['Units']);
     } else {
         $units = null;
     }
     if (array_key_exists('Editable', $data)) {
         $editable = $data['Editable'];
         unset($data['Editable']);
     } else {
         $editable = 2;
     }
     if ($parameterAttribution) {
         $parameter = $parameterAttribution->parameter;
         if ($overwrite) {
             $parameterAttribution->Value = $value;
             $parameterAttribution->Editable = $editable;
             $parameterAttribution->Widget = $widget;
             $parameterAttribution->Units = $units;
             $parameterAttribution->save();
         } else {
             $value = $parameterAttribution->Value;
             $editable = $parameterAttribution->Editable;
             $widget = $parameterAttribution->Widget;
             $units = $parameterAttribution->Units;
         }
     } else {
         $parameter = Parameter::whereName($data['Name'])->first();
         if (empty($parameter)) {
             $parameter = Parameter::create($data);
         } else {
             $parameter->update(array_filter($data));
         }
         /* Convention over configuration */
         $id_name = train_case(get_class($this)) . '_Id';
         if ($id_name == 'Context_Id') {
             $id_name = Context::$idField;
         }
         $type = array_key_exists('Type', $data) ? $data['Type'] : $parameter->Type;
         $widget = isset($widget) ? $widget : $parameter->Widget;
         $units = isset($units) ? $units : $parameter->Units;
         $attribution = [$id_name => $this->Id, 'Parameter_Id' => $parameter->Id, 'Value' => $value, 'Format' => $type, 'Editable' => $editable, 'Widget' => $widget, 'Units' => $units];
         if ($context !== null) {
             $attribution[Context::$idContext] = $context->Id;
         }
         $parameterAttribution = ParameterAttribution::create($attribution);
     }
     if ($parameter->Widget === null && $parameter->Units === null || $overwrite_parameter) {
         $parameter->Widget = $widget;
         $parameter->Units = $units;
     }
     $parameter->Value = $value;
     $parameter->Editable = $editable;
     $parameter->Widget = $widget;
     $parameter->Units = $units;
     $parameterAttribution->save();
     return $parameter;
 }
 public function compileParameters($userSupplied, $needles, $needleUserParameters, &$incompatibilities = array(), &$userRequiredParameters)
 {
     if (is_array($needles)) {
         $needlesCollection = new Collection($needles);
     } else {
         $needlesCollection = $needles;
     }
     $disallowedNeedles = $needlesCollection->diff($this->Needles);
     foreach ($disallowedNeedles as $needle) {
         $incompatibilities[] = "Needle {$needle->Name} is not marked for use in this combination";
     }
     $allowedNeedles = $needlesCollection->intersect($this->Needles);
     $attributionsWithoutNeedle = ParameterAttribution::join("Parameter", "Parameter.Id", "=", "Parameter_Attribution.Parameter_Id")->addSelect("Parameter_Attribution.*", "Parameter.Name AS parameterName", "Parameter_Attribution.Value AS parameterValue");
     $automaticFields = array_diff($this->attributingFields, ['Protocol']);
     foreach ($automaticFields as $field) {
         $property = train_case($field);
         $class = get_class($this->{$property});
         $attributionsWithoutNeedle = $attributionsWithoutNeedle->where(function ($q) use($field, $property, $class) {
             $q->whereNull($class::$idField)->orWhere($class::$idField, "=", $this->{$property}->Id);
         });
     }
     $resultList = new Collection();
     if (in_array('Protocol', $this->attributingFields)) {
         $resultList = $resultList->merge($this->Protocol->Algorithms->lists('Result'));
     }
     foreach ($this->Protocol->Algorithms as $algorithm) {
         $algorithms = $this->Protocol->Algorithms;
         $attributionsWithoutNeedle = $attributionsWithoutNeedle->where(function ($q) use($algorithms) {
             $q->whereNull("Algorithm_Id");
             foreach ($algorithms as $algorithm) {
                 $q->orWhere("Algorithm_Id", "=", $algorithm->Id);
             }
         });
     }
     $needleParametersByNeedle = [];
     if (count($allowedNeedles)) {
         foreach ($needlesCollection as $needleIx => $needle) {
             if (!in_array($needle, $allowedNeedles->all())) {
                 continue;
             }
             $attributions = with(clone $attributionsWithoutNeedle)->where(function ($q) use($needle) {
                 $q = $q->whereNull("Needle_Id");
                 $q = $q->orWhere("Needle_Id", "=", $needle->Id);
             });
             $requirements = with(clone $attributions)->whereNull("Parameter_Attribution.Value");
             $supplies = with(clone $attributions)->whereNotNull("Parameter_Attribution.Value");
             $needleParameters = [];
             foreach ($supplies->get() as $a) {
                 $name = $a->Parameter->Name;
                 if (!isset($needleParameters[$name])) {
                     $needleParameters[$name] = [];
                 }
                 $needleParameters[$name][] = $a;
             }
             array_walk($needleParameters, function (&$v, $name) {
                 /* Remove any redundant parameters - only needle parameters and parameters
                  * overriding a needle-specific parameter count */
                 if (!count(array_filter($v, function ($n) {
                     return $n->Needle_Id !== null;
                 }))) {
                     $v = false;
                 } else {
                     $v = $this->chooseAttribution($v);
                 }
             });
             $needleParameters = array_filter($needleParameters);
             $needleUser = isset($needleUserParameters[$needleIx]) ? $needleUserParameters[$needleIx] : [];
             foreach ($needleUser as $needleUserParameter) {
                 $needleParameters[$needleUserParameter->Name] = $needleUserParameter;
             }
             $needleParametersByNeedle[$needleIx] = $needleParameters;
             $requirementsList = $requirements->lists("parameterName");
             $supplyList = $supplies->lists("parameterName");
             $needleUserSupplied = [];
             if (isset($needleUserParameters[$needleIx])) {
                 $needleUserSupplied = $needleUserParameters[$needleIx]->lists('Name');
             }
             $undefinedList = array_diff($requirementsList, $supplyList, $needleUserSupplied, $userSupplied->lists('Name'));
             $undefinedList = array_diff($undefinedList, $resultList->lists("Name"));
             if (!empty($undefinedList)) {
                 $requirementsMap = array_combine($requirements->lists("parameterName"), $requirements->get()->all());
                 foreach ($undefinedList as $missingParameterName) {
                     $editable = $requirementsMap[$missingParameterName]->Editable >= 2;
                     if ($editable) {
                         $userRequiredParameters[] = $requirementsMap[$missingParameterName]->Parameter;
                     } else {
                         $incompatibilities[] = "Parameter {$missingParameterName} is missing";
                     }
                 }
             }
         }
     } else {
         $requirements = with(clone $attributionsWithoutNeedle)->whereNull("Parameter_Attribution.Value");
         $supplies = with(clone $attributionsWithoutNeedle)->whereNotNull("Parameter_Attribution.Value");
         $requirementsList = $requirements->lists("parameterName");
         $supplyList = $supplies->lists("parameterName");
         $undefinedList = array_diff($requirementsList, $supplyList, $userSupplied->lists('Name'));
         $undefinedList = array_diff($undefinedList, $resultList->lists("Name"));
         if (!empty($undefinedList)) {
             $requirementsMap = array_combine($requirements->lists("parameterName"), $requirements->get()->all());
             foreach ($undefinedList as $missingParameterName) {
                 $editable = $requirementsMap[$missingParameterName]->Editable >= 2;
                 if ($editable) {
                     $userRequiredParameters[] = $requirementsMap[$missingParameterName]->Parameter;
                 } else {
                     $incompatibilities[] = "Parameter {$missingParameterName} is missing";
                 }
             }
         }
     }
     $supplies = $attributionsWithoutNeedle->whereNull("Needle_Id")->whereNotNull("Parameter_Attribution.Value");
     $attributions = [];
     foreach ($supplies->get() as $attribution) {
         $name = $attribution->Parameter->Name;
         if (!isset($attributions[$name])) {
             $attributions[$name] = [];
         }
         $attributions[$name][] = $attribution;
     }
     $parameters = [];
     foreach ($attributions as $name => $available) {
         $parameters[$name] = $this->chooseAttribution($available);
     }
     foreach ($userSupplied as $userParameter) {
         if ($userParameter->Value === null) {
             $userParameter->Value = $userParameter->pivot->ValueSet;
         }
         $parameters[$userParameter->Name] = $userParameter;
     }
     foreach ($parameters as $parameter) {
         if ($parameter->Editable == 3) {
             /* Always editable */
             $userRequiredParameters[] = $parameter;
         }
     }
     foreach ($needleParametersByNeedle as $needle => $needleParameters) {
         foreach ($needleParameters as $parameter) {
             if ($parameter->Editable == 3) {
                 /* Always editable */
                 $userRequiredParameters[] = $parameter;
             }
         }
     }
     return [$parameters, $needleParametersByNeedle];
 }