/** * Update an object a given CtrlClass, if an ID is given * Or create a new object if not * @param Request $request * @param integer $ctrl_class_id The ID of the class we're editing * @param integer $object_id The ID of the object we're editing (zero to create a new one) * @param string $filter_string This tracks whether we're adding a filtered object -- so we can bounce back to the filtered list.. * * @return Response */ public function save_object(Request $request, $ctrl_class_id, $object_id = NULL, $filter_string = NULL) { $ctrl_class = CtrlClass::where('id', $ctrl_class_id)->firstOrFail(); $ctrl_properties = $ctrl_class->ctrl_properties()->where('fieldset', '!=', '')->get(); // Validate the post: $validation = []; foreach ($ctrl_properties as $ctrl_property) { $field_name = $ctrl_property->get_field_name(); $flags = explode(',', $ctrl_property->flags); if (in_array('required', $flags)) { $validation[$field_name][] = 'required'; } // Note: could also do this in query builder: /* $required_properties = $ctrl_class->ctrl_properties() ->whereRaw("FIND_IN_SET('required',flags) > 0") ->get(); */ if ($ctrl_property->field_type == 'email') { $validation[$field_name][] = 'email'; } // Check for valid dates; not sure if tis is correct? if (in_array($ctrl_property->field_type, ['date', 'datetime'])) { $validation[$field_name][] = 'date'; } if (!empty($validation[$field_name])) { $validation[$field_name] = implode('|', $validation[$field_name]); } } if ($validation) { $this->validate($request, $validation); } $class = $ctrl_class->get_class(); $object = $object_id ? $class::where('id', $object_id)->firstOrFail() : new $class(); // Convert dates back into MySQL format; this feels quite messy but I can't see where else to do it: foreach ($ctrl_properties as $ctrl_property) { if (in_array($ctrl_property->field_type, ['date', 'datetime']) && !empty($_POST[$ctrl_property->name])) { $date_format = $ctrl_property->field_type == 'date' ? 'Y-m-d' : 'Y-m-d H:i:s'; $_POST[$ctrl_property->name] = date($date_format, strtotime($_POST[$ctrl_property->name])); } } $object->fill($_POST); $object->save(); // Save the new object, otherwise we can't save any relationships... // Now load any related fields (excluding belongsTo, as this indicates the presence of an _id field) $related_ctrl_properties = $ctrl_class->ctrl_properties()->where('fieldset', '!=', '')->where(function ($query) { $query->where('relationship_type', 'hasMany')->orWhere('relationship_type', 'belongsToMany'); })->get(); foreach ($related_ctrl_properties as $related_ctrl_property) { $related_field_name = $related_ctrl_property->get_field_name(); if ($request->input($related_field_name)) { // $request->input($related_field_name) is always an array here, I think? $related_ctrl_class = \Sevenpointsix\Ctrl\Models\CtrlClass::find($related_ctrl_property->related_to_id); // NOTE: we need some standard functions for the following: /* - Loading the object for a class - Loading the related class from a property - Loading related properties for a class ... etc */ $related_class = $related_ctrl_class->get_class(); $related_objects = $related_class::find($request->input($related_field_name)); if ($related_ctrl_property->relationship_type == 'hasMany' || $related_ctrl_property->relationship_type == 'belongsToMany') { // OK, I think we can use synch here; or does this break for hasMany? // Yeah, breaks for hasMany... :-( This works though: if ($related_ctrl_property->relationship_type == 'belongsToMany') { $object->{$related_field_name}()->sync($related_objects); } else { if ($related_ctrl_property->relationship_type == 'hasMany') { $object->{$related_field_name}()->saveMany($related_objects); } } /* // A hasMany relationship needs saveMany // belongsToMany might need attach, or synch -- TBC dump ("Loading existing_related_objects with $related_field_name"); $existing_related_objects = $object->$related_field_name(); $inverse_property = CtrlProperty::where('ctrl_class_id',$related_ctrl_class->id) ->where('foreign_key',$related_ctrl_property->foreign_key) ->first(); // Does this always hold true? $inverse_field_name = $inverse_property->name; foreach ($existing_related_objects as $existing_related_object) { dump('$existing_related_object'. $existing_related_object); $existing_related_object->$inverse_field_name()->dissociate(); dump("Removing relationship to $related_field_name"); $existing_related_object->save(); // This seems unnecessarily complicated; review this. // Is there no equivalent of synch() for hasMany/belongsTo relationships? // Something like, $object->related_field_name()->sync($related_objects); // That doesn't work though... } // dd($related_objects->toArray()); dump("Saving $related_field_name"); // dump($related_objects->lists('id')); foreach ($related_objects as $r) { // dump("Saving $related_field_name with ID $r->id to object ".get_class($object)); // $object->$related_field_name()->save($r); } // Why wouldn't this work? // $object->$related_field_name()->sync($related_objects); // Gives same error as below: // $object->$related_field_name()->saveMany($related_objects); // Maybe... if ($related_ctrl_property->relationship_type == 'hasMany') { $object->$related_field_name()->saveMany($related_objects); } else if ($related_ctrl_property->relationship_type == 'belongsToMany') { // $object->$related_field_name()->attach($related_objects); foreach ($related_objects as $r) { dump("Saving $related_field_name with ID $r->id to object ".get_class($object)); $object->$related_field_name()->attach($r->id); } } //$object->save(); // This is ALMOST working but glitches; we seem to save the relationship then overwrite it when we try to remove it, even though we try to remove it first. Do we need to lock the tables here? */ } } } $object->save(); // Add a custom post_save module if ($this->module->enabled('post_save')) { // We may eventually need to patch this into the validation...? Or would that imply the need for a validation (or pre_save) module? $this->module->run('post_save', [$request, $object, $filter_string]); } $redirect = route('ctrl::list_objects', [$ctrl_class->id, $filter_string]); if ($request->ajax()) { return json_encode(['redirect' => $redirect]); } else { return redirect($redirect); } }
/** * Generate model files based on the ctrl_tables * * @return Response */ public function generate_model_files() { $model_folder = 'Ctrl/Models/'; if (!File::exists(app_path($model_folder))) { File::makeDirectory(app_path($model_folder), 0777, true); // See http://laravel-recipes.com/recipes/147/creating-a-directory } else { // Otherwise, empty the folder: File::cleanDirectory(app_path($model_folder)); } $ctrl_classes = \Sevenpointsix\Ctrl\Models\CtrlClass::get(); foreach ($ctrl_classes as $ctrl_class) { $view_data = ['model_name' => $ctrl_class->name, 'soft_deletes' => false, 'table_name' => $ctrl_class->table_name, 'fillable' => [], 'belongsTo' => [], 'hasMany' => [], 'belongsToMany' => []]; // NOTE: this may need to include properties that we set using a filter in the URL // ie, if we want to add a course to a client, but "client" isn't directly visible in the form; // instead, we get to the list of courses by clicking the filtered_list "courses" when listing clients. $fillable_properties = $ctrl_class->ctrl_properties()->where('fieldset', '!=', '')->where(function ($query) { $query->whereNull('relationship_type')->orWhere('relationship_type', 'belongsTo'); })->get(); // We can only fill relationships if they're belongsTo (ie, have a specific local key, such as one_id) // OR if they're belongsToMany, in which case we have a pivot table (I think?) foreach ($fillable_properties as $fillable_property) { $view_data['fillable'][] = $fillable_property->get_field_name(); // Does Laravel/Eloquent give us a quick way of extracting all ->name properties into an array? // I think it does. } // Which properties can be automatically filled via a filtered list? ie, clicking to add a related page to a pagesection, should set the pagesection variable. // This is a bit complex as we have to look at properties of other classes, linking to this class... $filtered_list_properties = \Sevenpointsix\Ctrl\Models\CtrlProperty::whereRaw('(find_in_set(?, flags))', ['filtered_list'])->where('related_to_id', $ctrl_class->id)->get(); if (!$filtered_list_properties->isEmpty()) { foreach ($filtered_list_properties as $filtered_list_property) { $default_properties = $ctrl_class->ctrl_properties()->where('relationship_type', 'belongsTo')->where('related_to_id', $filtered_list_property->ctrl_class_id)->get(); if (!$default_properties->isEmpty()) { foreach ($default_properties as $default_property) { $view_data['fillable'][] = $default_property->get_field_name(); } } } } $relationship_properties = $ctrl_class->ctrl_properties()->whereNotNull('related_to_id')->get(); foreach ($relationship_properties as $relationship_property) { $related_ctrl_class = \Sevenpointsix\Ctrl\Models\CtrlClass::find($relationship_property->related_to_id); $relationship_data = ['name' => $relationship_property->name, 'model' => $related_ctrl_class->name, 'foreign_key' => $relationship_property->foreign_key, 'local_key' => $relationship_property->local_key]; if ($relationship_property->relationship_type == 'belongsToMany') { $relationship_data['pivot_table'] = $relationship_property->pivot_table; } $view_data[$relationship_property->relationship_type][] = $relationship_data; } $model_code = View::make('ctrl::model_template', $view_data)->render(); $model_path = app_path($model_folder . $ctrl_class->name . '.php'); File::put($model_path, $model_code); } $this->info($ctrl_classes->count() . ' files generated'); }