XF 2.2entity_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)
{
});
}

public function installStep2()
{
\$this->schemaManager()->alterTable('xf_user', function(Alter \$table)
{
});
}``````

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?

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.

Anatoliy

Well-known member
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.
Yes!
Thank you!

Anatoliy

Well-known member
'type' =>Entity::UINT or 'type' =>self::UINT?

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`

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];
}
}``````

Chris D

XenForo developer
Staff member
Looks good to me!

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. )

Chris D

XenForo developer
Staff member
You probably just want it to be set to `'default' => 0`

Thank you!!!

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.

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
Nope, the 0 will only be written if a value doesn't yet exist, such as when a new user is being created.
Cool!
Tank you!!!

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()
#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) "********"
["Xxxxxxxxxxx"] => string(14) "Delta "
["Xxxxxxxxxxx"] => string(18) "xxxxxxxx@gmail.com"
["Xxxxxxxx"] => string(8) "********"
["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?

PHP:
``````<?php

namespace AV\ActivityWatcher;

use XF\Db\Schema\Alter;

class Setup extends AbstractSetup
{
use StepRunnerInstallTrait;
use StepRunnerUninstallTrait;

public function installStep1()
{
\$this->schemaManager()->alterTable('xf_user', function(Alter \$table)
{
});
}

public function installStep2()
{
\$this->schemaManager()->alterTable('xf_user', function(Alter \$table)
{
});
}

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');
});
}

}``````

Replies
1
Views
348
Replies
3
Views
312
Replies
1
Views
273
Replies
3
Views
336
Replies
0
Views
222