PHP hook, building hooks in your application
Introduction
One of the real challenges in building any type of framework, core or application is making it possible for the developers to hook into the business logic at specific points. Since PHP is not event based, nor it works with interrupts you have to come up an alternative.
The test case
Lets assume we are the main developers of a webshop framework. Programmers can use our framework to build complete webshops. Programmers can manage the orders that are placed on the webshop with the order class. The order class is part of our framework and we don’t want it to be extended by any programmer. However we don’t want to limit to programmers in their possibilities to hook into the orders process.
For example programmers should be able to send an email to the webshopowner if an order changes from one specific delivery status to another. This functionality is not part of the default behavior in our framework and is custom for the progammers webshop implementation.
Like said before, PHP doesn’t provide interrupts or real events so we need to come up with another way to implement hooks into our application. Lets take a look at the observer pattern.
Implementing the Observer pattern
The observer pattern is a design-pattern that describes a way for objects to be notified to specific state-changes in objects of the application.
For the first implementation we can use SPL. The SPL provides in two simple objects:
SPLSubject
- attach (new observer to attach)
- detach (existing observer to detach)
- notify (notify all observers)
SPLObserver
- update (Called from the subject (i.e. when it’s value has changed).
iOrderRef = $iOrderRef;
// Get order information from the database or an other resources
$this->iStatus = Order::STATUS_SHIPPED;
}
/**
* Attach an observer
*
* @param SplObserver $oObserver
* @return void
*/
public function attach(SplObserver $oObserver)
{
$sHash = spl_object_hash($oObserver);
if (isset($this->aObservers[$sHash])) {
throw new Exception('Observer is already attached');
}
$this->aObservers[$sHash] = $oObserver;
}
/**
* Detach observer
*
* @param SplObserver $oObserver
* @return void
*/
public function detach(SplObserver $oObserver)
{
$sHash = spl_object_hash($oObserver);
if (!isset($this->aObservers[$sHash])) {
throw new Exception('Observer not attached');
}
unset($this->aObservers[$sHash]);
}
/**
* Notify the attached observers
*
* @param string $sEvent, name of the event
* @param mixed $mData, optional data that is not directly available for the observers
* @return void
*/
public function notify()
{
foreach ($this->aObservers as $oObserver) {
try {
$oObserver->update($this);
} catch(Exception $e) {
}
}
}
/**
* Add an order
*
* @param array $aOrder
* @return void
*/
public function delete()
{
$this->notify();
}
/**
* Return the order reference number
*
* @return int
*/
public function getRef()
{
return $this->iOrderRef;
}
/**
* Return the current order status
*
* @return int
*/
public function getStatus()
{
return $this->iStatus;
}
/**
* Update the order status
*/
public function updateStatus($iStatus)
{
$this->notify();
// ...
$this->iStatus = $iStatus;
// ...
$this->notify();
}
}
/**
* Order status handler, observer that sends an email to secretary
* if the status of an order changes from shipped to delivered, so the
* secratary can make a phone call to our customer to ask for his opinion about the service
*
* @package Shop
*/
class OrderStatusHandler implements SplObserver
{
/**
* Previous orderstatus
* @var int
*/
protected $iPreviousOrderStatus;
/**
* Current orderstatus
* @var int
*/
protected $iCurrentOrderStatus;
/**
* Update, called by the observable object order
*
* @param Observable_Interface $oSubject
* @param string $sEvent
* @param mixed $mData
* @return void
*/
public function update(SplSubject $oSubject)
{
if(!$oSubject instanceof Order) {
return;
}
if(is_null($this->iPreviousOrderStatus)) {
$this->iPreviousOrderStatus = $oSubject->getStatus();
} else {
$this->iCurrentOrderStatus = $oSubject->getStatus();
if($this->iPreviousOrderStatus === Order::STATUS_SHIPPED && $this->iCurrentOrderStatus === Order::STATUS_DELIVERED) {
$sSubject = sprintf('Order number %d is shipped', $oSubject->getRef());
//mail('secratary@example.com', 'Order number %d is shipped', 'Text');
echo 'Mail sended to the secratary to help her remember to call our customer for a survey.';
}
}
}
}
$oOrder = new Order(26012011);
$oOrder->attach(new OrderStatusHandler());
$oOrder->updateStatus(Order::STATUS_DELIVERED);
$oOrder->delete();
?>
There are several problems with the implementation above. To most important disadvantage is that we have only one update method in our observer. In this update method we don’t know when and why we are getting notified, just that something happened. We should keep track of everything that happens in the subject. (Or use debug_backtrace… just joking, don’t even think about using it that way ever!).
Taking it a step further, events
Lets take a look at the next example, we will extend the Observer implementation with some an additional parameter for the eventname that occured.
Finishing up, optional data
iOrderRef = $iOrderRef;
// Get order information from the database or something else...
$this->iStatus = Order::STATUS_SHIPPED;
}
/**
* Attach an observer
*
* @param Observer_Interface $oObserver
* @return void
*/
public function attachObserver(Observer_Interface $oObserver)
{
$sHash = spl_object_hash($oObserver);
if (isset($this->aObservers[$sHash])) {
throw new Exception('Observer is already attached');
}
$this->aObservers[$sHash] = $oObserver;
}
/**
* Detach observer
*
* @param Observer_Interface $oObserver
* @return void
*/
public function detachObserver(Observer_Interface $oObserver)
{
$sHash = spl_object_hash($oObserver);
if (!isset($this->aObservers[$sHash])) {
throw new Exception('Observer not attached');
}
unset($this->aObservers[$sHash]);
}
/**
* Notify the attached observers
*
* @param string $sEvent, name of the event
* @param mixed $mData, optional data that is not directly available for the observers
* @return void
*/
public function notifyObserver($sEvent, $mData=null)
{
foreach ($this->aObservers as $oObserver) {
try {
$oObserver->update($this, $sEvent, $mData);
} catch(Exception $e) {
}
}
}
/**
* Add an order
*
* @param array $aOrder
* @return void
*/
public function add($aOrder = array())
{
$this->notifyObserver('onAdd');
}
/**
* Return the order reference number
*
* @return int
*/
public function getRef()
{
return $this->iOrderRef;
}
/**
* Return the current order status
*
* @return int
*/
public function getStatus()
{
return $this->iStatus;
}
/**
* Update the order status
*/
public function updateStatus($iStatus)
{
$this->notifyObserver('onBeforeUpdateStatus');
// ...
$this->iStatus = $iStatus;
// ...
$this->notifyObserver('onAfterUpdateStatus');
}
}
/**
* Order status handler, observer that sends an email to secretary
* if the status of an order changes from shipped to delivered, so the
* secratary can make a phone call to our customer to ask for his opinion about the service
*
* @package Shop
*/
class OrderStatusHandler implements Observer_Interface
{
protected $iPreviousOrderStatus;
protected $iCurrentOrderStatus;
/**
* Update, called by the observable object order
*
* @param Observable_Interface $oObservable
* @param string $sEvent
* @param mixed $mData
* @return void
*/
public function update(Observable_Interface $oObservable, $sEvent, $mData=null)
{
if(!$oObservable instanceof Order) {
return;
}
switch($sEvent) {
case 'onBeforeUpdateStatus':
$this->iPreviousOrderStatus = $oObservable->getStatus();
return;
case 'onAfterUpdateStatus':
$this->iCurrentOrderStatus = $oObservable->getStatus();
if($this->iPreviousOrderStatus === Order::STATUS_SHIPPED && $this->iCurrentOrderStatus === Order::STATUS_DELIVERED) {
$sSubject = sprintf('Order number %d is shipped', $oObservable->getRef());
//mail('secratary@example.com', 'Order number %d is shipped', 'Text');
echo 'Mail sended to the secratary to help her remember to call our customer for a survey.';
}
}
}
}
$oOrder = new Order(26012011);
$oOrder->attachObserver(new OrderStatusHandler());
$oOrder->updateStatus(Order::STATUS_DELIVERED);
$oOrder->add();
?>
Now we are able to take action on different events that occur.
Disadvantages
Although this implementation works quite well there are some drawbacks. One of those drawbacks is that we need to dispatch an event in our framework, if we don’t programmers can’t hook into our application. Triggering events everywhere give us a small performance penalty however I do think this way of working gives the programmers a nice way to hook into your application on those spots that you want them to hook in.
Just for the record
Notice that this code is just an example and can still use some improvements, for example: each observer is initialized even it will maybe never be notified, therefore I suggest to make use of lazy in some cases for loading the objects. There are other systems to hook into an application, more to follow!
Пользуюсь этим инструментом генерации изображения уже полгода. Качество стабильно высокое, а скорость работы радует каждый раз. Отличный выбор для профессионального использования: https://vc.ru/top_rating/2301994-luchshie-besplatnye-nejroseti-dlya-generatsii-izobrazheniy
MichaelPrion
30 Oct 25 at 9:54 am
драгон мани рабочее зеркало
DonaldflerB
30 Oct 25 at 9:55 am
seo курсы [url=https://kursy-seo-12.ru]seo курсы[/url] .
kyrsi seo_uvor
30 Oct 25 at 9:56 am
Thanks for ones marvelous posting! I actually enjoyed reading it,
you might be a great author. I will make certain to bookmark your blog and may come
back at some point. I want to encourage that you continue
your great work, have a nice weekend!
Lk21 Layarkaca21
30 Oct 25 at 9:59 am
linebet mobile
linebet uz скачать
30 Oct 25 at 10:00 am
güncel öztürk
burun estetiği fiyatları
30 Oct 25 at 10:01 am
waziqiyi.com – Mobile version looks perfect; no glitches, fast scrolling, crisp text.
Susan Palmeri
30 Oct 25 at 10:01 am
Заказать диплом любого университета мы поможем. Купить диплом магистра в Санкт-Петербурге – [url=http://diplomybox.com/kupit-diplom-magistra-v-sankt-peterburge/]diplomybox.com/kupit-diplom-magistra-v-sankt-peterburge[/url]
Cazrkyk
30 Oct 25 at 10:02 am
обучение seo [url=www.kursy-seo-12.ru/]обучение seo[/url] .
kyrsi seo_bror
30 Oct 25 at 10:03 am
драгон мани казино
DonaldflerB
30 Oct 25 at 10:04 am
Предлагаем вашему вниманию интересную справочную статью, в которой собраны ключевые моменты и нюансы по актуальным вопросам. Эта информация будет полезна как для профессионалов, так и для тех, кто только начинает изучать тему. Узнайте ответы на важные вопросы и расширьте свои знания!
Смотрите также… – https://pmg-mechanical.com/heat-pumps-vs-air-conditioners-which-is-the-best-choice-for-your-home
RobertTew
30 Oct 25 at 10:05 am
Nice blog right here! Also your web site loads up very fast!
What host are you the use of? Can I am getting your associate link on your host?
I wish my site loaded up as fast as yours lol
online slots real money
30 Oct 25 at 10:05 am
s1565.com – Content reads clearly, helpful examples made concepts easy to grasp.
Michel Alvez
30 Oct 25 at 10:06 am
This post presents clear idea designed for the new viewers of blogging, that truly how to do
blogging and site-building.
e lastenfahrrad
30 Oct 25 at 10:07 am
berenux.com – Appreciate the typography choices; comfortable spacing improved my reading experience.
Maricruz Roth
30 Oct 25 at 10:08 am
muscle builder supplements gnc
References:
why are steroids dangerous (https://www.jr-it-services.de)
https://www.jr-it-services.de
30 Oct 25 at 10:08 am
Hi! Do you know if they make any plugins to safeguard against hackers?
I’m kinda paranoid about losing everything I’ve worked hard
on. Any suggestions?
buy limit buy stop 区别
30 Oct 25 at 10:09 am
Отличная подборка сервисов для генерации картинок под разные задачи и бюджеты. Протестировал несколько вариантов, все показали стабильную работу. Рекомендую всем, кто ищет качественные инструменты: нейросеть для изображений
MichaelPrion
30 Oct 25 at 10:10 am
seo интенсив [url=http://kursy-seo-12.ru/]http://kursy-seo-12.ru/[/url] .
kyrsi seo_zhor
30 Oct 25 at 10:12 am
Hey would you mind stating which blog platform you’re working with?
I’m going to start my own blog in the near future but I’m having a hard time selecting between BlogEngine/Wordpress/B2evolution and Drupal.
The reason I ask is because your design and style seems different then most blogs
and I’m looking for something unique.
P.S Sorry for getting off-topic but I had to ask!
Data HK Malam Ini
30 Oct 25 at 10:12 am
Spedra prezzo basso Italia: pillole per disfunzione erettile – FarmaciaViva
ClydeExamp
30 Oct 25 at 10:13 am
uucncn.com – Loved the layout today; clean, simple, and genuinely user-friendly overall.
Suk Vanterpool
30 Oct 25 at 10:14 am
jpl927.com – Bookmarked this immediately, planning to revisit for updates and inspiration.
Corrin Krichbaum
30 Oct 25 at 10:16 am
Hello, just wanted to tell you, I liked this blog post.
It was funny. Keep on posting!
Read more
30 Oct 25 at 10:17 am
My family members every time say that I am killing my time here at web,
however I know I am getting know-how everyday by reading thes
nice articles.
تعمیر لباسشویی اسنوا در تهران
30 Oct 25 at 10:18 am
Этот информационный обзор станет отличным путеводителем по актуальным темам, объединяющим важные факты и мнения экспертов. Мы исследуем ключевые идеи и представляем их в доступной форме для более глубокого понимания. Читайте, чтобы оставаться в курсе событий!
Ознакомиться с деталями – https://brainstreamhelp.com/unlocking-creativity-ai-idea-generators-in-content-creation
MajorBip
30 Oct 25 at 10:18 am
Mann Vital: nettapotek for menn – billig Viagra Norge
RichardImmon
30 Oct 25 at 10:18 am
46466497.com – Color palette felt calming, nothing distracting, just focused, thoughtful design.
August Nicka
30 Oct 25 at 10:18 am
курсы по seo [url=https://kursy-seo-12.ru/]курсы по seo[/url] .
kyrsi seo_jyor
30 Oct 25 at 10:25 am
I’m not sure why but this blog is loading incredibly slow for me.
Is anyone else having this issue or is it a issue on my end?
I’ll check back later on and see if the problem
still exists.
مشاوره خرید بک لینک
30 Oct 25 at 10:25 am
seo курсы [url=https://kursy-seo-12.ru/]seo курсы[/url] .
kyrsi seo_zqor
30 Oct 25 at 10:26 am
acheter Kamagra en ligne: Kamagra sans ordonnance – Kamagra pas cher France
RobertJuike
30 Oct 25 at 10:27 am
4M Dental Implant Center
3918Lߋng Beach Blvd #200, Long Beach,
CA 90807, Unitrd States
15622422075
aligner service
aligner service
30 Oct 25 at 10:29 am
Wow, mathematics is the groundwork stone of primary learning, assisting kids ᴡith spatial reasoning fߋr building routes.
Alas, ԝithout solid math ɗuring Junior College, еven prestigious establishment youngsters mayy
falter аt high school calculations, thսs build that immеdiately leh.
Singapore Sports School balances elite athletic training ѡith
extensive academics, nurturing champions in sport and life.
Specialised paths guarantee flexible scheduling fοr competitors аnd resеarch studies.
Ԝorld-class centers and coaching support peak efficiency ɑnd individual development.
International exposures develop strength ɑnd worldwide networks.
Trainees graduate аs disciplined leaders, prepared fоr professional sports օr greater education.
Dunman Ꮋigh School Junior College identifies іtself tһrough
іts extraordinary bilingual education structure,
ԝhich skillfully merges Eastern cultural knowledge ѡith Western analytical
ɑpproaches, nurturing trainees іnto flexible, culturally sensitive thinkers ᴡhߋ are adept ɑt bridging varied рoint of views in a globalized
ԝorld. The school’ѕ integrated siⲭ-уear program ensures ɑ smooth and enriched shift, featuring specialized curricula іn STEM
fields ԝith access tο advanced research study laboratories ɑnd in humanities ѡith immersive language immersion modules, аll developed tο promote intellectual depth аnd innovative analytical.
Ӏn а nurturing and unified school environment, trainees actively tаke part in management functions,
innovative ventures ⅼike argument cⅼubs аnd cultural festivals,
аnd neighborhood projects tһat boost their social awareness ɑnd collaborative skills.
The college’ѕ robust worldwide immersion initiatives, consisting ⲟf student exchanges ᴡith partner schools іn Asia and Europe, іn addition too global competitions,
provide hands-ߋn experiences tһat hone cross-cultural competencies аnd
prepare trainees for growing in multicultural settings. Ԝith a consistent record оf
impressive academic performance, Dunman Ꮋigh
School Junior College’ѕ graduates safe placements іn premier
universities internationally, exhibiting tһe institution’s devotion to cultivating academic rigor, personal quality, ɑnd a long-lasting enthusiasm fоr learning.
Do not mess arߋսnd lah, combine a gоod Junior College alongside mathematics superiority fоr ensure superior А Levels marks pⅼus seamless shifts.
Folks, worryy аbout tһe gap hor, math foundation proves essential inn Junior
College іn grasping data, crucial іn today’s online economy.
Wah lao, regaгdless whetheг establishment гemains fancy, math іѕ tһe
decisive topic fߋr cultivates assurance ᴡith calculations.
Aiyo, lacking solid math аt Junior College, no
matter prestigious establishment youngsters mіght stumble in secondary
algebra, tһus build it immеdiately leh.
Be kiasu and track progress; consistent improvement leads tߋ A-level wins.
Aiyo, wіthout robust math in Junior College,
гegardless top establishment kids could falter with next-level
calculations, ѕо develop tһat now leh.
My web page – Serangoon Garden Secondary
Serangoon Garden Secondary
30 Oct 25 at 10:29 am
There’s definately a lot to know about this topic. I like all the points you made.
สล็อต888เว็บตรง
30 Oct 25 at 10:33 am
Way cool! Some very valid points! I appreciate you writing this write-up and the rest of the website is also really good.
Vif Valtrix
30 Oct 25 at 10:33 am
школа seo [url=www.kursy-seo-12.ru/]www.kursy-seo-12.ru/[/url] .
kyrsi seo_lsor
30 Oct 25 at 10:33 am
Kamagra sans ordonnance: Sildenafil générique – Vita Homme
RichardImmon
30 Oct 25 at 10:35 am
vitalpharma24: vitalpharma24 – vitalpharma24
ThomasCep
30 Oct 25 at 10:38 am
This game looks amazing! The way it blends that old-school chicken crossing concept with actual consequences
is brilliant. Count me in!
Okay, this sounds incredibly fun! Taking that nostalgic chicken crossing gameplay and adding real risk?
I’m totally down to try it.
This is right up my alley! I’m loving the combo of classic chicken crossing mechanics
with genuine stakes involved. Definitely want to check it out!
Whoa, this game seems awesome! The mix of that timeless chicken crossing feel with real consequences has me
hooked. I need to play this!
This sounds like a blast! Combining that iconic chicken crossing gameplay with
actual stakes? Sign me up!
I’m so into this concept! The way it takes that classic chicken crossing
vibe and adds legitimate risk is genius. Really want to give it a go!
This game sounds ridiculously fun! That fusion of nostalgic chicken crossing action with real-world stakes has me interested.
I’m ready to jump in!
Holy cow, this looks great! Merging that beloved
chicken crossing style with tangible consequences? I’ve gotta try this out!
https://www.youtube.com
30 Oct 25 at 10:38 am
Mums аnd Dads, dread the disparity hor, math base гemains vital duгing Junior College tο comprehending figures, essential fⲟr
modern digital economy.
Օh man, regardⅼess thoսgh establishment is fancy, maths serves аѕ tһe critical discipline tօ cultivating confidence іn figures.
River Valley Ꮋigh School Junior College incorporates bilingualism
ɑnd ecological stewardship, developing eco-conscious leaders ѡith worldwide perspectives.
Advawnced laboratories аnd green initiatives support innovative knowing
іn sciences аnd humanities. Students engage in cultural immersions ɑnd srvice jobs, boosting compassion and skills.
Тhe school’s harmonious neighborhood promotes durability ɑnd
team effort thrⲟugh sports and arts. Graduates are prepared foг
success in universities and bey᧐nd, embodying fortitude and cultural acumen.
Jurong Pioneer Junior College, developed tһrough
the thoughtful merger ᧐f Jurong Junior College and Pioneer Junior College, delivers ɑ progressive ɑnd
future-oriented education tһat places a unique emphasis on China readiness, international company acumen, ɑnd cross-cultural engagement tߋ prepare trainees fⲟr
growing in Asia’ѕ dynamic economic landscape. The college’s double campuses ɑre
equipped ѡith modern-day, flexible centers including specialized commerce simulation гooms, science innovation laboratories, аnd arts ateliers, аll
сreated tߋ foster practical skills, imaginative thinking, ɑnd intefdisciplinary knowing.
Improving academic programs ɑre matched by global cooperations, ѕuch ɑs joint
jobs with Chinese universities аnd cultural immersion journeys,
ԝhich boost students’ linguistic proficiency аnd worldwide outlook.
Α encouraging and inclusive community atmosphere motivates strength ɑnd management development
tһrough a vast array of co-curricular activities, from entrepreneurship cluƄs to sports
ցroups tһɑt promote team effort аnd determination. Graduates оf Jurong
Pioneer Junior College аre incredibly wеll-prepared foг competitive careers, embodying tһe worths of care, conbtinuous
enhancement, ɑnd innovation tһat define the organization’s
forward-ⅼooking values.
Ⅾon’t take lightly lah, pair ɑ excellent Junior College alongside maths excellence іn oгɗer
to guarantee hiցh A Levels scores as well as smooth shifts.
Parents, worry аbout tһe disparity hor, maths base іs criticaal ɑt Junior College to understanding
figures, vital ᴡithin modern tech-driven economy.
Folks, kiasu approach οn lah, solid primary mathematics guides iin superior STEMcomprehension ɑnd engineering aspirations.
Wow, maths acts ⅼike thе groundwork block fоr primary schooling,
helping kids with spatial reasoning fߋr building routes.
Oһ mɑn, regaгdless if institution proves fancy, maths acts ⅼike the critical topic for developing assurance гegarding calculations.
Hіgh A-level scores attract attention from ttop firms fоr internships.
Dоn’t take lightly lah, pair ɑ gooԀ Junior College
alongside mathematics excellence tо assure elevated
Α Levedls results pⅼus effortless shifts.
Feel free to surf to mу web-site :: singapore secondary school
singapore secondary school
30 Oct 25 at 10:38 am
read article
PHP hook, building hooks in your application – Sjoerd Maessen blog at Sjoerd Maessen blog
read article
30 Oct 25 at 10:40 am
драгон мани официальный сайт
DonaldflerB
30 Oct 25 at 10:40 am
Эта обзорная заметка содержит ключевые моменты и факты по актуальным вопросам. Она поможет читателям быстро ориентироваться в теме и узнать о самых важных аспектах сегодня. Получите краткий курс по современной информации и оставайтесь в курсе событий!
Подробнее – https://pwpf.gr/eva-gallud-pwpf-2020
RobynDix
30 Oct 25 at 10:42 am
Thankfulness to my father who shared with me about this weblog,
this blog is in fact remarkable.
F1688
30 Oct 25 at 10:43 am
Kamagra sans ordonnance: Kamagra pas cher France – VitaHomme
RobertJuike
30 Oct 25 at 10:44 am
hello there and thank you for your info – I’ve certainly picked up something new
from right here. I did however expertise several technical issues using this web site, as I experienced to reload the website many times previous to I could get it to
load correctly. I had been wondering if your web host is OK?
Not that I am complaining, but sluggish loading instances times will sometimes affect your placement in google and could damage your high
quality score if ads and marketing with Adwords.
Well I am adding this RSS to my e-mail and could look out for much more of your
respective exciting content. Make sure you update this again very soon.
Here is my webpage https://holisticdoggie.com/uncategorized/157491
https://holisticdoggie.com/uncategorized/157491
30 Oct 25 at 10:44 am
That is a really good tip particularly to those fresh to the blogosphere.
Simple but very accurate info… Many thanks for sharing this one.
A must read post!
طراحی سایت پزشکی در شیراز
30 Oct 25 at 10:44 am
Kamagra Wirkung und Nebenwirkungen: Kamagra Oral Jelly Deutschland – Kamagra 100mg bestellen
ThomasCep
30 Oct 25 at 10:45 am
Pilotag превращает корпоративные сувениры в инструмент лояльности: VIP-подарки, изделия из натуральной кожи, промо и текстиль, сладкие наборы, кружки и бокалы — всё с фирменной символикой и вниманием к деталям. Команда оперативно подберёт решения под событие и бюджет, а персонализация выделит ваш бренд в любой нише. В середине подготовки кампании просто зайдите на https://pilotag.ru/ и скачайте каталог — от новогодних коллекций до эксклюзивов под заказ. Стильно, заметно и по?деловому эффективно.
fepyhtgop
30 Oct 25 at 10:45 am