Level-2 / Maphper

Maphper - A php ORM using the Data Mapper pattern
BSD 2-Clause "Simplified" License
52 stars 7 forks source link

Unexpected behaviour ? #47

Closed tontof closed 7 years ago

tontof commented 7 years ago

As in README

$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id', ['editmode' => true]);
$blogs = new \Maphper\Maphper($blogSource);

$blog = new stdClass;

$blog->title = 'My Blog Title';
$blog->content = 'This is my first blog entry';

//Store the blog using the next available ID
$blogs[] = $blog;

echo 'The new blog ID is :' . $blog->id;

it works

But if I use resultClass

class Blog {}

$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id', ['editmode' => true]);
$blogs = new \Maphper\Maphper($blogSource, ['resultClass' => 'Blog']);

$blog = new Blog();

$blog->title = 'My Blog Title';
$blog->content = 'This is my first blog entry';

//Store the blog using the next available ID
$blogs[] = $blog;

echo 'The new blog ID is :' . $blog->id;

I've got PHP Notice: Undefined property: Blog::$id

I guess it's because of the createNew method from maphper that does not handle stdClass the same way. If this is the expected behaviour, then i guess it should be warn in the README or maybe I'm wrong ?

solleer commented 7 years ago

Try defining the id property in the Blog class

tontof commented 7 years ago

The PHP Notice is removed, but the problem persists as it does not associate the id to the defined blog object.

solleer commented 7 years ago

These tests pass

public function testResultClassPrivateProperties() {
        $this->populateBlogs();
        $blogs = new \Maphper\Maphper($this->getDataSource('blog', 'id'), ['resultClass' => 'BlogPrivate']);

        $blog = $blogs[2];
        $this->assertInstanceOf('BlogPrivate', $blog);
        $this->assertEquals('blog number 2', $blog->getTitle());
    }

    public function testResultClassPrivatePropertiesWrite() {
        $this->populateBlogs();
        $blogs = new \Maphper\Maphper($this->getDataSource('blog', 'id'), ['resultClass' => 'BlogPrivate']);

        $blog = $blogs[2];

        $blog->setTitle('Title Updated');

        $blogs[] = $blog;

        //Reload the mapper to ensure no cacheing is used
        unset($blogs);
        unset($blog);
        $blogs = new \Maphper\Maphper($this->getDataSource('blog', 'id'), ['resultClass' => 'BlogPrivate']);

        $blog = $blogs[2];

        $this->assertEquals('Title Updated', $blog->getTitle());
    }
tontof commented 7 years ago

I don't think your pasted tests are linked with the problem. The correct one is:

    public function testResultClassPrivatePropertiesWriteWhenUpdating() {
        $this->dropTable('blog');
        $blogs = new \Maphper\Maphper($this->getDataSource('blog', 'id', ['editmode' => true]), ['resultClass' => 'Blog']);

        $blog = new Blog();

       $blog->title = 'My Blog Title';
        $blog->content = 'This is my first blog entry';

        //Store the blog using the next available ID
        $blogs[] = $blog;

        $this->assertEquals(1, $blog->id);
    }

Anyway, it works great now ! Thanks

tontof commented 7 years ago

Oups, it works better, but it does not work correctly with sqlite

$pdo = new PDO('sqlite:memory');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id', ['editmode' => true]);
$blogs = new \Maphper\Maphper($blogSource, ['resultClass' => 'Blog']);

$blog = new Blog;

$blog->title = 'My Blog Title';
$blog->content = 'This is my first blog entry';

//Store the blog using the next available ID
$blogs[] = $blog;

var_dump($blog);

gives

class Blog#19 (3) {
  public $id =>
  NULL
  public $title =>
  string(13) "My Blog Title"
  public $content =>
  string(27) "This is my first blog entry"
}

So the id property is now created correcly, but its value is NULL (same behaviour using $pdo = new PDO('sqlite:./test.db');

tontof commented 7 years ago

My bads, it works if the $id property is not defined in class

class Blog {
    //    public $id;
}

$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id', ['editmode' => true]);
$blogs = new \Maphper\Maphper($blogSource, ['resultClass' => 'Blog']);

$blog = new Blog;

$blog->title = 'My Blog Title';
$blog->content = 'This is my first blog entry';

//Store the blog using the next available ID
$blogs[] = $blog;

var_dump($blog);

Works correcty

class Blog#9 (3) {
  public $title =>
  string(13) "My Blog Title"
  public $content =>
  string(27) "This is my first blog entry"
  public $id =>
  string(1) "1"
}

But

class Blog {
       public $id;
}

$pdo = new PDO('sqlite::memory:');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$blogSource = new \Maphper\DataSource\Database($pdo, 'blog', 'id', ['editmode' => true]);
$blogs = new \Maphper\Maphper($blogSource, ['resultClass' => 'Blog']);

$blog = new Blog;

$blog->title = 'My Blog Title';
$blog->content = 'This is my first blog entry';

//Store the blog using the next available ID
$blogs[] = $blog;

var_dump($blog);

gives

class Blog#9 (3) {
  public $id =>
  NULL
  public $title =>
  string(13) "My Blog Title"
  public $content =>
  string(27) "This is my first blog entry"
}

which is quite unexpected, even if for me that's not really a problem as I do not defined the $id property !

tontof commented 7 years ago

Just in case. Adding $valueCopy->{$pk[0]} = $value->{$pk[0]}; to the offsetSet method seems to correct this problem.

    public function offsetSet($offset, $valueObj) {
        if ($valueObj instanceof \Maphper\Relation) throw new \Exception();

        //Extract private properties from the object
        $propertyReader = new \Maphper\Lib\VisibilityOverride();
        $value = $propertyReader->getProperties($valueObj);

        $value = $this->processFilters($value);
        $pk = $this->dataSource->getPrimaryKey();
        if ($offset !== null) $value->{$pk[0]} = $offset;
        $valueCopy = clone $value;
        $value = $this->wrap($value);
        $this->dataSource->save($value);
        $valueCopy->{$pk[0]} = $value->{$pk[0]}; // Added line !
        $value = $this->wrap((object) array_merge((array)$value, (array)$valueCopy));
        $this->createNew($value, $valueObj);
    }

edit: maybe with adding a test if (is_null($offset))