Love Fuel?    Donate

FuelPHP Forums

Ask your question about FuelPHP in the appropriate forum, or help others by answering their questions.
Updating model and its loaded relations with a single nested array
  • Hi,

    It seems like updating a model and its nested relationships should be possible with a nested array, but I can't find how.

    Currently I have the following code to update my user:

    $query = Model_User::query()
    ->where('id', \Input::json('id'))
    ->related('contact');
    $user = $query->get_one();

    $user->username = \Input::json('username');
    $user->contact->set(\Input::json('contact'));
    $user->save();

    My Input.json() array looks like this:

    Array
    (
    [id] => 2
    [username] => leo
    [contact] => Array
    (
    [locale] => nl_NL
    )

    )

    This works.

    However I have the feeling this can be done quicker. But when I try this:


    $query = Model_User::query()
    ->where('id', \Input::json('id'))
    ->related('contact');
    $user = $query->get_one();

    $user->set(\Input::json());
    $user->save();

    I get an error: SQLSTATE[42000]: Syntax error or access violation: 1064

    Is it possible to update a model from a nested array like this? If so, am I overlooking something?

    Any feedback is appreciated.
  • The question is what  this error means. You clearly don't get a Fuel error, you get a SQL error, so I'm curious what is generated.

  • Hi Harro,


    Many thanks for looking into this.


    The error I get is:


    Fuel\Core\Database_Exception [ 42000 ]:

    SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WHERE `id` = '2'' at line 1 with query: "UPDATE `users` SET WHERE `id` = '2'"



    My code looks like this:

    public function put_model($accountId = 0)
    {
    $query = Model_User::query()

    ->where('id', \Input::json('id'))
    ->related('contact');
    $user = $query->get_one();

    $user->set(\Input::json()); // TODO: y u no work?

    // $user->username = \Input::json('username');
    // $user->contact->set(\Input::json('contact')); // setting this object does work, but it has no nested objects
    $user->save();

    return $this->response($user->to_array(), 200);
    }


    The data I PUT is:

    {"id":2,"username":"leo","contact":{"locale":"be_BE"}}

    My tables look like this:


    users:
    image

    contacts:
    image

  • I don't really understand this error, the ORM sees an updated object, but it doesn't update any fields?

    set() supports an assoc array as input, so that should at least make sure 'username' got updated. It will ignore 'contact' (well, not really, it sees it as an array, will loop again, and then create the custom variable "locale").

    What happens if you do
    $user->set(array('username' => 'leo'));

  • $user->set(array('username' => 'leo'));

    Works just fine, just like:

    $user->contact->set(\Input::json('contact'));
    , which also sets an array.
  • Which version of Fuel are you on?

    It isn't by any chance that you have an issue elsewhere because 'id' is part of the array, and ORM objects don't like their primary key being updated?
  • I tried to surround the save with a try/catch, and the print the sql like so:

            try {
    $user->save();
    }
    catch(\Exception $e) {}

    \Debug::d(\DB::last_query());


    I get this SQL:

            SELECT `t0`.`id` AS `t0_c0`, `t0`.`username` AS `t0_c1`, `t0`.`password` AS `t0_c2`, `t1`.`id` AS `t1_c0`, `t1`.`title` AS `t1_c1`, `t1`.`user_id` AS `t1_c2`, `t1`.`locale` AS `t1_c3` FROM (SELECT `t0`.`id`, `t0`.`username`, `t0`.`password` FROM `users` AS `t0` WHERE `t0`.`id` = 2 LIMIT 1) AS `t0` LEFT JOIN `contacts` AS `t1` ON (`t0`.`id` = `t1`.`user_id`)


    But this doesn't seem to be the SQL that the save operation generated. Is there a way to view the SQL that ORM generates when saving?
  • Ah yes, I'll check what happens when I remove the id.

    My version is 1.7.2
  • This is the initial get(), so nothing  it doesn't even attempt to save.

    What is $e, in other words, was there an exception caught?
  • You are right about the primary key issue. However this does generate another error.

    When I do this:

            $data = \Input::json();
    unset($data['id']);

    $user->set($data); // TODO: y u no work?

    $user->save();


    I get the following error:

    Fuel\Core\FuelException [ Error ]:
    Invalid Model instance added to relations in this model.
  • HarroHarro
    Accepted Answer
    Ok.

    The primary key issue is solved in 1.8/develop, as long as the value set is the current value. Exactly to cover the issue you have with it.

    This new exception is because this array will create model property 'contact' (since set does not support relations), and when saving the object, it expects it to be an instance of your contact model, but is isn't, it contains array('locale' => 'NL_nl');

    1.8/develop also has partial support for recursive set()'s.

    You can try switching ORM to 1.8/develop, and see if that would fix your problem?
  • Good to know what is happening, thanks for your swift feedback!

    I've tried to install the latest version of ORM but I don't think I understand what the best way would be to do this. I assumed that updating the version numbers in composer.json like so

               
    "package": {
    "name": "fuel/orm",
    "type": "fuel-package",
    "version": "1.8",
    "dist": {
    "url": "https://github.com/fuel/orm/archive/1.8/develop.zip",
    "type": "zip"
    },
    "source": {
    "url": "https://github.com/fuel/orm.git",
    "type": "git",
    "reference": "1.8/develop"
    }
    }


    and running composer update would do the trick, but it says 'Nothing to install or update'.
  • Ah, I forgot to change

    "fuel/orm": "1.8",


    It looks like composer has now installed 1.8, but I still get the following error when doing my PUT request:

    Fuel\Core\FuelException [ Error ]:
    Invalid Model instance added to relations in this model.

    PKGPATH/orm/classes/hasone.php @ line 122
  • Are you sure a contact object exists for that user? Because it does not create a new one on the fly, it can only update an already loaded one.

    edit: it does according to your DB dump

    Can you check the ORM model.php, and see if line 1249 tests against "instanceof static"? Just to make sure you have the latest code?

    If so, can you debug it? That if should be true, and the set() should be called at L#1251.
  • It does test for "instanceof static", so I must have 1.8:
    1249: if ($this->_data_relations[$property] instanceof static and is_array($value))


    I added this code :

    \Debug::dump('orm test: ', $property, $value, $this->_data_relations[$property], $this->_data_relations[$property] instanceof static and is_array($value));
    die();


    Here is the result I get:

    http://arttech.nl/orm-test-result.txt
  • Everything looks ok, but the last should return "true". $value is an array we can see from the dump, so it must be the instanceof that fails.

    Can you dump that indivually (so without the "and is_array..."), to check if it is the instanceof test, of if the thing needs brackets because of the and...

    I have a feeling it must be

    if (($this->_data_relations[$property] instanceof static) and is_array($value))

  • Just to be sure, here is my contact.php file:

    class Model_Contact extends \Orm\Model
    {
    protected static $_properties = array(
    'id',
    'title',
    'user_id',
    'locale'
    );

    protected static $_belongs_to = array('user');
    }


    and user.php starts like this:

    class Model_User extends \Orm\Model
    {
    protected static $_properties = array(
    'id',
    'username',
    'password',
    );

    protected static $_to_array_exclude = array (
    'password',
    'login_hash' // exclude these columns from being exported
    );

    protected static $_has_one = array(
    'contact'
    );


    Could something be wrong there?
  • Yup, the result of

     \Debug::dump('orm test: ', $property, $value, ($this->_data_relations[$property] instanceof static), is_array($value));


    Is:

    PKGPATH/orm/classes/model.php @ line: 1249
    Variable #1:
    (String): "orm test: " (10 characters)


    Variable #2:
    (String): "contact" (7 characters)


    Variable #3:
    (Array, 1 element) ↵
    locale (String): "be_BE" (5 characters)


    Variable #4:
    (Boolean): false


    Variable #5:
    (Boolean): true
  • Ok, when I change your line 1249 to:

    if (is_a($this->_data_relations[$property], __CLASS__) and is_array($value))
    it works :)

    I think instanceof static doesn't work in this case due to this issue: http://proger.i-forge.net/The_PHP_dualism_-_self_and_static/SSM
  • HarroHarro
    Accepted Answer
    Hmm, yeah. always tricky this self vs static business.

    I guess in this case self would do, as you would want to know if it is an \Orm\Model instance.

    I'll push the fix in a minute.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

In this Discussion