XF 2.2 entity_structure Listener

Anatoliy

Well-known member
Following the "Let's build addon" I added two columns to xf_user table: av_aw_processed and av_aw_ignored.

PHP:
    public function installStep1()
    {
        $this->schemaManager()->alterTable('xf_user', function(Alter $table)
        {
            $table->addColumn('av_aw_processed', 'int')->setDefault(0);
        });
    }

    public function installStep2()
    {
        $this->schemaManager()->alterTable('xf_user', function(Alter $table)
        {
            $table->addColumn('av_aw_ignored', 'tinyint')->setDefault(0);
        });
    }

I added code event listener as instructed. I'm stuck with a code in the Listener.php

PHP:
class Listener
{
    public static function userEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
        $structure->columns['av_aw_processed'] = ['type' => Entity::BOOL, 'default' => time()];
        $structure->columns['av_aw_ignored'] = ['type' => Entity::BOOL, 'default' => false];
    }
}

What should go for av_aw_processed instead of Entity::BOOL? self::UINT? Or what?

Thanks in advance.
 
Solution
self wouldn't work here because self essentially means "this class".

You'll want to use Entity::UINT because these type constants exist in the Entity class.

However, you may be running into an issue because Entity does not give PHP enough information to know where that class is.

There are two ways around this and either is valid (though second is probably preferred for brevity)

1. Refer to the fully qualified class name, e.g.

PHP:
class Listener
{
    public static function userEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
        $structure->columns['av_aw_processed'] = ['type' => \XF\Mvc\Entity\Entity::UINT, 'default' => time()]...

Chris D

XenForo developer
Staff member
The answer to that question very much depends on what data you're storing in there.

If it is simply a 0 or a 1 then it would be BOOL. If it is a number ranging from 0 up to ~4 billion then it would be UINT. If it's a number ranging from -2147483648 up to 2147483647 then it would be INT.

You are setting the default value to time() so I'm assuming UINT is what you're after as it looks like you're storing a unix timestamp.
 

Chris D

XenForo developer
Staff member
self wouldn't work here because self essentially means "this class".

You'll want to use Entity::UINT because these type constants exist in the Entity class.

However, you may be running into an issue because Entity does not give PHP enough information to know where that class is.

There are two ways around this and either is valid (though second is probably preferred for brevity)

1. Refer to the fully qualified class name, e.g.

PHP:
class Listener
{
    public static function userEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
        $structure->columns['av_aw_processed'] = ['type' => \XF\Mvc\Entity\Entity::UINT, 'default' => time()];
        $structure->columns['av_aw_ignored'] = ['type' => \XF\Mvc\Entity\Entity::BOOL, 'default' => false];
    }
}

2. Define an alias, e.g.

PHP:
use XF\Mvc\Entity\Entity;

class Listener
{
    public static function userEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
        $structure->columns['av_aw_processed'] = ['type' => Entity::UINT, 'default' => time()];
        $structure->columns['av_aw_ignored'] = ['type' => Entity::BOOL, 'default' => false];
    }
}

The use declaration outside of the class is now telling PHP that Entity is an alias of XF\Mvc\Entity\Entity. So whenever it comes across e.g. Entity::UINT it now knows you mean \XF\Mvc\Entity\Entity::UINT
 
Solution

Anatoliy

Well-known member
2. Define an alias, e.g.

Thank you very much!
I ended up with this. Do hope nothing will explode. )

PHP:
<?php

namespace AV\ActivityWatcher;

use XF\Mvc\Entity\Entity;

class Listener
{
    public static function userEntityStructure(\XF\Mvc\Entity\Manager $em, \XF\Mvc\Entity\Structure &$structure)
    {
        $structure->columns['av_aw_processed'] = ['type' => Entity::UINT, 'default' => time()];
        $structure->columns['av_aw_ignored'] = ['type' => Entity::BOOL, 'default' => false];
    }
}
 

Anatoliy

Well-known member
Oh, wait... I just understood.

$structure->columns['av_aw_processed'] = ['type' => Entity::UINT, 'default' => time()];

'default' => time() means that whenever a user will be edited (not with my add-on) the current timestamp will be written into 'av_aw_processed'? That's not what I was planning. I want it happen only when a special button in my add-on got clicked.

Does it mean I should remove , 'default' => time() ?

Sorry that I'm such a noob. )
 

Anatoliy

Well-known member
Hmm... but then again... Let's say I "processed" a user and marked him as "Processed" in my add-on. Timestamp got written into av_aw_processed. But if later I'll edit this user profile in ACP the value in av_aw_processed will be overwriten to 0?
I'll lose the "mark" that he was already "processed".

The idea is that addon shows me a list of members who started a thread within say last 60 days and were not active for say last 30 days.
I'm sending them a PM if their option set to notify about PM with email. And mark them "processed". And they will not appear in my "Inactive thread creators" list say for 30 days.

May be I do architecture wrong and I should create a custom table, not a column? Then nothing will overwrite data in it. But this way I will have an extra query I guess, which is not good as I understand.

Please help me untangle. )
 

Chris D

XenForo developer
Staff member
Hmm... but then again... Let's say I "processed" a user and marked him as "Processed" in my add-on. Timestamp got written into av_aw_processed. But if later I'll edit this user profile in ACP the value in av_aw_processed will be overwriten to 0?
I'll lose the "mark" that he was already "processed".
Nope, the 0 will only be written if a value doesn't yet exist, such as when a new user is being created.
 

Anatoliy

Well-known member
Oops ... It looks like I missed something. Did I have to modify some standard method like memberCreate or memberSave or something like that?

I see an error in ACP

Code:
MySQL statement prepare error [1054]: Unknown column 'av_aw_processed' in 'field list'
Today at 7:22 AM src/XF/Db/AbstractStatement.php:230
MySQL statement prepare error [1054]: Unknown column 'av_aw_processed' in 'field list'
Today at 7:21 AM src/XF/Db/AbstractStatement.php:230
Showing all items
Log out Community platform by XenForo®
Oregon Fishing Forum v2.2.11
Server error log
XF\Db\Exception: MySQL statement prepare error [1054]: Unknown column 'av_aw_processed' in 'field list' src/XF/Db/AbstractStatement.php:230
Generated by: Unknown account Dec 8, 2022 at 7:22 AM
Stack trace
INSERT  INTO `xf_user` (`visible`, `activity_visible`, `user_group_id`, `timezone`, `language_id`, `last_summary_email_date`, `username`, `email`, `user_state`, `privacy_policy_accepted`, `terms_accepted`, `display_style_group_id`, `secondary_group_ids`, `secret_key`, `user_id`, `username_date`, `username_date_visible`, `style_id`, `permission_combination_id`, `message_count`, `question_solution_count`, `alerts_unviewed`, `alerts_unread`, `conversations_unread`, `register_date`, `last_activity`, `trophy_points`, `avatar_date`, `avatar_width`, `avatar_height`, `avatar_highdpi`, `gravatar`, `security_lock`, `is_moderator`, `is_admin`, `is_staff`, `is_banned`, `reaction_score`, `vote_score`, `custom_title`, `warning_points`, `av_aw_processed`, `av_aw_ignored`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
------------

#0 src/XF/Db/Mysqli/Statement.php(198): XF\Db\AbstractStatement->getException('MySQL statement...', 1054, '42S22')
#1 src/XF/Db/Mysqli/Statement.php(41): XF\Db\Mysqli\Statement->getException('MySQL statement...', 1054, '42S22')
#2 src/XF/Db/Mysqli/Statement.php(56): XF\Db\Mysqli\Statement->prepare()
#3 src/XF/Db/AbstractAdapter.php(96): XF\Db\Mysqli\Statement->execute()
#4 src/XF/Db/AbstractAdapter.php(220): XF\Db\AbstractAdapter->query('INSERT  INTO `x...', Array)
#5 src/XF/Mvc/Entity/Entity.php(1516): XF\Db\AbstractAdapter->insert('xf_user', Array, false)
#6 src/XF/Mvc/Entity/Entity.php(1248): XF\Mvc\Entity\Entity->_saveToSource()
#7 src/XF/Service/User/Registration.php(301): XF\Mvc\Entity\Entity->save()
#8 src/XF/Service/ValidateAndSavableTrait.php(42): XF\Service\User\Registration->_save()
#9 src/XF/Pub/Controller/Register.php(426): XF\Service\User\Registration->save()
#10 src/XF/Mvc/Dispatcher.php(352): XF\Pub\Controller\Register->actionRegister(Object(XF\Mvc\ParameterBag))
#11 src/XF/Mvc/Dispatcher.php(259): XF\Mvc\Dispatcher->dispatchClass('XF:Register', 'Register', Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\Register), NULL)
#12 src/XF/Mvc/Dispatcher.php(115): XF\Mvc\Dispatcher->dispatchFromMatch(Object(XF\Mvc\RouteMatch), Object(XF\Pub\Controller\Register), NULL)
#13 src/XF/Mvc/Dispatcher.php(57): XF\Mvc\Dispatcher->dispatchLoop(Object(XF\Mvc\RouteMatch))
#14 src/XF/App.php(2353): XF\Mvc\Dispatcher->run()
#15 src/XF.php(524): XF\App->run()
#16 index.php(20): XF::runApp('XF\\Pub\\App')
#17 {main}
Request state
array(4) {
  ["url"] => string(18) "/register/register"
  ["referrer"] => string(44) "https://www.xxxxxxx.com/register/"
  ["_GET"] => array(0) {
  }
  ["_POST"] => array(14) {
    ["_xfToken"] => string(8) "********"
    ["username"] => string(0) ""
    ["Xxxxxxxxxxx"] => string(14) "Delta "
    ["Xxxxxxxxxxx"] => string(18) "xxxxxxxx@gmail.com"
    ["password"] => string(8) "********"
    ["Xxxxxxxx"] => string(8) "********"
    ["captcha_question_answer"] => string(5) "xxxxxxxx"
    ["captcha_question_hash"] => string(40) "xxxxxxx"
    ["accept"] => string(1) "1"
    ["reg_key"] => string(16) "xxxxxxxxx"
    ["Xxxxxxxxxx"] => string(15) "America/Chicago"
    ["_xfRequestUri"] => string(10) "/register/"
    ["_xfWithData"] => string(1) "1"
    ["_xfResponseType"] => string(4) "json"
  }
}
 

Anatoliy

Well-known member
I'm re-reading again "Let's build an add-on" and it looks like I may be have to add a class extension for XF\Admin\Controller\User ?
But do I really have to do it if in Setup.ph I indicated setDefault(0) for both custom columns? Shouldn't the system just put 0 in both fields without any class/action extension?

Please advise.

PHP:
<?php

namespace AV\ActivityWatcher;

use XF\AddOn\AbstractSetup;
use XF\AddOn\StepRunnerInstallTrait;
use XF\AddOn\StepRunnerUninstallTrait;
use XF\AddOn\StepRunnerUpgradeTrait;

use XF\Db\Schema\Alter;

class Setup extends AbstractSetup
{
    use StepRunnerInstallTrait;
    use StepRunnerUpgradeTrait;
    use StepRunnerUninstallTrait;

    public function installStep1()
    {
        $this->schemaManager()->alterTable('xf_user', function(Alter $table)
        {
            $table->addColumn('av_aw_processed', 'int')->setDefault(0);
        });
    }

    public function installStep2()
{
    $this->schemaManager()->alterTable('xf_user', function(Alter $table)
    {
        $table->addColumn('av_aw_ignored', 'tinyint')->setDefault(0);
    });
}

public function uninstallStep1()
{
    $this->schemaManager()->alterTable('xf_user', function(Alter $table)
    {
        $table->dropColumns('av_aw_processed');
    });
}

public function uninstallStep2()
{
    $this->schemaManager()->alterTable('xf_user', function(Alter $table)
    {
        $table->dropColumns('av_aw_ignored');
    });
}

}
 
Top