We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

$model->save(); saves not the entire model, return "xy is required"

Good evening,

I got some columns in my table that must not be null:

CREATE TABLE `articles` (
  `aid` int(11) NOT NULL AUTO_INCREMENT,
  `sysartno` int(11) NOT NULL,
  `sku` varchar(20) COLLATE utf8_bin NOT NULL,
  `localartno` int(11) NOT NULL,
  `articlename` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `shortdesc` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `desc` text COLLATE utf8_bin,
  `brand` int(11) DEFAULT NULL,
  `HAN` varchar(45) COLLATE utf8_bin DEFAULT NULL,
  `priceNetto` double DEFAULT NULL,
  `tax` double DEFAULT NULL,
  `priceBrutto` double DEFAULT NULL,
  `searchTerms` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `metaKeywords` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `metaTitle` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  `metaDesc` text COLLATE utf8_bin,
  `gid` int(11) NOT NULL,
  `sid` int(11) NOT NULL,
  PRIMARY KEY (`aid`),
  UNIQUE KEY `sysartno_UNIQUE` (`sysartno`),
  UNIQUE KEY `localartno_UNIQUE` (`localartno`),
  KEY `grundFK_idx` (`gid`),
  KEY `statusFK_idx` (`sid`),
  KEY `brandFK_idx` (`brand`),
  CONSTRAINT `grundFK` FOREIGN KEY (`gid`) REFERENCES `irrelevanttable1` (`gid`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `markeFK` FOREIGN KEY (`brand`) REFERENCES `irrelevanttable2` (`mid`) ON DELETE NO ACTION ON UPDATE NO ACTION,
  CONSTRAINT `statusFK` FOREIGN KEY (`sid`) REFERENCES `irrelevanttable3` (`sid`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

The model file created on top of this is from the webtools.php; the initalize():

public function initialize()
{   
    $this->setSource("articles");
    $this->hasMany('aid', 'irrelevanttable4', 'aid', ['alias' => 'irrelevanttable4']);
    $this->hasMany('aid', 'irrelevanttable5', 'aid', ['alias' => 'irrelevanttable5']);
    $this->hasMany('aid', 'irrelevanttable6', 'aid', ['alias' => 'irrelevanttable6']);
    $this->belongsTo('gid', '\irrelevanttable1', 'gid', ['alias' => 'irrelevanttable1']);
    $this->belongsTo('brand', '\irrelevanttable2', 'mid', ['alias' => 'irrelevanttable2']);
    $this->belongsTo('sid', '\irrelevanttable3', 'sid', ['alias' => 'irrelevanttable3']);
}

Now, I populate a new instance of the model with the data:

$article = new articles();

$article->setSysartno($art->sysartno);
$article->setSku($art->sku);
$article->setLocalartno($art->localartno);
$article->setArticlename($art->articlename);
...
(all the fields get populated)

Now, when I $article->save(); the model, I get a error message "Articlename is required". My first thought was "oh no, not THAT again" (see this, might be related: https://forum.phalcon.io/discussion/20567/query-tells-field-is-required-but-is-set)

Then I checked the values of $article BEFORE the save(), and it shows me all fields well populated, just like the data provided in $art (which is always populated by the way, so no random null values from there).

Then I made NULL values in articlename possible (just for testing). This way, the query sent from the save() looks like this:

INSERT INTO `articles` (all the field names) VALUES (105144, 'DE-105144', 105144, null, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, DEFAULT, 9, 1)

If I now check $article AFTER the save(), all values except the first four (aid, sysartno, sku, localartno) and the last two (gid, sid) are set to null (the last two fields contain hardcoded values). So the data seems to get lost inside the save()-call.

Also:

$tmp = $article; 
var_dump($tmp); // fine
var_dump($article); // fine
echo $article->getArticlename(); // fine
echo $tmp->getArticlename(); // fine
$tmp->save(); // Articlename required

This will probably work with raw SQL, but I want to make use of the foreign keys from the model, so this isn't really an option. I do know this problem with "xy is required" has been asked some times on this forum, but none of the solutions helped me. Hard coding the "problematic values" doesn't change anything, too.


Oh and another thing; why does the output of var_dump($modelInstance) contain ALL the application data, like metaDataCache, configiguration values, paths, or in short: EVERYTHING thats inside the DI?

edited Jun '20

Do you have custom validation set on your model? I don't know if webtools creates the validation() method.

As for the other thing: Models extend a bunch of stuff that has access to all those things mentioned, which makes dumping them pretty much useless. However, dumping toArray() works great:

var_dump($Model->toArray());

Personally, I use this custom function that automatically calls toArray() if an object has it defined:

function dump($passed,$use_vardump = FALSE,$backtrace_offset = 0)
{
    $is_cli = (php_sapi_name() == 'cli');
    $newline = ($is_cli) ? "\n" : '<br />';
    $backtrace = debug_backtrace();
    $file = $backtrace[$backtrace_offset]['file'];
    $line = $backtrace[$backtrace_offset]['line'];

    if(!$is_cli){
        echo '<pre>';
    }
    echo "File: $file$newline";
    echo "Line: $line$newline";

    if($use_vardump){
        var_dump($passed);
    }
    else{
        if(is_array($passed)){
            print_r($passed);
        }
        else if(is_object($passed)){
            if(method_exists($passed, 'toArray')){
                print_r($passed->toArray());
            }
            else{
                print_r($passed);
            }
        }
        else{
            echo $passed;
        }
    }
    if(!$is_cli){
        echo '</pre>';
    }

    if(count(ob_get_status()) != 0){
        ob_flush();
    }
}
dump($Model);

It also outputs where it was called, so I never have to hunt through my code trying to find debugging statements.



4.0k
edited Jun '20

Webtools only generates validators for fields like "email", which returns the correct EmailValidator then (obviously). I don't have any custom validators set.

I felt free to use your dump function with surprising results.

...
(all the setters get called)
echo $article->getArticlename();
$this->dump($article);
$this->dump($article, true);
echo $article->getArticlename();

Output:

(the correct articlename gets displayed)

File: D:\path\to\app\controllers\SomeController.php
Line: 173
Array
(
    [aid] => 
    [sysartno] => 105144
    [sku] => 105144
    [localartno] => 105144
    [articlename] => 
    [shortdesc] => 
    [desc] => 
    [brand] => 
    [HAN] => 
    [priceNetto] => 
    [tax] => 
    [priceBrutto] => 
    [searchTerms] => 
    [metaKeywords] => 
    [metaTitle] => 
    [metaDesc] => 
    [gid] => 9
    [sid] => 1
)
File: D:\path\to\app\controllers\SomeController.php
Line: 174
D:\path\to\app\controllers\ControllerBase.php:63:
object(articles)[191]
  protected 'aid' => null (gets filled in by the database)
  protected 'sysartno' => int 105144 (correct value)
  protected 'sku' => int 105144 (correct value)
  protected 'localartno' => int 105144 (correct value)
  protected 'articlename' => (correct value)
  protected 'shortDesc' => (correct value)
  protected 'desc' => (correct value)
  protected 'brand' => (correct value)
  protected 'hAN' =>  (correct value)
  protected 'priceNetto' => (correct value)
  protected 'tax' => (correct value)
  protected 'priceBrutto' =>  (correct value)
  protected 'searchTerms' =>  (correct value)
  protected 'metaKeywords' => null  (correct value)
  protected 'metaTitle' => null  (correct value)
  protected 'metaDesc' => null  (correct value)
  protected 'gid' => int 9 (correct value)
  protected 'sid' => int 1 (correct value)

(the correct articlename gets displayed)
edited Jun '20

It looks like the article name doesn't get displayed when calling print_r, but it does when calling var_dump?

So just to recap - you've got a model with a property articleName that defaults to NULL. Even when setting articleName to a string, you get a validation error?

Not a permanent solution, but what if you just bypassed validation by writing your own validation method? That could at least get the query sent to the database, so you can see exactly what values it's dealing with.

public function validation(){ 
    return TRUE; 
}


4.0k

It looks like the article name doesn't get displayed when calling print_r, but it does when calling var_dump?

This.

So just to recap - you've got a model with a property articleName that defaults to NULL. Even when setting articleName to a string, you get a validation error?

It doesn't default to null, it sets to null (atleast for print_r and save()). See the table definition and the query above; the fields that have default null also get this default value in the query, instead of their real values. So for the recap: If I set the value to a string (as desired), save() and print_r treat it as null.

Not a permanent solution, but what if you just bypassed validation by writing your own validation method? That could at least get the query sent to the database, so you can see exactly what values it's dealing with.

public function validation(){ 
  return TRUE; 
}

Still yields Articlename is required, with no query sent.

Can you post the entirety of your model? I'll see if I can reproduce.

Are you using Phalcon v4 or v3.4?



4.0k

4.0.6 on Windows 10 with PHP 7.4.4., MySQL 5.7.11.

I have the same problem with another model, but it behaves exactly like the article model. I opened a Github issue about it (before your first answer), so I suggest you use the (much smaller) model in the issue:

https://github.com/phalcon/cphalcon/issues/15061

CREATE TABLE `barcodes` (
  `cid` int(11) NOT NULL AUTO_INCREMENT,
  `aid` int(11) NOT NULL,
  `Barcode` varchar(20) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`cid`),
  KEY `articleFK_idx` (`aid`),
  CONSTRAINT `articleFK` FOREIGN KEY (`aid`) REFERENCES `articles` (`aid`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
<?php

class barcodes extends \Phalcon\Mvc\Model
{
    protected $cid;
    protected $aid;
    protected $barcode;

    public function setCid($cid)
    {
        $this->cid = $cid;

        return $this;
    }

    public function setAid($aid)
    {
        $this->aid = $aid;

        return $this;
    }

    public function setBarcode($barcode)
    {
        $this->barcode = $barcode;

        return $this;
    }

    public function getCid()
    {
        return $this->cid;
    }

    public function getAid()
    {
        return $this->aid;
    }

    public function getBarcode()
    {
        return $this->barcode;
    }

    public function initialize()
    {
        $this->setSource("barcodes");
        $this->belongsTo('aid', '\articles', 'aid', ['alias' => 'articles']);
    }

    public static function find($parameters = null): \Phalcon\Mvc\Model\ResultsetInterface
    {
        return parent::find($parameters);
    }

    public static function findFirst($parameters = null)
    {
        return parent::findFirst($parameters);
    }

    public function columnMap()
    {
        return [
            'cid' => 'cid',
            'aid' => 'aid',
            'Barcode' => 'Barcode'
        ];
    }

}
$barcodeArray = array('123', '456');

foreach($barcodeArray as $code)
{
    $bc = new barcodes();

    $bc->setAid($aid);
    $bc->setBarcode($code);

    var_dump($bc->barcode);
    echo $bc->getBarcode()."<br />";

    if ($bc->save() == false) 
    {
        foreach ($bc->getMessages() as $message)
            echo $message."<br />";
    }

    echo $bc->getBarcode();
}


125.7k
Accepted
answer

Ok, I think I have it figured out. And you're going to kick yourself.

setBarcode() sets $this->barcode, but the model has $this->Barcode defined. Capital B. The model has also explicitly declared protected $barcode, but the column map uses Barcode.

So when you're calling setBarcode() and dumping $bc->barcode, yes it's working - it's setting the object's property $barcode. But that property is completely separate from the property that is mapped to a database column - Barcode.

By changing $barcode to $Barcode everywhere, the code worked.

This sounds like a bug in webtools.php rather than Phalcon.

If I were you, I'd go through and normalize all database columns and variables to lowercase, and all class names to capital case.



4.0k
edited Jun '20

This hurts a bit.

Solution works for articles too, also for the other tables from my post some week ago. I'll make an issue about this in the webtools repo. This is my fault in some ways, but I think webtools should take something like this in account.

Thank you so much for pointing this out, I'd never have seen this. :)

Yeah, it totally should. Someone put in an extra strtolower()

The only reason I noticed it is because lowercase classes and capitalized non-objects go against my personal coding style, so it stood out.