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=http://www.avtomaticheskie-rulonnye-shtory5.ru]автоматические рулонные шторы с электроприводом[/url] .
avtomaticheskie rylonnie shtori_rysr
16 Sep 25 at 12:35 am
электрокарнизы цена [url=www.karniz-s-elektroprivodom-kupit.ru]www.karniz-s-elektroprivodom-kupit.ru[/url] .
karniz s elektroprivodom kypit_jlEr
16 Sep 25 at 12:37 am
рулонные шторы с автоматическим управлением [url=https://elektricheskie-rulonnye-shtory15.ru]https://elektricheskie-rulonnye-shtory15.ru[/url] .
elektricheskie rylonnie shtori_kbEi
16 Sep 25 at 12:37 am
how to buy tetracycline prices
can i get tetracycline pills
16 Sep 25 at 12:39 am
Не всегда получается самостоятельно поддерживать чистоту в помещении. Для экономии времени и сил лучше всего воспользоваться помощью профессионалов. Но для того, чтобы выяснить, в какое клининговое агентство правильней обращаться, нужно изучить рейтинг лучших компаний на текущий год. https://sravnishka.ru/ – на сайте те предприятия, которые оказывают услуги на высоком уровне и по доступной стоимости. Ознакомьтесь с режимом работы, телефоном, а также перечнем оказываемых услуг.
PejureProon
16 Sep 25 at 12:41 am
прокарниз [url=https://karniz-s-elektroprivodom-kupit.ru/]karniz-s-elektroprivodom-kupit.ru[/url] .
karniz s elektroprivodom kypit_ysEr
16 Sep 25 at 12:42 am
сочетание рулонных рулонные шторы и тюль фото [url=http://www.elektricheskie-rulonnye-shtory15.ru]http://www.elektricheskie-rulonnye-shtory15.ru[/url] .
elektricheskie rylonnie shtori_diEi
16 Sep 25 at 12:42 am
рулонная штора автоматическая [url=http://www.avtomaticheskie-rulonnye-shtory5.ru]рулонная штора автоматическая[/url] .
avtomaticheskie rylonnie shtori_dhsr
16 Sep 25 at 12:42 am
Buffalo Stack Sync 1xbet AZ
Willietat
16 Sep 25 at 12:46 am
электрические жалюзи на окна [url=https://www.avtomaticheskie-rulonnye-shtory5.ru]https://www.avtomaticheskie-rulonnye-shtory5.ru[/url] .
avtomaticheskie rylonnie shtori_zzsr
16 Sep 25 at 12:46 am
Hey there! I just wanted to ask if you ever have any problems with hackers?
My last blog (wordpress) was hacked and I ended up losing
a few months of hard work due to no data backup. Do you have any methods to protect against hackers?
contemporary aesthetic
16 Sep 25 at 12:46 am
https://fernandorlay904.yousher.com/hidratacin-inteligente-cunta-agua-tomar-antes-de-un-test-de-orina
Aprobar una prueba preocupacional puede ser complicado. Por eso, se ha creado una alternativa confiable creada con altos estandares.
Su receta unica combina carbohidratos, lo que ajusta tu organismo y enmascara temporalmente los marcadores de toxinas. El resultado: un analisis equilibrado, lista para pasar cualquier control.
Lo mas notable es su ventana de efectividad de 4 a 5 horas. A diferencia de metodos caseros, no promete milagros, sino una solucion temporal que responde en el momento justo.
Miles de estudiantes ya han comprobado su rapidez. Testimonios reales mencionan paquetes 100% confidenciales.
Si no deseas dejar nada al azar, esta alternativa te ofrece respaldo.
JuniorShido
16 Sep 25 at 12:47 am
стоимость согласования перепланировки квартиры [url=www.angelladydety.getbb.ru/viewtopic.php?f=42&t=59347&p=109062/]стоимость согласования перепланировки квартиры[/url] .
soglasovanie pereplanirovki kvartiri moskva _bgka
16 Sep 25 at 12:48 am
электрические жалюзи на окна [url=www.avtomaticheskie-rulonnye-shtory5.ru/]www.avtomaticheskie-rulonnye-shtory5.ru/[/url] .
avtomaticheskie rylonnie shtori_dvsr
16 Sep 25 at 12:51 am
http://evakuatorhelp.ru/ Не стесняйтесь обращаться к нам – мы всегда рады помочь!
Jamiedaymn
16 Sep 25 at 12:52 am
Казино 1xslots слот Cai Shen 168
Derekjency
16 Sep 25 at 12:52 am
согласование перепланировки квартиры под ключ [url=http://fanfiction.borda.ru/?1-0-0-00030334-000-0-0]согласование перепланировки квартиры под ключ [/url] .
soglasovanie pereplanirovki kvartiri moskva _vxka
16 Sep 25 at 12:55 am
Book of Vampires играть в Пинко
Edgarclome
16 Sep 25 at 12:58 am
стоимость согласования перепланировки квартиры [url=http://www.www.bisound.com/forum/showthread.php?p=2851657]стоимость согласования перепланировки квартиры[/url] .
soglasovanie pereplanirovki kvartiri moskva _prka
16 Sep 25 at 1:01 am
Ich bin total begeistert von Wunderino Casino, es verstromt eine Spielstimmung, die wie ein Marchenbuch verzaubert. Die Spielauswahl im Casino ist wie ein verwunschener Garten, inklusive stilvoller Casino-Tischspiele. Das Casino-Team bietet Unterstutzung, die wie ein Zauberspruch wirkt, liefert klare und schnelle Losungen. Casino-Zahlungen sind sicher und reibungslos, dennoch mehr regelma?ige Casino-Boni waren verhext. Insgesamt ist Wunderino Casino ein Casino, das man nicht verpassen darf fur Zauberer im Casino! Und au?erdem die Casino-Navigation ist kinderleicht wie ein Zauberpfad, einen Hauch von Marchen-Magie ins Casino bringt.
wunderino bonus ohne einzahlung 2024|
zanyglittermongoose3zef
16 Sep 25 at 1:03 am
проект перепланировки нежилого помещения стоимость [url=http://pereplanirovka-nezhilogo-pomeshcheniya2.ru/]http://pereplanirovka-nezhilogo-pomeshcheniya2.ru/[/url] .
pereplanirovka nejilogo pomesheniya_olEt
16 Sep 25 at 1:06 am
Kaizenaire.com proѵides Singaporeans thе finest promotions,
maҝing іt tһe beѕt site foг deals.
The streets of Singapore, a real shopping paradise, echo ԝith the excitement ⲟf residents
scoring deals ᴡith brilliant promotions.
Participating іn cultural events like Chinese New Yеаr events unites Singaporeans, ɑnd kеep іn mind
tⲟ stay upgraded ߋn Singapore’s most current promotions and shopping deals.
Revenue Insurance ɡives cost effective insurance coverage fοr automobiles and homes,
preferred Ьy Singaporeans for tһeir reliable casеs procedure and community-focused efforts.
Singtel, а leading telecommunications service provider ѕia,
products mobile strategies, broadband, ɑnd homе entertainment services
thɑt Singaporeans ᴠalue foг their reputable connection and packed
deals lah.
Wilmar International oils ѡith food preparation essentials, treasured fօr Golden Circle and ⲟther house staples.
Ꮃhy wait lor, jump onto Kaizenaire.com sia.
my blog … singapore shopping
singapore shopping
16 Sep 25 at 1:07 am
online apotheke rezept: blaue pille erfahrungen männer – apotheke online
Donaldanype
16 Sep 25 at 1:07 am
how can i get cheap albenza prices
where buy cheap albenza
16 Sep 25 at 1:10 am
Howdy I am so excited I found your web site, I really
found you by error, while I was researching on Yahoo for something else,
Nonetheless I am here now and would just like to say thanks for
a tremendous post and a all round enjoyable blog (I also love
the theme/design), I don’t have time to browse it all
at the minute but I have saved it and also added in your RSS feeds, so when I have time I will be back to read a
lot more, Please do keep up the great job.
swap BTC to gold
16 Sep 25 at 1:12 am
樂富娛樂城
樂富
16 Sep 25 at 1:13 am
Привет фортовым игрокам КАЗИНО онлайн!
1win casino зеркало открывает мир азартных развлечений. Играй стабильно и честно. Каждый день новые возможности для побед. Получай бонусы и призы. 1win casino зеркало гарантирует честность.
Заходите скорее на рабочее 1win casino зеркало – [url=https://t.me/s/onewincasinotoday]1win casino зеркало[/url]
Удачи и персональных выйгрышей в 1win casino!
Casinojence
16 Sep 25 at 1:13 am
What’s up friends, good paragraph and pleasant urging commented
at this place, I am in fact enjoying by these.
Sûreté Tradecore
16 Sep 25 at 1:16 am
It’s appropriate time to make a few plans for the future and it is time to be happy.
I’ve read this publish and if I could I want to recommend
you some fascinating issues or tips. Maybe you can write next articles referring to
this article. I want to learn more issues approximately it!
AI video generator
16 Sep 25 at 1:16 am
get cheap bactrim price
cost of cheap bactrim without insurance
16 Sep 25 at 1:17 am
This is really fascinating, You’re an overly skilled blogger.
I have joined your rss feed and sit up for in the hunt for extra of
your magnificent post. Additionally, I have shared your site in my social networks
xnxx
16 Sep 25 at 1:17 am
стоимость согласования перепланировки квартиры [url=http://www.www.bisound.com/forum/showthread.php?p=2851657]стоимость согласования перепланировки квартиры[/url] .
soglasovanie pereplanirovki kvartiri moskva _dzka
16 Sep 25 at 1:21 am
согласование перепланировки квартиры москва [url=http://dimitrov.forum24.ru/?1-7-0-00000193-000-0-0/]согласование перепланировки квартиры москва [/url] .
soglasovanie pereplanirovki kvartiri moskva _wgka
16 Sep 25 at 1:24 am
согласование перепланировки квартиры под ключ [url=https://www.turforum.borda.ru/?1-8-0-00003551-000-0-0]согласование перепланировки квартиры под ключ [/url] .
soglasovanie pereplanirovki kvartiri moskva _gvka
16 Sep 25 at 1:26 am
согласование перепланировки в нежилом здании [url=www.pereplanirovka-nezhilogo-pomeshcheniya3.ru/]www.pereplanirovka-nezhilogo-pomeshcheniya3.ru/[/url] .
pereplanirovka nejilogo pomesheniya_cwsa
16 Sep 25 at 1:28 am
tadalafil erfahrungen deutschland: tadalafil erfahrungen deutschland – medikament ohne rezept notfall
Israelpaync
16 Sep 25 at 1:28 am
порядок согласования перепланировки нежилого помещения [url=http://www.pereplanirovka-nezhilogo-pomeshcheniya1.ru]http://www.pereplanirovka-nezhilogo-pomeshcheniya1.ru[/url] .
pereplanirovka nejilogo pomesheniya_vksi
16 Sep 25 at 1:28 am
Howdy are using WordPress for your site platform? I’m new to
the blog world but I’m trying to get started and set up my own. Do you require any
html coding knowledge to make your own blog? Any help would be really appreciated!
LimoFamoPro
16 Sep 25 at 1:28 am
I simply could not depart your site before suggesting that I
actually enjoyed the usual info a person provide
for your visitors? Is going to be back ceaselessly in order to investigate cross-check new posts
paito hk
16 Sep 25 at 1:30 am
согласование перепланировки нежилого помещения в жилом доме [url=http://www.pereplanirovka-nezhilogo-pomeshcheniya.ru]http://www.pereplanirovka-nezhilogo-pomeshcheniya.ru[/url] .
pereplanirovka nejilogo pomesheniya_uyKn
16 Sep 25 at 1:30 am
согласование перепланировки квартиры под ключ [url=https://fanfiction.borda.ru/?1-0-0-00030334-000-0-0]согласование перепланировки квартиры под ключ [/url] .
soglasovanie pereplanirovki kvartiri moskva _ijka
16 Sep 25 at 1:35 am
экстренный вывод из запоя смоленск
vivod-iz-zapoya-smolensk016.ru
вывод из запоя цена
narkologiyasmolenskNeT
16 Sep 25 at 1:41 am
согласование перепланировки квартиры под ключ [url=http://angelladydety.getbb.ru/viewtopic.php?f=42&t=59347&p=109062]согласование перепланировки квартиры под ключ [/url] .
soglasovanie pereplanirovki kvartiri moskva _szka
16 Sep 25 at 1:42 am
стоимость согласования перепланировки квартиры [url=http://angelladydety.getbb.ru/viewtopic.php?f=42&t=59347&p=109062]стоимость согласования перепланировки квартиры[/url] .
soglasovanie pereplanirovki kvartiri moskva _dmka
16 Sep 25 at 1:45 am
Inspiring story there. What happened after? Good luck!
Norlios Platform
16 Sep 25 at 1:47 am
согласование перепланировки квартиры под ключ [url=www.bisound.com/forum/showthread.php?p=2851657]согласование перепланировки квартиры под ключ [/url] .
soglasovanie pereplanirovki kvartiri moskva _bxka
16 Sep 25 at 1:47 am
https://myrentacar.site/
https://myrentacar.site/
16 Sep 25 at 1:51 am
Aiyo, minus solid math аt Junior College, гegardless toρ institution children mіght struggle іn next-level calculations,
tһerefore develop thаt immеdiately leh.
Anglo-Chinese Junior College stands as а beacon ⲟf welⅼ balanced education, mixing
strenuous academics ԝith a supporting Christian ethos tһat influences ethical stability аnd individual growth.
Тhе college’ѕ ѕtate-of-thе-art facilities and experienced faculty support outstanding
performance іn both arts and sciences, witһ trainees ߋften accomplishing leading honors.
Ꭲhrough its focus on sports and carrying ⲟut arts, trainees establish discipline,
camaraderie, ɑnd an enthusiasm fοr quality Ьeyond the class.
International partnerships аnd exchange opportunities improve tһe learning experience,
promoting international awareness аnd cultural appreciation. Alumni
thrive іn varied fields, testament tо the college’ѕ role іn forming principled leaders ready
tо contribute favorably tⲟ society.
River Valley Нigh School Junior College perfectly іncludes bilingual education witһ a strong dedication to ecological stewardship,
supporting eco-conscious leaders ԝhⲟ possss sharp global viewpoints ɑnd a commitment tⲟ sustwinable practices in an increasingly interconnected
ԝorld. The school’s advanced laboratories, green technology centers, аnd
eco-friendly campus designs support pioneering knowing іn sciences,
humanities, аnd environmental reѕearch studies, motivating
trainees tօ participate in hands-on experiments аnd
ingenious solutions t᧐ real-wߋrld obstacles. Cultural immersion programs,
ѕuch ɑs language exchanges аnd heritage journeys, integrated ѡith community service
jobs concentrated օn conservation, enhance students’ empathy,
cultural intelligence, ɑnd uѕeful skills for favorable societal еffect.
Within a unified and supportive neighborhood, involvement іn sports teams, arts societies, аnd leadership workshops promotes physical wellness, team effort,
аnd resilience, producing ѡell-balanced individuals ready fⲟr future undertakings.
Graduates fгom River Valley Higһ School Junior College аre preferably ρlaced
for success in leading universities ɑnd professions, embodying tһe
school’s core values of fortitude, cultural acumen, аnd a proactive approach tⲟ international sustainability.
Alas, primary maths instructs everyday ᥙses suϲh aѕ financial planning,
tһus guarantee your kid grasps іt right from yⲟung.
Listen up, steady pom ρi pі, mathematics rеmains ⲣart іn thе
leading subjects ⅾuring Junior College, building base fоr A-Level hіgher calculations.
Goodness, no matter if school remains fancy, mathematics serves ɑs the decisive subject to developing poise іn figures.
Wah lao, regardless tһough institution proves fancy, math іѕ the critical discipline
in developing confidence regarding figures.
Alas, primary mathematics educates everyday implementations including budgeting, ѕo maкe ѕure your
child getѕ this properly ƅeginning yoᥙng.
Math at A-levels fosters а growth mindset, crucial fоr lifelong learning.
Oh man, no matter іf school proves һigh-end,
maths acts lіke the critical subject іn building confidence with calculations.
Aiyah, primary mathematics instructs everyday implementations
lik financial planning, tһus ensure your child grasps tһat properly beցinning earlү.
Anglo-Chinese Junior College
16 Sep 25 at 1:54 am
согласование перепланировки квартиры под ключ [url=http://fanfiction.borda.ru/?1-0-0-00030334-000-0-0/]согласование перепланировки квартиры под ключ [/url] .
soglasovanie pereplanirovki kvartiri moskva _fska
16 Sep 25 at 1:54 am
Hi there, You’ve done a fantastic job. I will certainly digg it and personally suggest to my friends.
I am confident they will be benefited from this website.
chicken breast protein smoothie
16 Sep 25 at 1:59 am