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!
Стоимость услуг по установке капельницы определяется индивидуально и зависит от нескольких факторов. В первую очередь, цена обусловлена тяжестью состояния пациента: при более сильной интоксикации и выраженных симптомах абстинентного синдрома может потребоваться расширенная терапия. Кроме того, итоговая сумма зависит от продолжительности запоя, так как длительное употребление спиртного ведет к более серьезному накоплению токсинов, требующему дополнительных лечебных мероприятий.
Получить больше информации – [url=https://kapelnica-ot-zapoya-nizhniy-novgorod000.ru/]капельница от запоя вызов город[/url]
Terrytalia
17 Jul 25 at 12:05 am
I was able to find good advice from your blog articles.
kubet casino scam
17 Jul 25 at 12:08 am
apotek helgГ¶ppet: svinkoppor Г¤rr – kГ¶p frakt online
Kennethrip
17 Jul 25 at 12:09 am
online pharmacy nl [url=http://zorgpakket.com/#]apotheek nederland[/url] apotheek medicijnen
ScottFup
17 Jul 25 at 12:12 am
Когда организм уже не может обходиться без алкоголя, возникает запой. Вызов нарколога на дом в Волгограде – это безопасно и эффективно. Нарколог приедет на дом, проведет детоксикацию и поможет стабилизировать состояние, не нарушая привычный ритм жизни. Своевременная помощь нарколога поможет избежать осложнений, вызванных запоем. Дома нарколог поможет очистить организм и восстановить его нормальную работу. Избавление от запоя на дому – это сочетание лекарств и психологической помощи. Сначала нужно очистить организм от токсинов, и в этом помогут специальные препараты.
Ознакомиться с деталями – [url=https://vyvod-iz-zapoya-volgograd0.ru/]вывод. из. запоя. волгоград.[/url]
Thomasheada
17 Jul 25 at 12:17 am
Howdy just wanted to give you a quick heads up. The text
in your content seem to be running off the screen in Internet explorer.
I’m not sure if this is a format issue or something to do with web browser compatibility but I figured I’d post
to let you know. The design look great though! Hope you get the problem solved soon. Cheers
Смотреть порно с мишустиным и детьми
17 Jul 25 at 12:23 am
http://tryggmed.com/# salttabletter apotek
MichaelDeeli
17 Jul 25 at 12:23 am
Терапия начинается с подробного обследования: врачи выясняют не только текущее физическое состояние, но и личные мотивы пациента, его страхи и ожидания от лечения. Диагностика включает анализы, ЭКГ, ультразвуковое исследование, а также психодиагностику. Уже на этом этапе формируется контакт между специалистом и пациентом, выстраивается доверие, что крайне важно для успеха последующей терапии.
Подробнее – [url=https://lechenie-alkogolizma-vidnoe4.ru/]наркологическое лечение алкоголизма[/url]
MichaelCiz
17 Jul 25 at 12:32 am
Алкогольная и наркотическая зависимости требуют незамедлительного медицинского вмешательства. Длительные запои или острое отравление могут привести к тяжелым осложнениям и серьезным проблемам со здоровьем. Клиника «МедТрезвость» в Санкт-Петербурге предлагает квалифицированную наркологическую помощь прямо на дому, обеспечивая полную конфиденциальность и безопасность пациента. Наши специалисты оперативно выезжают по адресу, проводят все необходимые процедуры и помогают максимально быстро стабилизировать состояние пациента в комфортных домашних условиях.
Получить больше информации – https://narcolog-na-dom-sankt-peterburg0.ru/narkolog-na-dom-czena-spb/
WilliamFed
17 Jul 25 at 12:39 am
Алкогольный запой представляет собой длительное непрерывное употребление спиртного, приводящее к накоплению токсинов в организме и серьёзным нарушениям работы внутренних систем. В такие моменты помощь профессионального нарколога необходима как можно скорее — ведь от скорости детоксикации зависит минимизация рисков осложнений и сохранение здоровья пациента. Клиника «ВоронежМед» предлагает анонимный круглосуточный выезд специалиста на дом в пределах города за 60 минут, обеспечивая качественную инфузионную терапию в привычной обстановке.
Получить больше информации – http://kapelnica-ot-zapoya-voronezh.ru/
JamisonZot
17 Jul 25 at 12:47 am
Услуга
Получить дополнительную информацию – https://narkolog-na-dom-ramenskoe4.ru/narkolog-na-dom-srochno-v-ramenskom/
Charlesonemn
17 Jul 25 at 12:47 am
При запое важна каждая минута! Круглосуточный вызов нарколога в Волгоградской области – решение проблемы! Обращайтесь к нам в любое время суток, и мы вам поможем! Мы обеспечиваем оперативное вмешательство, индивидуальный подход и высокий уровень безопасности. Мы – это оперативная и надежная помощь в трудную минуту. Мы обеспечим детоксикацию и стабилизацию вашего состояния, где вам будет удобно. Первый шаг – диагностика, чтобы определить план лечения.
Изучить вопрос глубже – [url=https://vyvod-iz-zapoya-volgograd000.ru/]вывод из запоя цены на дому волгоград[/url]
Danielmef
17 Jul 25 at 12:50 am
1win peliculas [url=https://1win3048.com/]1win peliculas[/url]
1win_ieMi
17 Jul 25 at 12:57 am
тмг трансформатор [url=maslyanie-transformatory-kupit2.ru]тмг трансформатор[/url] .
maslyanie transformatori kypit_dhOn
17 Jul 25 at 12:59 am
В течение процедуры пациент находится под непрерывным наблюдением: внутривенные капельницы с регидратационными растворами, витаминно-минеральными комплексами и гепатопротекторами обеспечивают восстановление функций печени, нормализацию обменных процессов и поддержку работы сердечно-сосудистой системы. При необходимости к лечению привлекаются узкопрофильные врачи — кардиолог, невролог или терапевт — что делает процесс максимально комплексным. По завершении выведения из запоя врач предоставляет подробный план поддерживающей терапии с рекомендациями по питанию, питьевому режиму и графику повторных консультаций.
Узнать больше – [url=https://vyvod-iz-zapoya-reutov4.ru/]вывод из запоя[/url]
Josephses
17 Jul 25 at 1:00 am
1win es confiable [url=https://1win3044.com]https://1win3044.com[/url]
1win_tmea
17 Jul 25 at 1:09 am
После процедуры пациент и его близкие получают развернутые рекомендации по дальнейшему восстановлению, советы по профилактике рецидивов и возможности прохождения кодирования при желании пациента.
Выяснить больше – https://narcolog-na-dom-sankt-peterburg000.ru/narkolog-na-dom-czena-spb/
Allensow
17 Jul 25 at 1:14 am
nad tilskudd apotek [url=https://tryggmed.shop/#]sitronsГҐpe apotek[/url] allergitest apotek
ScottFup
17 Jul 25 at 1:16 am
купить диплом легально [url=www.arus-diplom32.ru]купить диплом легально[/url] .
Diplomi_knpi
17 Jul 25 at 1:16 am
Покупка документа о высшем образовании через качественную и надежную компанию дарит множество плюсов для покупателя. Купить диплом о высшем образовании: [url=http://newmedtime.ru/individualnyiy-podhod-k-kazhdomu-klientu]newmedtime.ru/individualnyiy-podhod-k-kazhdomu-klientu[/url]
Sazrqtx
17 Jul 25 at 1:19 am
«ВоронежДоктор» сочетает в себе профессионализм, современное оборудование и заботу о приватности пациента. Ключевые преимущества:
Детальнее – [url=https://kapelnica-ot-zapoya-voronezh2.ru/]капельница от запоя анонимно[/url]
Manueltroky
17 Jul 25 at 1:21 am
Запой — это серьёзное состояние, при котором организм страдает от накопления токсинов, обезвоживания и нарушения обмена веществ. Клиника «ВоронежЛайф» предлагает услугу выездного инфузионного лечения от запоя с возможностью круглосуточного реагирования. Благодаря персонализированному подходу и использованию современных автоматизированных систем дозирования, наши специалисты обеспечивают безопасную и эффективную детоксикацию в условиях домашнего уюта.
Подробнее – https://kapelnica-ot-zapoya-voronezh3.ru/vyezd-na-dom-kapelnicza-ot-zapoya-v-voronezhe
Jerryjep
17 Jul 25 at 1:25 am
мелбет казино играть [url=https://melbet3001.com/]https://melbet3001.com/[/url]
melbet_ybkn
17 Jul 25 at 1:33 am
Продолжительный запой приводит к накоплению токсинов, нарушению обменных процессов и ухудшению работы внутренних органов. Чем быстрее начинается лечение, тем ниже риск развития осложнений, таких как повреждение печени, почек, сердечно-сосудистые нарушения и нервные расстройства. Экстренная помощь нарколога на дому в Рязани позволяет приступить к терапии уже в первые часы кризиса, что является решающим для предотвращения необратимых изменений в организме.
Получить дополнительную информацию – https://narcolog-na-dom-ryazan0.ru/vrach-narkolog-na-dom-ryazan/
Jamieflelf
17 Jul 25 at 1:35 am
where to buy prednisone tablets
Pregnant women should use Prednisone only if clearly needed.
Although it may be prescribed during pregnancy, the benefits must outweigh the potential risks to the fetus.
Discuss all options with your healthcare provider.
where to buy prednisone tablets
17 Jul 25 at 1:36 am
Действие и назначение
Исследовать вопрос подробнее – https://vyvod-iz-zapoya-novosibirsk00.ru/
JimmyAided
17 Jul 25 at 1:45 am
nattГҐpent apotek: neseplugg apotek – spoteket
Altonjah
17 Jul 25 at 1:47 am
1win apk android [url=http://1win3047.com]http://1win3047.com[/url]
1win_efMa
17 Jul 25 at 1:49 am
Запой – это очень опасно, нужно срочно вызывать нарколога на дом в Волгограде! Помощь нарколога на дому – это оперативно, удобно и анонимно. Мы быстро очистим ваш организм от алкоголя и восстановим его работу. Наши специалисты готовы приехать к вам в любое время суток. Лечение комплексное: лекарства, индивидуальный подход и психологическая помощь. Первый шаг – детоксикация, мы поможем вам избавиться от алкогольной интоксикации.
Исследовать вопрос подробнее – [url=https://vyvod-iz-zapoya-volgograd00.ru/]вывод из запоя в волгограде[/url]
WilliamWheew
17 Jul 25 at 1:49 am
Введение препаратов осуществляется внутривенно, что обеспечивает оперативное действие медикаментов. В состав лечебного раствора входят средства для детоксикации организма, нормализации водно-электролитного и кислотно-щелочного баланса. При необходимости врач дополнительно вводит препараты, защищающие печень, стабилизирующие работу сердца и успокаивающие нервную систему. Вся процедура проводится под строгим контролем нарколога, который следит за состоянием пациента и корректирует терапию при необходимости. По завершении процедуры врач дает пациенту и его родственникам подробные рекомендации по дальнейшему восстановлению и профилактике повторных запоев.
Получить дополнительные сведения – https://kapelnica-ot-zapoya-nizhniy-novgorod0.ru/
MelvinExisy
17 Jul 25 at 1:54 am
http://tryggmed.com/# engangshansker apotek
WilliamNog
17 Jul 25 at 1:56 am
Medication information for patients. Cautions.
order anastrozole without insurance
Actual information about medication. Get information now.
order anastrozole without insurance
17 Jul 25 at 1:59 am
acquista nortriptyline a basso costo
acquista nortriptyline a basso costo senza ricetta
17 Jul 25 at 2:00 am
ставки на спорт melbet [url=http://melbet3002.com/]ставки на спорт melbet[/url]
melbet_qeOl
17 Jul 25 at 2:10 am
kraken client
DavidFoogs
17 Jul 25 at 2:17 am
gravid test apotek: kГ¶pa sГ¶mntabletter pГҐ nГ¤tet – apotek kreatin
Altonjah
17 Jul 25 at 2:19 am
This is my first time go to see at here and i
am actually happy to read everthing at single place.
vavada casino
17 Jul 25 at 2:27 am
https://tryggmed.shop/# ph mГҐler apotek
WilliamNog
17 Jul 25 at 2:30 am
melbet app iphone [url=https://www.melbet3002.com]https://www.melbet3002.com[/url]
melbet_cyOl
17 Jul 25 at 2:32 am
1win site officiel [url=www.1win3047.com]1win site officiel[/url]
1win_bzMa
17 Jul 25 at 2:36 am
Если ищете, где можно смотреть UFC в прямом эфире, то этот сайт отлично подойдёт. Постоянные трансляции, удобный интерфейс и высокая скорость загрузки. Всё работает стабильно и без рекламы: mma fan
GarrySwicy
17 Jul 25 at 2:37 am
Thanks for every other informative web site. Where else may
I am getting that type of info written in such a perfect way?
I’ve a venture that I am simply now operating on, and I’ve
been at the glance out for such info.
https://carbatterydelivery.my/car-battery-breakdown-service
17 Jul 25 at 2:47 am
que es 1win [url=https://www.1win3045.com]https://www.1win3045.com[/url]
1win_yqpi
17 Jul 25 at 2:48 am
Откопаться ? данное действие не просто физический процесс, но и захватывающее исследование земли, которое открывает врата к тайнам истории. Археология, как наука, предполагает раскопки, позволяющие обнаружить исторические артефакты, предметы старины и подземные ресурсы. На сайте narkolog-tula001.ru можно найти разнообразные материалы о современных методах бурения и анализа почвы, которые помогают в поиске уникальных объектов. Земляные работы, включая раскопки, являются основой археологических исследований. Каждый слой почвы, вырываем, может рассказать о жизни людей в разные исторические эпохи. Восстановление найденных артефактов даёт возможность лучше понять культуру и технологии прошлых эпох. Геология также играет ключевую роль в процессе, помогая в анализе почвы и определении местонахождения объектов, что облегчает поиски более целенаправленными и эффективными. Таким образом, откопаться – это не только работа с землёй, но и увлекательное путешествие в прошлое, где каждая находка становится важным элементом исторической мозаики.
lechenietulaNeT
17 Jul 25 at 2:49 am
Запой – это критическое состояние, возникающее при длительном или чрезмерном употреблении алкоголя, когда организм не может справиться с токсическим воздействием этанола. Такое состояние характеризуется накоплением токсинов, разрушением внутренних органов и сильным нарушением психоэмоционального баланса. Когда запой продолжается, у человека развивается абстинентный синдром, сопровождающийся не только физическим истощением, но и резкими изменениями в психике: от выраженной тревожности и агрессии до галлюцинаций и потери ориентации.
Подробнее – [url=https://narcolog-na-dom-krasnoyarsk0.ru/]нарколог на дом недорого в красноярске[/url]
BrianSip
17 Jul 25 at 2:49 am
1xBet — это одна из самых популярных и надежных платформ, которая предлагает уникальные возможности и привлекательные бонусы для своих пользователей. Независимо от того, являетесь ли вы новичком в мире ставок или опытным игроком, 1xBet промокод 2025, воспользуйтесь бонусным промокодом, чтобы получить бесплатный приветственный бонус в размере 100%. При регистрации введите данный код и получите бонус до 32 500 рублей (или эквивалент в другой валюте до 130$).
MauriceedUpe
17 Jul 25 at 2:50 am
I’m gone to inform my little brother, that he should also visit this webpage on regular basis to get updated from latest news update.
macaron monster
17 Jul 25 at 2:52 am
Клиника «НаркоЩит» предоставляет возможность безопасного вывода из запоя на дому в Нижнем Новгороде и Нижегородской области с помощью установки капельницы. Наши опытные специалисты оперативно приезжают для проведения детоксикации, снятия симптомов алкогольной интоксикации и стабилизации состояния пациента. Мы гарантируем круглосуточный выезд, соблюдение конфиденциальности и высокий уровень профессионального обслуживания.
Детальнее – [url=https://kapelnica-ot-zapoya-nizhniy-novgorod00.ru/]капельница от запоя нижний новгород[/url]
Robertspook
17 Jul 25 at 2:52 am
https://tryggmed.shop/# betakaroten apotek
MichaelDeeli
17 Jul 25 at 2:54 am
Цена капельницы от запоя в Иркутске зависит от ряда факторов, таких как выбор метода лечения (на дому или в стационаре), продолжительность лечения и степень тяжести состояния пациента. Для уточнения стоимости и записи на консультацию вы можете обратиться к нашим менеджерам, которые подробно расскажут о стоимости услуг и ответят на все ваши вопросы.
Подробнее можно узнать тут – [url=https://kapelnica-ot-zapoya-irkutsk3.ru/]после капельницы от запоя иркутск[/url]
Walteredger
17 Jul 25 at 2:55 am