Search code examples
phpfat-free-framework

Using computed properties on FatFree mapper object (not retrieved from database)


I know I can have a metod like this, in my class that extends \DB\SQL\Mapper:

public function getAll()
{
  $this->upperBody = 'upper(body)';
  $all = $this->find();
  return $all;
}

Then, when called, this model will have a virtual(computed) property upperBody which will hold the uppercased value of body field in database.

But then I am limited only to what the underlying database engine offers in terms of functions.

Is it possible somehow to use these on-the-fly fields like this:

$this->stripped_tags = strip_tags($this->body);

Assuming of course we have an existing body field in a database?

If not, then how would I go about writing my own filter to use in templates,thus having instead {{@post.body|strip_tags}}... or for example {{@post.body|excerpt 30}}.

I solved this, by using cast():

    foreach ($this->model->getAll() as $p) {

        $c = $p->cast();
        $c[ 'excerpt' ] = chop_string( strip_tags($c[ 'body' ], '<p>'), $this->excerpt_limit);
        $all_posts[] = $c;
    }

    $f3->set('posts', $all_posts);

But it seems there must be a more elegant way to do this, as here I double the array processing just to assign one new computed property.

So is there a way to do it all in one go?

AMEND 1: As @ikkez suggested I tried doing this:

In my model class I added a property called upperTitle like so:

public $upperTitle;

Then in controller I have code like this:

$this->mdlPost->upperTitle = strtoupper($this->mdlPost->title);

After querying a database using $this->mdlPost->paginate(...,...,...) and var_dumping, upperTitle is an empty string.

AMEND 2: (suggested in comment by @ikkez)

public function __construct(\DB\SQL $db)
    {
        $this->db = $db;
        parent::__construct($db, 'posts');

        $this->onload(function($self){
            $self->set('upper_title', strtoupper($self->get('title')));
        });
    }

Again, no success.

After dumping a mapper object, maybe this could help?

enter image description here

enter image description here

So it's visible as adhoc whatever that might be, but null as a property on a mapper object itself.

I can see that value is NULL, and the expression is what the value should be. That's why a get an empty output when doing @post.upper_title in a template, so how would you resolve this. When doing things normally expression takes the query string, and the value is the result of that query being executed, but here obviously that's not the case.


Solution

  • The Mapper implementation is a little more complex as one would expect it without looking at the source code. First you shouldn't try to set non-existing mapper fields as the Mapper implementation interprets them as virtual fields which need to be derived by the database engine. That's also the reason why the values are NULL. Instead, you should use real class attributes (as said by ikkez).

    Hints

    • The Mapper->cast() method ignores class attributes so if one wants to use it one should adapt the cast() method, too.

    • Defining virtual fields is also only effective before querying the database.


    Solution

    • Don't create (or update) virtual fields by calling Mapper->set() or setting Mapper->field = … (which calls internally Mapper->set())

    • Create class attributes instead

    • Use the ONLOAD event to populate the attributes


    Snippet

    The following snippet shows how to use the ONLOAD trigger to define custom class attributes for storing programmatically derived values. An executable example is available at: https://gist.github.com/Rayne/fd24f5b664788cdf35956222ce790c02

    /**
     * @property string title
     * @see https://gist.github.com/Rayne/fd24f5b664788cdf35956222ce790c02
     */
    class TestMapper extends \DB\SQL\Mapper {
        /**
         * Custom mapper field which isn't backed and persisted by the database.
         *
         * @var null|string
         */
        public $upper_title;
    
        /**
         * @param \DB\SQL $sql
         */
        public function __construct(\DB\SQL $sql) {
            parent::__construct($sql, 'test');
    
            $this->onload(function (TestMapper $mapper) {
                $mapper->upper_title = strtoupper($mapper->title);
            });
        }
    }
    

    Read from the database:

    $mapper = new TestMapper($sql);
    for ($mapper->load(); !$mapper->dry(); $mapper->next()) {
        printf("title:       %s\n", $mapper->title);
        printf("upper_title: %s\n", $mapper->upper_title);
    }
    

    Result:

    title:       Hello World
    upper_title: HELLO WORLD