Skip to content
Advertisement

Adding custom method to Laravel model causes create() to ignore attributes

In Laravel 8, I have already had a working DocumentPdf resource – at least I was able to create the object using DocumentPdf::create($attributes).

Now I want to create a custom method in the model, a one that will fill the document with $data and send it to browser. While it works for existing documents, adding the method broke create() in a way that when I send a filled form, I receive a IlluminateDatabaseQueryException:

SQLSTATE[HY000]: General error: 1364 Field ‘filename’ doesn’t have a default value (SQL: insert into document_pdfs (updated_at, created_at) values (2020-10-22 02:42:26, 2020-10-22 02:42:26))

It’s not a first custom method in the model, but the only breaking one – commenting out the method makes create() work again, but then I lose an ability to fill the document.

app/DocumentPdf.php:

namespace App;

use IlluminateDatabaseEloquentModel;
use mikehaertlpdftkPdf;

class DocumentPdf extends Model
{
    // (... some constants, static methods and relations ...)

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $guarded = [];

    /**
     * Checks whether the document is active
     *
     * @return bool
     */
    public function isActive()
    {
        return $this->active;
    }

    /**
     * Get full path to the file
     *
     * @return string
     */
    public function fullpath()
    {
        return storage_path('app' . DIRECTORY_SEPARATOR . $this->path);
    }

    /**
     * Fill the document and send it to browser
     *
     */
    public function fill(array $data)
    {
        $pdf = new Pdf($this->fullpath());
        $pdf->fillForm($data)
            ->needAppearances()
            ->send(date('YmdHis') . '.pdf');
    }

app/Http/Controllers/DocumentPdfController.php:

namespace AppHttpControllers;

use AppDocumentPdf;
use IlluminateHttpRequest;
use mikehaertlpdftkPdf;

class DocumentPdfController extends Controller
{
    // (...)

    /**
     * Show the form for creating a new resource.
     *
     * @return IlluminateHttpResponse
     */
    public function create()
    {
        return view('documents.create');
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  IlluminateHttpRequest  $request
     * @return IlluminateHttpResponse
     */
    public function store(Request $request)
    {
        $this->authorize('create', DocumentPdf::class);

        DocumentPdf::create($this->validateFormAndStoreFile());

        return redirect(route('documents.index'))
            ->with('model-message', 'Document created');
    }

    // (...)

    /**
     * Validate and parse data for storing/updating the resource.
     *
     * @return Array
     */
    protected function validateFormAndStoreFile()
    {
        $form = $this->validateForm();
        $form += $this->storeFile();
        $form += $this->discoverFields($form['path']);
        unset($form['file']);

        return $form;
    }

    /**
     * Validate data for storing/updating the resource.
     *
     * @return Array
     */
    protected function validateForm()
    {
        return request()->validate([
            'display_name' => 'nullable|max:255',
            'description' => 'nullable',
            'file' => 'required|file|mimes:pdf',
        ]);
    }

    /**
     * Parse data for storing/updating the resource in database.
     *
     * @return Array
     */
    protected function storeFile()
    {
        $path = request('file')->store('documents');

        $form = [
            'filename' => request('file')->getClientOriginalName(),
            'extension' => request('file')->extension(),
            'size' => request('file')->getSize(),
            'mimetype' => request('file')->getMimeType(null),
            'path' => $path
        ];

        return $form;
    }


    /**
     * Discover PDF file fields.
     * Todo: I feel like it should be handled in DocumentPdf model
     *      but failed to do so due to BadMethodCallException - to be verified
     *
     * @return Array
     */
    protected function discoverFields($filename)
    {
        $pdf = new Pdf(storage_path('app' . DIRECTORY_SEPARATOR . $filename));
        return [
            'fields' => json_encode($pdf->getDataFields()),
        ];
    }

How could I define such method not to break existing functionality? I’d like to have it in a model, since in the future I plan to handle other types of documents, e. g. DocumentXls, and have a parent abstract class Document that’d be handled by DocumentController.

Advertisement

Answer

fill is a Model method that fills the attributes of the Model. You should not override this; change your method name to something else.

IlluminateDatabaseEloquentBuilder@create creates a new instance of the Model with the data passed, which will call fill to fill the attributes for the Model.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement