From be0eebc5186a3bfc00bdcbb311269dad718001ef Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 11:06:31 +0100 Subject: [PATCH 01/68] change namespace --- docs/assets/favicon.ico | Bin 15086 -> 0 bytes docs/assets/flame.svg | 11 ----------- docs/assets/github-dark-dimmed.css | 15 --------------- docs/assets/hljs.js | 3 --- docs/{basic_usage.md => basic-usage.md} | 4 ++-- docs/installation.md | 4 ++-- docs/{running_queues.md => running-queues.md} | 0 src/BaseJob.php | 2 +- src/Commands/Generators/JobGenerator.php | 2 +- src/Commands/Generators/Views/job.tpl.php | 4 ++-- src/Commands/QueueClear.php | 2 +- src/Commands/QueueFailed.php | 4 ++-- src/Commands/QueueFlush.php | 2 +- src/Commands/QueueForget.php | 2 +- src/Commands/QueuePublish.php | 2 +- src/Commands/QueueRetry.php | 2 +- src/Commands/QueueStop.php | 2 +- src/Commands/QueueWork.php | 6 +++--- src/Config/Queue.php | 10 +++++----- src/Config/Registrar.php | 4 ++-- src/Config/Services.php | 8 ++++---- .../2023-10-12-112040_AddQueueTables.php | 2 +- .../2023-11-05-064053_AddPriorityField.php | 2 +- src/Entities/QueueJob.php | 2 +- src/Entities/QueueJobFailed.php | 2 +- src/Enums/Status.php | 2 +- src/Exceptions/QueueException.php | 2 +- src/Handlers/BaseHandler.php | 12 ++++++------ src/Handlers/DatabaseHandler.php | 14 +++++++------- src/Handlers/PredisHandler.php | 12 ++++++------ src/Handlers/RedisHandler.php | 12 ++++++------ src/Interfaces/JobInterface.php | 2 +- src/Interfaces/QueueInterface.php | 4 ++-- src/Models/QueueJobFailedModel.php | 4 ++-- src/Models/QueueJobModel.php | 6 +++--- src/Payload.php | 2 +- src/Queue.php | 8 ++++---- tests/DatabaseHandlerTest.php | 10 +++++----- tests/PredisHandlerTest.php | 6 +++--- tests/QueueTest.php | 6 +++--- tests/RedisHandlerTest.php | 6 +++--- tests/_support/Config/Queue.php | 8 ++++---- .../Database/Seeds/TestDatabaseQueueSeeder.php | 10 +++++----- .../Database/Seeds/TestRedisQueueSeeder.php | 8 ++++---- tests/_support/Jobs/Failure.php | 4 ++-- tests/_support/Jobs/Success.php | 4 ++-- 46 files changed, 105 insertions(+), 134 deletions(-) delete mode 100644 docs/assets/favicon.ico delete mode 100644 docs/assets/flame.svg delete mode 100644 docs/assets/github-dark-dimmed.css delete mode 100644 docs/assets/hljs.js rename docs/{basic_usage.md => basic-usage.md} (97%) rename docs/{running_queues.md => running-queues.md} (100%) diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico deleted file mode 100644 index 531feed8bc363eca341dd9a839cb9c8c77886579..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15086 zcmdU$3$WBx9mmi0k_$py$`H`wZ52Y98lub-iI&~dp0?Qs+|1Rxcqiz2DP@n7EvYF1!Lx(ObYV8Q; z(Azq_hObBeY&mT=n=9`lWY1{0+{E+SEP^?tv#@3CRD^pz(NDl5jdyU>h6`7W<`?$Dj%u;jJ(V ztH5;nD1Qc|vlm_k*0+H29#G%oAlovSz2@Tr*bUOz2g||i^OR+yKI5B|bD*)U1@o`| zUGPFM`)5$z1ZFF_0v5q9;Xe@f-AMgVFnh^UKyzz)H&MPT)c+XD zt5`Ry9DMX6F9z9a3|bFaCI_@j7nJK4;V~${0eX!Zq#x|`T%XyY{cP-xm2H;&j`f@e zTbKV_3`WlY)+9?O0dR9w8>vI{T`la2^BU0-_`%h~`YfJmDov>b4 zHZTJ|2|HmWWIihkU^>Vb!|iY#=)7xcoA9C7Uj^;8%}_rtO)pELYjg2B%GxK};T6`N zWvBL_qd8m+I-7*f$CmSt{AkT=0gd5a&{>@6$NG><;1Q7BzhNQBZOb3&2d+lSkJt)F zLaHCNL-M@V`{0Z$n^Z@kG5Grc(&9@Fh4uqXL;GCh4v1~mm<>|T=J{|u_`Ps|BO2&4 zv~OZxI^7%F>f$jqzht&N=yL6ug+aUI(ch5PH3~%|KN1xVQ zY@_m5K<^qoL+jf`*}rRJXSA6w-UM|JJ# zPeZ&H)vi187hpBq17@o=aSa>>7A8|lC-7RN&N=fF%g1Ps*V^OMb#|Do_J0Yqmp%g1 zATwsNL%N`Kt$Vv-#j(@Mzd`5OVDTqY);`x+P(LG*aT?WwFTG=iLC-HOuC>D_x|J;v@hQYTIX?W zE9-qMy@Q#9*5sKm3KrL-&ZW$=trtn*-;WQe9Flu;6K%?`7ojqJ$bX3QVPt$)Srhs) zZF|)b;v9k84w`@62Rgc^c9IjZ>^WQ>;*TKpF6_izM>ZjcWk|i({Mi3r@k4CQK>^+Y z9i6cq4IHB={s3j2(Ve<-)%827;{&B?rBE)G3zfoHu`)PTq(!V8&z16(T*-+tlyarM zkzA?Y<;D(yNp4`wxxq42-0T7r$K9N2)eTmwLofoRfoioZC|6)y6avMneh=j*Qnv!? zzf&CB-tQEcW`4q4>0Ht~SZAo8zf1!LdLsg&OYI^Qpb!@>9UavbIpsXe7T z+iuteOQGXMqw-Hyz*=&m~kYeC=F zdgXs6zHSD)PuxwpXLq`gr}opgLk!K&58xQ+cnrZ-`%>RM;@=KF+l^nFdz0o1xeWG# z&C_j^^U(Gf%m#S@#NRXYo$@W}@8w7SK;Kh-4VJ&;_d|PI;jocEtr30yiT&w5bQ-kf zubHy^f!+%@LL5(PsR%9Kf6?{N3;8~X?~(GW`uWf-rMCLzFK`1g&R^w+;gzAkso$VC z9XSF?U#hPQI!%4Gsu285Lf!&tF*nk#{-!Ra122QD-@ATApFiK^Ea>amKM$6`i=eGD z9DVuG`zz}=uATH9FTG|T>A-oQy_z-``)GeVSniVN!o8p~T+e$oOoe9Qe82SI)vyQB zV(R=`1x@`)2QGy)Kh~ymVle2w0Qu*HzJ3$9F!VQ!8FaKiKM&?VO z4D_tDuahx1(bn+Xpx>V1$&_^`N&1NEKcQdmqgLkQ6HR={?^AFcTn1@tO=n>%`Ri`- zGl+9dE9-uwyLv16U)_Q~8^b8N8cQ5g`#{gd&u=oIwIh|g?hWz#J@ylqe~O;&8A*5h z)zuI0g4nj5vT`4S)L69Jn$d5*w?QkpEB-e?=iGkzCM)U({YI)i*iPQk)jn7Ysq2(> z`PR5Df(M};|5_7%?8%(tdeE7R+|j0g<+2TCLG04UR2vM!28i!et&Bm>bUEzjH?1Na z(7B@Z){5^~=l#LH?mE;DMV<}1Pqn&UV!yhx&xX3+tb%j`S0G~_&B}UT>%Hw=2Osiv z9O&$R6q@R*Gc&nqkMxIO5efYf_C%V)BE6y1EVRV>ZWAvb=#>wguU)0af%Jg z(uID|T5UB}30=!y_n#{v>v!>1eQK>0LHRVyIoTO(PC~Z2kCm(LZ;Ev;bhBP;e7EE0 zfb$@kYg#?zp(1{dWLk<%+hhmI=ZBEY zA+4@GtaUS*p}&vz@4y@2HJ~&7FOc-5``}V&d)>r?GJB-%-hY5(oV5DC(9_(e^=+Wt z->Z>6H{_Y{2$=qzlovr)x!X9*5AqE7J6N1{rOuIgV6H8Hom01h_K(Jwho0nafr3BX zJ>CO*q21i2zXkTYS5Q};I_Fk`-me;qh3%=7-{8N9)H-f=4Bx{>xz_ViIneN)T{q~m z5Mt?Wu?}>Gv@-_XHK#zAS#)Qv_@F(gF|_j>9zcH(x=Wx3*2)3&9EPFQyJQ(%oi#nk zpnDg}1GL{Rf!)x`TGBJ~Yrgvoy7v%r(caVh=zeH6h7ok8LHEIX7@l zW`e0rSg9NXmm`(G_QFM>ZNlaqCl+#jBU0ZzH-hdPvC4!lD_78*{|Js!{ps>iuAp<~ zR2U5Pr%U>__LcvngPl8nGqi_4EOON{f2br`;t!Q1@2FODlB=uyhwQIbhmqqs{`|;f zk^gU$>V=vtAbV?k=9>Ni;lb|&=)S?aG#{y0nZN-@Z) - - - - - - - - - - diff --git a/docs/assets/github-dark-dimmed.css b/docs/assets/github-dark-dimmed.css deleted file mode 100644 index e702177..0000000 --- a/docs/assets/github-dark-dimmed.css +++ /dev/null @@ -1,15 +0,0 @@ -pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! - Theme: GitHub Dark Dimmed - Description: Dark dimmed theme as seen on github.com - Author: github.com - Maintainer: @Hirse - Updated: 2021-05-15 - Modified: 2022:12:27 by @michalsn - - Colors taken from GitHub's CSS -*/.hljs{color:#adbac7 !important;background-color:#22272e !important}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#f47067}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#dcbdfb}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#6cb6ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#96d0ff}.hljs-built_in,.hljs-symbol{color:#f69d50}.hljs-code,.hljs-comment,.hljs-formula{color:#768390}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#8ddb8c}.hljs-subst{color:#adbac7}.hljs-section{color:#316dca;font-weight:700}.hljs-bullet{color:#eac55f}.hljs-emphasis{color:#adbac7;font-style:italic}.hljs-strong{color:#adbac7;font-weight:700}.hljs-addition{color:#b4f1b4;background-color:#1b4721}.hljs-deletion{color:#ffd8d3;background-color:#78191b} - -[data-md-color-scheme="default"] { - --md-default-fg-color--lightest: #575757; - --md-default-fg-color--light: #959595; -} diff --git a/docs/assets/hljs.js b/docs/assets/hljs.js deleted file mode 100644 index 48bc360..0000000 --- a/docs/assets/hljs.js +++ /dev/null @@ -1,3 +0,0 @@ -document.addEventListener('DOMContentLoaded', (ev) => { - hljs.highlightAll(); -}); diff --git a/docs/basic_usage.md b/docs/basic-usage.md similarity index 97% rename from docs/basic_usage.md rename to docs/basic-usage.md index 16f2f42..afe403f 100644 --- a/docs/basic_usage.md +++ b/docs/basic-usage.md @@ -47,8 +47,8 @@ One of the most popular tasks delegated to a queue is sending email messages. Th namespace App\Jobs; use Exception; -use Michalsn\CodeIgniterQueue\BaseJob; -use Michalsn\CodeIgniterQueue\Interfaces\JobInterface; +use CodeIgniter\Queue\BaseJob; +use CodeIgniter\Queue\Interfaces\JobInterface; class Email extends BaseJob implements JobInterface { diff --git a/docs/installation.md b/docs/installation.md index daf7973..24e6418 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -14,7 +14,7 @@ The only thing you have to do is to run this command, and you're ready to go. In the example below we will assume, that files from this project will be located in `app/ThirdParty/queue` directory. -Download this project and then enable it by editing the `app/Config/Autoload.php` file and adding the `Michalsn\CodeIgniterQueue` namespace to the `$psr4` array, like in the below example: +Download this project and then enable it by editing the `app/Config/Autoload.php` file and adding the `CodeIgniter\Queue` namespace to the `$psr4` array, like in the below example: ```php APPPATH, // For custom app namespace 'Config' => APPPATH . 'Config', - 'Michalsn\CodeIgniterQueue' => APPPATH . 'ThirdParty/queue/src', + 'CodeIgniter\Queue' => APPPATH . 'ThirdParty/queue/src', ]; // ... diff --git a/docs/running_queues.md b/docs/running-queues.md similarity index 100% rename from docs/running_queues.md rename to docs/running-queues.md diff --git a/src/BaseJob.php b/src/BaseJob.php index fd94582..ad67410 100644 --- a/src/BaseJob.php +++ b/src/BaseJob.php @@ -1,6 +1,6 @@ [ - 'queue:job' => 'Michalsn\CodeIgniterQueue\Commands\Generators\Views\job.tpl.php', + 'queue:job' => 'CodeIgniter\Queue\Commands\Generators\Views\job.tpl.php', ], ]; } diff --git a/src/Config/Services.php b/src/Config/Services.php index fe5e158..3624e46 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -1,11 +1,11 @@ Date: Fri, 15 Dec 2023 11:07:14 +0100 Subject: [PATCH 02/68] update docs --- docs/assets/css/codeigniter.css | 18 ++++++ docs/assets/css/codeigniter_dark_mode.css | 71 ++++++++++++++++++++++ docs/assets/favicon.ico | Bin 0 -> 5087 bytes docs/assets/flame.svg | 11 ++++ docs/assets/js/hljs.js | 3 + docs/commands.md | 18 +++--- docs/configuration.md | 12 ++-- docs/index.md | 8 +-- docs/installation.md | 2 +- mkdocs.yml | 51 +++++++++++----- 10 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 docs/assets/css/codeigniter.css create mode 100644 docs/assets/css/codeigniter_dark_mode.css create mode 100644 docs/assets/favicon.ico create mode 100644 docs/assets/flame.svg create mode 100644 docs/assets/js/hljs.js diff --git a/docs/assets/css/codeigniter.css b/docs/assets/css/codeigniter.css new file mode 100644 index 0000000..9895207 --- /dev/null +++ b/docs/assets/css/codeigniter.css @@ -0,0 +1,18 @@ +[data-md-color-scheme="codeigniter"] { + --md-primary-fg-color: #dd4814; + --md-primary-fg-color--light: #ECB7B7; + --md-primary-fg-color--dark: #90030C; + + --md-default-bg-color: #fcfcfc; + + --md-typeset-a-color: #e74c3c; + --md-accent-fg-color: #97310e; + + --md-accent-fg-color--transparent: #ECB7B7; + + --md-code-bg-color: #ffffff; + + .md-typeset code { + border: 1px solid #e1e4e5; + } +} diff --git a/docs/assets/css/codeigniter_dark_mode.css b/docs/assets/css/codeigniter_dark_mode.css new file mode 100644 index 0000000..3c708d3 --- /dev/null +++ b/docs/assets/css/codeigniter_dark_mode.css @@ -0,0 +1,71 @@ +[data-md-color-scheme="slate"] { + --md-primary-fg-color: #b13a10; + --md-primary-fg-color--light: #8d7474; + --md-primary-fg-color--dark: #6d554d; + + --md-default-bg-color: #1e2129; + + --md-typeset-a-color: #ed6436; + --md-accent-fg-color: #f18a67; + + --md-accent-fg-color--transparent: #625151; + + --md-code-bg-color: #282b2d; + + .hljs-title, + .hljs-title.class_, + .hljs-title.class_.inherited__, + .hljs-title.function_ { + color: #c9a69b; + } + + .hljs-meta .hljs-string, + .hljs-regexp, + .hljs-string { + color: #a3b4c7; + } + + .hljs-attr, + .hljs-attribute, + .hljs-literal, + .hljs-meta, + .hljs-number, + .hljs-operator, + .hljs-selector-attr, + .hljs-selector-class, + .hljs-selector-id, + .hljs-variable { + color: #c1b79f; + } + + .hljs-doctag, + .hljs-keyword, + .hljs-meta .hljs-keyword, + .hljs-template-tag, + .hljs-template-variable, + .hljs-type, + .hljs-variable.language_ { + color: #c97100; + } + + .hljs-subst { + color: #ddba52 + } + + .md-typeset code { + border: 1px solid #3f4547; + } + + .md-typeset .admonition.note, + .md-typeset details.note { + border-color: #2c5293; + } + + .md-typeset .note > .admonition-title:before, + .md-typeset .note > summary:before { + background-color: #2c5293; + -webkit-mask-image: var(--md-admonition-icon--note); + mask-image: var(--md-admonition-icon--note); + } + +} diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a725c10e725181a53bd893fdae6e8d39370b8268 GIT binary patch literal 5087 zcmeHLWmlAs)4rGPZb<pF90Ud)*{GXMbkr~e5EULLK zrJ>$`%u7dE9yH(274nbZx~mv@0RaBLdVF#akeN*b0BBcQ>Uv85lFKR-|Fi#1;Q!SG z%4Wp_{;mFlw~nSRz<@`c#0(^iOA}w3)uC>E(VQM1qA>XnQZTTm(d*+uU%arcYyu7y z|B{Eb5qt_Nk@&TQOZz)f`Al@O)(Gcli$>dLhRV-wK`ifvJ0^ekSfQD3f`V{F&VCP3 z0lRhUXzHBh(nzQ(^ zo^$$!^7$H^B{FO1o%nKbMCOx^E;1KNHR=w!9AI9pK}qNNSiQ}rZ>cMxpBW9H-PZal z8M0Pot8eM%y2b&3_zgr+PR~F4Aje4-2BYe&rm?nIth$QDN&?Gikh>FT)#>S|D|La> za3vg*H5{Z>l?iD%1%uM(71J_oz6lynlD0NC z^vG__d19Ad6oI2|9Q3|PLkNMVxPV~o_Hrda5JUok7y{MuAz?I*xQ5hsFzo~N7;*hza2$BZg$1^hpA zJM+_cSK|W?7n08WIEF8G(PeowJJbG1l{vfvF~6+c6oXb{pnyX2aComrG6eVzZTs-M zC=A5>#S-5$UHWYV+88goNpiiZX86u0?tY`z7z_*9XNYIuodsStz?vDs0d9FZ6_V^r zXA@H?mjWct%++OE)x=PRGL{S*GTY(|ha?MAn565PCd(26w9(u{EbCQ;chEZMXW>8f z(TfRMfXsNS zYiLzxnqBS*7Ybth0wF!Q@s}Sxkq>0@)iSe$|H19tA*p#xN`*wBhCQ>0bu>hP)FfZ~ z9a9e>lFd2(S$}rlMV|kiMTJnGwIdzddw1v5U+>eOuFJGgnUwGs|NZ!{d)JIZtV27g zEa9vbzObUBC_k#7sU|g(WeZCV)gM2n6HoHeY$`;6)}#XWc8KJ%Lpvd=D>4tI>iSb3 zA3CCri|WUm^X!CByhYumN5QExtF|CFP+Kc}EZdP`&&SWN(#TqEstQroj0kI$N}1xp zTbT}R7{q%6EE{eV3a3qsYEo@mEsX{ZT|AxF5=tW#Iq-x+v3L3ooqbu;kJ+gJ^QGlaAImES_sM519~q<+uupb{1z zXM2~OGFnj^uFf~NORO(%uUOkUX9s?*R{WM`v9s8lWj~aj9?km{{o_T7z-#-9(X8bb z0Ziz|ET-&nGZ;6;rOZbZjC?D3tj8|bv}|CU=h^1Sus^nJW; z<#Im7gDDzY8%r|vC-s5pJKIQcjHwnz z>4&7U${gh`adTp^R_T&`)RK6WUM=UOXiT;QI&~t79c7F=lI$SVdz~HQH?^?xpmzcVTZ1$hVvO$6M z1O8A2Vo$9NA1)tEgo`q&ldDT@Wy!R0>u<1Y+RbN8`x$->MhD$4yps)c?lGC~;`syvkKA0mljCRJ#|P3&{iq}&qcvTtRB0Ta4Kqg1 zjyRCkj|m5msZvtu#d@(YUT(2;HN`sX91vW|UVKeHz>S}W?U|~HxgEr(TTQ`{*Gg)C zM^Iqi)tH#&hS-KjYUcr8IN1Y;6YGV@fF~Z!)8w7gLq|4Vf0mZ{Ms!-1(eRNat;Wuu z#?>BTT44I8v?(ap1ECqvVo>jWJpmFX!TJr&U8{}GoBz*YS=ZE=v$NE>!6&ILd$paB zcGS0>XlZOUOWUYJ#$a9g%X;o^2B4}L&Qean2E?2*jdQHzJ-v9wO591v46;Z6y1_T9 zGL%$+^#i+SFu+$Sw9f*!LD^(w5*aJkC!`nVKqBu-1({?(pAV)(5{2d}G6&paeA_*& z2^%x-mU`%_EU?t~l%}rt#T_DA3OtAMnqt<2OQS5x11D`4t*%p=jqXC2sZ~DM<3hV} z#>_Z&779A;LQ^U!5j3lEaAH=5<>fvqy+;wt%;w>5yl<#1U(HpnYoI*QDKIAj$Hnj5p;AQk(&af2#8{IVCTQGHC!bHpkRuE$x@X zoYH&+oBV6et4)LyO?-WKRw{24qWEE*Q{3$P@4lv89@}B-9NrC>1nk_Vu`I(b%M+Md z?B>49et2B-i~U+_;Nn^ML6Q&Lh3$)4^&bz^ZgW?23HiyEx_o%;(&@)-(*dfS#=5Zy8D;OnklK6Ed4 zv2*-U=P{4EMkDI8HcF=}#NZrLptZGvI*u{1wjNvqn$Y_z1xEy3r^s;(9A*130)WIdm4n#+~IaZex7{W(zVWH`^(E+BNMFfe*>8?`_4fL+?n z-ToyYCxLRLw>|gDL8d9U_|@7qgb?DxMWy#wn+eB2z-(;5BhU_7VPn)+6wuT~Un&aI zS`dIUq%h$a*S;Mp_Qh|lyJr2WU}Gh3rDhXfWToWJrZ+TK9_-k#ijtdBD-TH2a;2C| z8|omB@Z@J5BCy+EqrSZ32@<{`vGDo`jVx?jD6#1#D_^c!8xduV`4O~VbDJHh8m;BD zrz`}=DPmU6r%$8=C)b>v&o+4GsQ}WL9x9n`7=@{nj&$Nr6NCGeC0GvR!+`rzCpMk0 zo(y}DLP?K>DJzu8*@KP0J0)I-hj*B+m~^zQ;2cKQn5&>0maGv^g^Y>TIgpL^DHw7; znk}8S5Rx23&YV=LeCY^3VO7r9qKHhjZ9f85a6OLPQw~%x_`AxIOUKH4XV)pf!0yL< z(Eb}4w9%bSKcDN|1DO)1Fh2f$EIdIjX35k0ZX#fA{w=TIuqlSkt6YS-C9$EF@8yBr z{0;3uoYWBJ+;6;|bYDIu&_idjz-*m2HyVAeBJmrVEnefgZhY2a6=J5Li6zWsjRc#pK154+ZGOYWVE%kS?P+>23dBgZBD zR0}__PoyIj_t`Fl$4&1-&8%GX3xw*=o3vA!`UEUo+RS?9o~Y2u87$$oG83z#!$|~O z(r~bB(rbHdV3=lOyu}HI&2f)VV5~xgFe`rfXq;+SuEvQ=d@)5%g!CLOMQAveg=>9V zqX=)v$XZ*K@=WH$Rv2$d10Ha*u-(an(4vm%rImJk{zU|Q{AfMH!QdDmyQN(Wf2Ux= zZs;`kg3@?1`~ebg(M_SX|Ccp8I7?o zf@e^_d@}L{Q=9nCBXfXLVR3nL{(?yKRiBa4ig~tV;FZO8=7tl|DE1+aD`zs_P`}$r z=a_W8fw=t&WhS0#joWM2$hbs?B1vxWK@)-Fa{-*V*_Z?&nQOc8`+?Aq2(QCtmg?#MhMVHo*fAdc-9}&&-RpeE@&D>J*E!^L)%ncWt{W5g*v5}*T{d2|cibR*B zgy{K}_R9B1miK`Z0`?A?2PjnC6H)_fh52`j39Tf~eU0iFwTx9ejqNODOWhgy5|Ps# z1d|i-1i<<-WeSLlB8^S+$SO1I + + + + + + + + + + diff --git a/docs/assets/js/hljs.js b/docs/assets/js/hljs.js new file mode 100644 index 0000000..6f9098a --- /dev/null +++ b/docs/assets/js/hljs.js @@ -0,0 +1,3 @@ +document.addEventListener('DOMContentLoaded', (event) => { + hljs.highlightAll(); +}); diff --git a/docs/commands.md b/docs/commands.md index b539f6d..2631459 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -6,15 +6,15 @@ Here are all the commands you can use with the Queue library. Available options: -- [queue:publish](#queuePublish) -- [queue:job](#queueJob) -- [queue:work](#queueWork) -- [queue:stop](#queueStop) -- [queue:clear](#queueClear) -- [queue:failed](#queueFailed) -- [queue:retry](#queueRetry) -- [queue:forget](#queueForget) -- [queue:flush](#queueFlush) +- [queue:publish](#queuepublish) +- [queue:job](#queuejob) +- [queue:work](#queuework) +- [queue:stop](#queuestop) +- [queue:clear](#queueclear) +- [queue:failed](#queuefailed) +- [queue:retry](#queueretry) +- [queue:forget](#queueforget) +- [queue:flush](#queueflush) ### queue:publish diff --git a/docs/configuration.md b/docs/configuration.md index ea6b75a..858713e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,16 +12,16 @@ We will get our copy ready for modifications. Available options: -- [$defaultHandler](#defaultHandler) +- [$defaultHandler](#defaulthandler) - [$handlers](#handlers) - [$database](#database) - [$redis](#redis) - [$predis](#predis) -- [$keepDoneJobs](#keepDoneJobs) -- [$keepFailedJobs](#keepFailedJobs) -- [$queueDefaultPriority](#queueDefaultPriority) -- [$queuePriorities](#queuePriorities) -- [$jobHandlers](#jobHandlers) +- [$keepDoneJobs](#keepdonejobs) +- [$keepFailedJobs](#keepfailedjobs) +- [$queueDefaultPriority](#queuedefaultpriority) +- [$queuePriorities](#queuepriorities) +- [$jobHandlers](#jobhandlers) ### $defaultHandler diff --git a/docs/index.md b/docs/index.md index 1434981..7d3c93a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,14 +14,14 @@ Listen for queued jobs. ### Requirements -![PHP](https://img.shields.io/badge/PHP-%5E8.1-blue) -![CodeIgniter](https://img.shields.io/badge/CodeIgniter-%5E4.3-blue) +![PHP](https://img.shields.io/badge/PHP-%5E8.1-red) +![CodeIgniter](https://img.shields.io/badge/CodeIgniter-%5E4.3-red) ### Table of Contents * [Installation](installation.md) * [Configuration](configuration.md) -* [Basic usage](basic_usage.md) -* [Running queues](running_queues.md) +* [Basic usage](basic-usage) +* [Running queues](running-queues) * [Commands](commands.md) * [Troubleshooting](troubleshooting.md) diff --git a/docs/installation.md b/docs/installation.md index 24e6418..fd9df7b 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -8,7 +8,7 @@ The only thing you have to do is to run this command, and you're ready to go. - composer require michalsn/codeigniter-queue + composer require codeigniter4/queue ## Manual Installation diff --git a/mkdocs.yml b/mkdocs.yml index 774318b..15e9447 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,30 +9,51 @@ theme: repo: fontawesome/brands/github palette: - media: "(prefers-color-scheme: light)" - scheme: default - primary: indigo - accent: indigo + scheme: codeigniter + primary: custom + accent: custom toggle: icon: material/brightness-7 name: Switch to dark mode - media: "(prefers-color-scheme: dark)" scheme: slate - primary: indigo - accent: indigo + primary: custom + accent: custom toggle: icon: material/brightness-4 name: Switch to light mode + features: + - navigation.instant + - navigation.instant.prefetch + - content.code.copy + - navigation.footer + - content.action.edit + - navigation.top + - search.suggest + - search.highlight + - search.share extra: - homepage: https://michalsn.github.io/codeigniter-queue + homepage: https://codeigniter.com + generator: false social: - - icon: fontawesome/brands/github - link: https://github.com/michalsn/codeigniter-queue + - icon: material/github + link: https://github.com/codeigniter4/queue name: GitHub + - icon: material/twitter + link: https://twitter.com/CodeIgniterPhp + name: X + - icon: material/forum + link: https://forum.codeigniter.com + name: Forum Codeigniter + - icon: material/slack + link: https://join.slack.com/t/codeigniterchat/shared_invite/zt-244xrrslc-l_I69AJSi5y2a2RVN~xIdQ + name: Slack -repo_url: https://github.com/michalsn/codeigniter-queue +repo_url: https://github.com/codeigniter4/queue edit_uri: edit/develop/docs/ +copyright: Copyright © 2023 CodeIgniter Foundation. markdown_extensions: - admonition @@ -41,17 +62,19 @@ markdown_extensions: use_pygments: false extra_css: - - assets/github-dark-dimmed.css + - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/styles/github.min.css + - assets/css/codeigniter.css + - assets/css/codeigniter_dark_mode.css extra_javascript: - - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.6.0/build/highlight.min.js - - assets/hljs.js + - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/highlight.min.js + - assets/js/hljs.js nav: - Home: index.md - Installation: installation.md - Configuration: configuration.md - - Basic usage: basic_usage.md - - Running queues: running_queues.md + - Basic usage: basic-usage.md + - Running queues: running-queues.md - Commands: commands.md - Troubleshooting: troubleshooting.md From 13f5fd8570dd6d4972ca1f268585b51b3cd7bc42 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 11:07:43 +0100 Subject: [PATCH 03/68] add contributing and security info --- CONTRIBUTING.md | 10 ++++++++++ SECURITY.md | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 SECURITY.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b712dd6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing to CodeIgniter4 + +CodeIgniter is a community driven project and accepts contributions of +code and documentation from the community. + +If you'd like to contribute, please read [Contributing to CodeIgniter](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/README.md) +in the [main repository](https://github.com/codeigniter4/CodeIgniter4). + +If you are going to contribute to this repository, please report bugs or send PRs +to this repository instead of the main repository. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7879188 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,26 @@ +# Security Policy + +The development team and community take all security issues seriously. **Please do not make public any uncovered flaws.** + +## Reporting a Vulnerability + +Thank you for improving the security of our code! Any assistance in removing security flaws will be acknowledged. + +**Please report security flaws by emailing the development team directly: security@codeigniter.com**. + +The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating +the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the +progress towards a fix and full announcement, and may ask for additional information or guidance. + +## Disclosure Policy + +When the security team receives a security bug report, they will assign it to a primary handler. +This person will coordinate the fix and release process, involving the following steps: + +- Confirm the problem and determine the affected versions. +- Audit code to find any potential similar problems. +- Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible. + +## Comments on this Policy + +If you have suggestions on how this process could be improved please submit a Pull Request. From 0ab87e346d4c25ed4c8980d67ac7cb60122dd10a Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 11:07:56 +0100 Subject: [PATCH 04/68] update license --- LICENSE | 1 + 1 file changed, 1 insertion(+) diff --git a/LICENSE b/LICENSE index 3403dc7..3cbdd9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ MIT License Copyright (c) 2023 Michal Sniatala +Copyright (c) 2023 CodeIgniter Foundation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From eb8ed7f5d5aabfc4bd70a9d026a9d334d280f224 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 17:13:47 +0100 Subject: [PATCH 05/68] update readme --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e7af099..8ab9029 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,17 @@ Queues for the CodeIgniter 4 framework. -[![PHPUnit](https://github.com/michalsn/codeigniter-queue/actions/workflows/phpunit.yml/badge.svg)](https://github.com/michalsn/codeigniter-queue/actions/workflows/phpunit.yml) -[![PHPStan](https://github.com/michalsn/codeigniter-queue/actions/workflows/phpstan.yml/badge.svg)](https://github.com/michalsn/codeigniter-queue/actions/workflows/phpstan.yml) -[![Deptrac](https://github.com/michalsn/codeigniter-queue/actions/workflows/deptrac.yml/badge.svg)](https://github.com/michalsn/codeigniter-queue/actions/workflows/deptrac.yml) -[![Coverage Status](https://coveralls.io/repos/github/michalsn/codeigniter-queue/badge.svg?branch=develop)](https://coveralls.io/github/michalsn/codeigniter-queue?branch=develop) +[![PHPUnit](https://github.com/codeigniter4/queue/actions/workflows/phpunit.yml/badge.svg)](https://github.com/codeigniter4/queue/actions/workflows/phpunit.yml) +[![PHPStan](https://github.com/codeigniter4/queue/actions/workflows/phpstan.yml/badge.svg)](https://github.com/codeigniter4/queue/actions/workflows/phpstan.yml) +[![Deptrac](https://github.com/codeigniter4/queue/actions/workflows/deptrac.yml/badge.svg)](https://github.com/codeigniter4/queue/actions/workflows/deptrac.yml) +[![Coverage Status](https://coveralls.io/repos/github/codeigniter4/queue/badge.svg?branch=develop)](https://coveralls.io/github/codeigniter4/queue?branch=develop) ![PHP](https://img.shields.io/badge/PHP-%5E8.1-blue) ![CodeIgniter](https://img.shields.io/badge/CodeIgniter-%5E4.3-blue) ## Installation - composer require michalsn/codeigniter-queue + composer require codeigniter4/queue Migrate your database: @@ -58,4 +58,4 @@ Run the queue worker: ## Docs -https://michalsn.github.io/codeigniter-queue/ +https://codeigniter4.github.io/queue/ From 7cfd9c3777f325bf10a42962730e323f728c4319 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 17:14:04 +0100 Subject: [PATCH 06/68] composer update --- composer.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 2fff215..e6b709f 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "michalsn/codeigniter-queue", + "name": "codeigniter4/queue", "description": "Queues for CodeIgniter 4 framework", "license": "MIT", "type": "library", @@ -11,20 +11,20 @@ "role": "Developer" } ], - "homepage": "https://github.com/michalsn/codeigniter-queue", + "homepage": "https://github.com/codeigniter4/queue", "require": { "php": "^8.1" }, "require-dev": { "codeigniter4/devkit": "^1.0", - "codeigniter4/framework": "^4.4", + "codeigniter4/framework": "^4.3", "predis/predis": "^2.0" }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { "psr-4": { - "Michalsn\\CodeIgniterQueue\\": "src" + "CodeIgniter\\Queue\\": "src" } }, "autoload-dev": { From 982f45e0f0d5dd68afbbf4569bc0d8ad82d8df10 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 20:17:51 +0100 Subject: [PATCH 07/68] cs-fix --- src/Commands/QueueWork.php | 2 +- src/Handlers/PredisHandler.php | 2 +- tests/DatabaseHandlerTest.php | 4 ++-- tests/PredisHandlerTest.php | 4 ++-- tests/RedisHandlerTest.php | 4 ++-- tests/_support/Jobs/Failure.php | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index c19214b..c4a228d 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -4,9 +4,9 @@ use CodeIgniter\CLI\BaseCommand; use CodeIgniter\CLI\CLI; -use Exception; use CodeIgniter\Queue\Config\Queue as QueueConfig; use CodeIgniter\Queue\Entities\QueueJob; +use Exception; use Throwable; class QueueWork extends BaseCommand diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 2746b19..bdfeadd 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -4,12 +4,12 @@ use CodeIgniter\Exceptions\CriticalError; use CodeIgniter\I18n\Time; -use Exception; use CodeIgniter\Queue\Config\Queue as QueueConfig; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; use CodeIgniter\Queue\Interfaces\QueueInterface; use CodeIgniter\Queue\Payload; +use Exception; use Predis\Client; use Throwable; diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 6ab2aa9..af059e6 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -2,13 +2,13 @@ namespace Tests; -use CodeIgniter\Test\ReflectionHelper; -use Exception; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; use CodeIgniter\Queue\Exceptions\QueueException; use CodeIgniter\Queue\Handlers\DatabaseHandler; use CodeIgniter\Queue\Models\QueueJobFailedModel; +use CodeIgniter\Test\ReflectionHelper; +use Exception; use ReflectionException; use Tests\Support\Config\Queue as QueueConfig; use Tests\Support\Database\Seeds\TestDatabaseQueueSeeder; diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 19ec736..6cccdfe 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -3,11 +3,11 @@ namespace ThirdParty\queue\tests; use CodeIgniter\I18n\Time; -use CodeIgniter\Test\ReflectionHelper; -use Exception; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Exceptions\QueueException; use CodeIgniter\Queue\Handlers\PredisHandler; +use CodeIgniter\Test\ReflectionHelper; +use Exception; use ReflectionException; use Tests\Support\Config\Queue as QueueConfig; use Tests\Support\Database\Seeds\TestRedisQueueSeeder; diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index 60b57e5..43350f6 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -3,11 +3,11 @@ namespace Tests; use CodeIgniter\I18n\Time; -use CodeIgniter\Test\ReflectionHelper; -use Exception; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Exceptions\QueueException; use CodeIgniter\Queue\Handlers\RedisHandler; +use CodeIgniter\Test\ReflectionHelper; +use Exception; use Tests\Support\Config\Queue as QueueConfig; use Tests\Support\Database\Seeds\TestRedisQueueSeeder; use Tests\Support\TestCase; diff --git a/tests/_support/Jobs/Failure.php b/tests/_support/Jobs/Failure.php index a7e66c0..737ef53 100644 --- a/tests/_support/Jobs/Failure.php +++ b/tests/_support/Jobs/Failure.php @@ -2,9 +2,9 @@ namespace Tests\Support\Jobs; -use Exception; use CodeIgniter\Queue\BaseJob; use CodeIgniter\Queue\Interfaces\JobInterface; +use Exception; class Failure extends BaseJob implements JobInterface { From 3f85ca47e63c0265f8f8496f5df4f2aa09b7fc2a Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 15 Dec 2023 20:18:18 +0100 Subject: [PATCH 08/68] phpstan config update --- phpstan.neon.dist | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ede8df0..d8d5352 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -17,7 +17,7 @@ parameters: paths: - src/Config/Services.php - - message: '#Call to an undefined method Michalsn\\CodeIgniterQueue\\Handlers\\BaseHandler::push\(\).#' + message: '#Call to an undefined method CodeIgniter\\Queue\\Handlers\\BaseHandler::push\(\).#' paths: - src/Handlers/BaseHandler.php - @@ -40,19 +40,19 @@ parameters: - tests/RedisHandlerTest.php - tests/PredisHandlerTest.php - - message: '#Call to an undefined method Michalsn\\CodeIgniterQueue\\Models\\QueueJobFailedModel::affectedRows\(\).#' + message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::affectedRows\(\).#' paths: - src/Handlers/BaseHandler.php - - message: '#Call to an undefined method Michalsn\\CodeIgniterQueue\\Models\\QueueJobFailedModel::truncate\(\).#' + message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::truncate\(\).#' paths: - src/Handlers/BaseHandler.php - - message: '#Parameter \#3 \$tries of method Michalsn\\CodeIgniterQueue\\Commands\\QueueWork::handleWork\(\) expects int\|null, string\|true\|null given.#' + message: '#Parameter \#3 \$tries of method CodeIgniter\\Queue\\Commands\\QueueWork::handleWork\(\) expects int\|null, string\|true\|null given.#' paths: - src/Commands/QueueWork.php - - message: '#Parameter \#4 \$retryAfter of method Michalsn\\CodeIgniterQueue\\Commands\\QueueWork::handleWork\(\) expects int\|null, string\|true\|null given.#' + message: '#Parameter \#4 \$retryAfter of method CodeIgniter\\Queue\\Commands\\QueueWork::handleWork\(\) expects int\|null, string\|true\|null given.#' paths: - src/Commands/QueueWork.php - From e8c074bae85f4e253b37b39018cf6e225e5afb13 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Dec 2023 11:00:58 +0900 Subject: [PATCH 09/68] refactor: fix variable types --- src/Commands/QueueWork.php | 2 ++ src/Handlers/PredisHandler.php | 2 +- src/Handlers/RedisHandler.php | 8 ++++---- tests/_support/Database/Seeds/TestRedisQueueSeeder.php | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index c4a228d..6fc7bf2 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -94,7 +94,9 @@ public function run(array $params) $memory = $params['memory'] ?? CLI::getOption('memory') ?? 128; $priority = $params['priority'] ?? CLI::getOption('priority') ?? $config->getQueuePriorities($queue) ?? 'default'; $tries = $params['tries'] ?? CLI::getOption('tries'); + $tries = ($tries !== null) ? (int) $tries : $tries; $retryAfter = $params['retry-after'] ?? CLI::getOption('retry-after'); + $retryAfter = ($retryAfter !== null) ? (int) $retryAfter : $retryAfter; $countJobs = 0; if (array_key_exists('stop-when-empty', $params) || CLI::getOption('stop-when-empty')) { diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index bdfeadd..48eb343 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -80,7 +80,7 @@ public function pop(string $queue, array $priorities): ?QueueJob $queueJob->status = Status::RESERVED->value; $queueJob->syncOriginal(); - $this->predis->hset("queues:{$queue}::reserved", $queueJob->id, json_encode($queueJob)); + $this->predis->hset("queues:{$queue}::reserved", (string) $queueJob->id, json_encode($queueJob)); return $queueJob; } diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index b3bd0f1..3036893 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -99,7 +99,7 @@ public function pop(string $queue, array $priorities): ?QueueJob $queueJob->status = Status::RESERVED->value; $queueJob->syncOriginal(); - $this->redis->hSet("queues:{$queue}::reserved", $queueJob->id, json_encode($queueJob)); + $this->redis->hSet("queues:{$queue}::reserved", (string) $queueJob->id, json_encode($queueJob)); return $queueJob; } @@ -115,7 +115,7 @@ public function later(QueueJob $queueJob, int $seconds): bool $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; if ($result = (int) $this->redis->zAdd("queues:{$queueJob->queue}:{$queueJob->priority}", $queueJob->available_at->timestamp, json_encode($queueJob))) { - $this->redis->hDel("queues:{$queueJob->queue}::reserved", $queueJob->id); + $this->redis->hDel("queues:{$queueJob->queue}::reserved", (string) $queueJob->id); } return $result > 0; @@ -130,7 +130,7 @@ public function failed(QueueJob $queueJob, Throwable $err, bool $keepJob): bool $this->logFailed($queueJob, $err); } - return (bool) $this->redis->hDel("queues:{$queueJob->queue}::reserved", $queueJob->id); + return (bool) $this->redis->hDel("queues:{$queueJob->queue}::reserved", (string) $queueJob->id); } /** @@ -145,7 +145,7 @@ public function done(QueueJob $queueJob, bool $keepJob): bool $this->redis->lPush("queues:{$queueJob->queue}::done", json_encode($queueJob)); } - return (bool) $this->redis->hDel("queues:{$queueJob->queue}::reserved", $queueJob->id); + return (bool) $this->redis->hDel("queues:{$queueJob->queue}::reserved", (string) $queueJob->id); } /** diff --git a/tests/_support/Database/Seeds/TestRedisQueueSeeder.php b/tests/_support/Database/Seeds/TestRedisQueueSeeder.php index 76164b1..abaebed 100644 --- a/tests/_support/Database/Seeds/TestRedisQueueSeeder.php +++ b/tests/_support/Database/Seeds/TestRedisQueueSeeder.php @@ -40,7 +40,7 @@ public function run(): void 'attempts' => 0, 'available_at' => 1_697_269_864, ]); - $redis->hSet("queues:{$jobQueue->queue}::reserved", $jobQueue->id, json_encode($jobQueue)); + $redis->hSet("queues:{$jobQueue->queue}::reserved", (string) $jobQueue->id, json_encode($jobQueue)); $jobQueue = new QueueJob([ 'id' => '1234567890654321', From 5529415e2d1e7d08da71a814b84cd157cdc33b68 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Dec 2023 11:01:45 +0900 Subject: [PATCH 10/68] docs: add PHPDoc types --- src/Config/Queue.php | 8 ++++++++ src/Config/Services.php | 2 +- src/Entities/QueueJob.php | 11 +++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/Config/Queue.php b/src/Config/Queue.php index 7241578..db407e0 100644 --- a/src/Config/Queue.php +++ b/src/Config/Queue.php @@ -7,6 +7,8 @@ use CodeIgniter\Queue\Handlers\DatabaseHandler; use CodeIgniter\Queue\Handlers\PredisHandler; use CodeIgniter\Queue\Handlers\RedisHandler; +use CodeIgniter\Queue\Interfaces\JobInterface; +use CodeIgniter\Queue\Interfaces\QueueInterface; class Queue extends BaseConfig { @@ -17,6 +19,8 @@ class Queue extends BaseConfig /** * Available handlers. + * + * @var array> */ public array $handlers = [ 'database' => DatabaseHandler::class, @@ -81,6 +85,8 @@ class Queue extends BaseConfig /** * Your jobs handlers. + * + * @var array> */ public array $jobHandlers = []; @@ -95,6 +101,8 @@ public function __construct() /** * Resolve job class name. + * + * @return class-string */ public function resolveJobClass(string $name): string { diff --git a/src/Config/Services.php b/src/Config/Services.php index 3624e46..353e8ff 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -15,7 +15,7 @@ public static function queue(?QueueConfig $config = null, $getShared = true): Qu return static::getSharedInstance('queue', $config); } - /** @var QueueConfig $config */ + /** @var QueueConfig|null $config */ $config ??= config('Queue'); return (new Queue($config))->init(); diff --git a/src/Entities/QueueJob.php b/src/Entities/QueueJob.php index 4d79d97..28fac48 100644 --- a/src/Entities/QueueJob.php +++ b/src/Entities/QueueJob.php @@ -3,7 +3,18 @@ namespace CodeIgniter\Queue\Entities; use CodeIgniter\Entity\Entity; +use CodeIgniter\I18n\Time; +/** + * @property int $attempts + * @property Time $available_at + * @property Time $created_at + * @property int $id + * @property array $payload + * @property string $priority + * @property string $queue + * @property int $status + */ class QueueJob extends Entity { protected $dates = ['available_at', 'created_at']; From be1f5885518d1018b5034abdfd9aa055f1660343 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Dec 2023 11:02:03 +0900 Subject: [PATCH 11/68] chore: remove ignoreErrors --- phpstan.neon.dist | 48 ----------------------------------------------- 1 file changed, 48 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index d8d5352..42f061e 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,61 +8,13 @@ parameters: - vendor/codeigniter4/framework/system/Test/bootstrap.php excludePaths: ignoreErrors: - - - message: '#Cannot use \+\+ on array\|bool\|float\|int\|object\|string\|null.#' - paths: - - src/Commands/QueueWork.php - - - message: '#Variable \$config on left side of \?\?\= always exists and is not nullable.#' - paths: - - src/Config/Services.php - - - message: '#Call to an undefined method CodeIgniter\\Queue\\Handlers\\BaseHandler::push\(\).#' - paths: - - src/Handlers/BaseHandler.php - message: '#Call to deprecated function random_string\(\):#' paths: - src/Handlers/RedisHandler.php - src/Handlers/PredisHandler.php - - - message: '#Cannot access property \$timestamp on array\|bool\|float\|int\|object\|string.#' - paths: - - tests/_support/Database/Seeds/TestRedisQueueSeeder.php - message: '#Access to an undefined property CodeIgniter\\I18n\\Time::\$timestamp.#' - paths: - - src/Handlers/BaseHandler.php - - src/Handlers/DatabaseHandler.php - - src/Handlers/RedisHandler.php - - src/Handlers/PredisHandler.php - - src/Models/QueueJobModel.php - - tests/RedisHandlerTest.php - - tests/PredisHandlerTest.php - - - message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::affectedRows\(\).#' - paths: - - src/Handlers/BaseHandler.php - - - message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::truncate\(\).#' - paths: - - src/Handlers/BaseHandler.php - - - message: '#Parameter \#3 \$tries of method CodeIgniter\\Queue\\Commands\\QueueWork::handleWork\(\) expects int\|null, string\|true\|null given.#' - paths: - - src/Commands/QueueWork.php - - - message: '#Parameter \#4 \$retryAfter of method CodeIgniter\\Queue\\Commands\\QueueWork::handleWork\(\) expects int\|null, string\|true\|null given.#' - paths: - - src/Commands/QueueWork.php - - - message: '#Expression on left side of \?\? is not nullable.#' - paths: - - src/Commands/QueueWork.php - - - message: '#Variable \$job might not be defined.#' - paths: - - src/Commands/QueueWork.php universalObjectCratesClasses: - CodeIgniter\Entity - CodeIgniter\Entity\Entity From a2db811999a9cd505159fa63110fd089bdf1e8a8 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Dec 2023 09:55:06 +0900 Subject: [PATCH 12/68] fix: add missing () --- src/Commands/QueueWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 6fc7bf2..4438ca0 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -192,7 +192,7 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i CLI::write('The processing of this job was successful', 'green'); } catch (Throwable $err) { - if (isset($job) && ++$work->attempts < $tries ?? $job->getTries()) { + if (isset($job) && ++$work->attempts < ($tries ?? $job->getTries())) { // Schedule for later service('queue')->later($work, $retryAfter ?? $job->getRetryAfter()); } else { From 440d844007bc3e62fddead9f710a6968b30afbd0 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 09:53:10 +0100 Subject: [PATCH 13/68] read options and validate them --- src/Commands/QueueWork.php | 70 +++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 4438ca0..50de84e 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -79,7 +79,7 @@ public function run(array $params) $stopWhenEmpty = false; $waiting = false; - // Read params + // Read queue name from params if (! $queue = array_shift($params)) { CLI::error('The queueName is not specified.'); @@ -87,17 +87,25 @@ public function run(array $params) } // Read options - $sleep = $params['sleep'] ?? CLI::getOption('sleep') ?? 10; - $rest = $params['rest'] ?? CLI::getOption('rest') ?? 0; - $maxJobs = $params['max-jobs'] ?? CLI::getOption('max-jobs') ?? 0; - $maxTime = $params['max-time'] ?? CLI::getOption('max-time') ?? 0; - $memory = $params['memory'] ?? CLI::getOption('memory') ?? 128; - $priority = $params['priority'] ?? CLI::getOption('priority') ?? $config->getQueuePriorities($queue) ?? 'default'; - $tries = $params['tries'] ?? CLI::getOption('tries'); - $tries = ($tries !== null) ? (int) $tries : $tries; - $retryAfter = $params['retry-after'] ?? CLI::getOption('retry-after'); - $retryAfter = ($retryAfter !== null) ? (int) $retryAfter : $retryAfter; - $countJobs = 0; + [ + $error, + $sleep, + $rest, + $maxJobs, + $maxTime, + $memory, + $priority, + $tries, + $retryAfter + ] = $this->readOptions($params, $config, $queue); + + if ($error !== null) { + CLI::write($error, 'red'); + + return EXIT_ERROR; + } + + $countJobs = 0; if (array_key_exists('stop-when-empty', $params) || CLI::getOption('stop-when-empty')) { $stopWhenEmpty = true; @@ -113,7 +121,7 @@ public function run(array $params) CLI::write(PHP_EOL); - $priority = array_map('trim', explode(',', $priority)); + $priority = array_map('trim', explode(',', (string) $priority)); while (true) { $work = service('queue')->pop($queue, $priority); @@ -177,6 +185,42 @@ public function run(array $params) } } + private function readOptions(array $params, QueueConfig $config, string $queue): array + { + $options = [ + 'error' => null, + 'sleep' => $params['sleep'] ?? CLI::getOption('sleep') ?? 10, + 'rest' => $params['rest'] ?? CLI::getOption('rest') ?? 0, + 'maxJobs' => $params['max-jobs'] ?? CLI::getOption('max-jobs') ?? 0, + 'maxTime' => $params['max-time'] ?? CLI::getOption('max-time') ?? 0, + 'memory' => $params['memory'] ?? CLI::getOption('memory') ?? 128, + 'priority' => $params['priority'] ?? CLI::getOption('priority') ?? $config->getQueuePriorities($queue) ?? 'default', + 'tries' => $params['tries'] ?? CLI::getOption('tries'), + 'retryAfter' => $params['retry-after'] ?? CLI::getOption('retry-after'), + ]; + + // Options that, being defined, cannot be `true` + $keys = ['sleep', 'rest', 'maxJobs', 'maxTime', 'memory', 'priority', 'tries', 'retyAfter']; + + foreach ($keys as $key) { + if ($options[$key] === true) { + $options['error'] = sprintf('Option: "-%s" must have a defined value.', $key); + + return array_values($options); + } + } + // Options that, being defined, have to be `int` + $keys = array_diff($keys, ['priority']); + + foreach ($keys as $key) { + if ($options[$key] !== null && ! is_int($options[$key])) { + $options[$key] = (int) $options[$key]; + } + } + + return array_values($options); + } + private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?int $retryAfter): void { timer()->start('work'); From 93e4c5ffedaad54e624b5a4bd96f4efdf3d2b8fe Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 09:53:36 +0100 Subject: [PATCH 14/68] fix predis hdel --- src/Handlers/PredisHandler.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 48eb343..32f52f8 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -94,7 +94,7 @@ public function later(QueueJob $queueJob, int $seconds): bool $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; if ($result = $this->predis->zadd("queues:{$queueJob->queue}:{$queueJob->priority}", [json_encode($queueJob) => $queueJob->available_at->timestamp])) { - $this->predis->hdel("queues:{$queueJob->queue}::reserved", $queueJob->id); + $this->predis->hdel("queues:{$queueJob->queue}::reserved", [$queueJob->id]); } return $result > 0; @@ -109,7 +109,7 @@ public function failed(QueueJob $queueJob, Throwable $err, bool $keepJob): bool $this->logFailed($queueJob, $err); } - return (bool) $this->predis->hdel("queues:{$queueJob->queue}::reserved", $queueJob->id); + return (bool) $this->predis->hdel("queues:{$queueJob->queue}::reserved", [$queueJob->id]); } /** @@ -122,7 +122,7 @@ public function done(QueueJob $queueJob, bool $keepJob): bool $this->predis->lpush("queues:{$queueJob->queue}::done", [json_encode($queueJob)]); } - return (bool) $this->predis->hdel("queues:{$queueJob->queue}::reserved", $queueJob->id); + return (bool) $this->predis->hdel("queues:{$queueJob->queue}::reserved", [$queueJob->id]); } /** From 150715b78744495f0e70e3af421dd50875c62d0d Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 09:53:56 +0100 Subject: [PATCH 15/68] update JobInterface --- src/Interfaces/JobInterface.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Interfaces/JobInterface.php b/src/Interfaces/JobInterface.php index 0ae7aeb..eb466a6 100644 --- a/src/Interfaces/JobInterface.php +++ b/src/Interfaces/JobInterface.php @@ -7,4 +7,8 @@ interface JobInterface public function __construct(array $data); public function process(); + + public function getRetryAfter(): int; + + public function getTries(): int; } From 7cbe0470d79a2f6c149c74146d4d1628e626f5a0 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 09:54:26 +0100 Subject: [PATCH 16/68] add abstract methods to BaseHandler --- src/Handlers/BaseHandler.php | 12 ++++++++++++ tests/_support/Jobs/Success.php | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index b6c3a35..16da9bc 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -19,6 +19,18 @@ abstract class BaseHandler protected QueueConfig $config; protected ?string $priority = null; + abstract public function push(string $queue, string $job, array $data): bool; + + abstract public function pop(string $queue, array $priorities): ?QueueJob; + + abstract public function later(QueueJob $queueJob, int $seconds): bool; + + abstract public function failed(QueueJob $queueJob, Throwable $err, bool $keepJob): bool; + + abstract public function done(QueueJob $queueJob, bool $keepJob): bool; + + abstract public function clear(?string $queue = null): bool; + /** * Set priority for job queue. */ diff --git a/tests/_support/Jobs/Success.php b/tests/_support/Jobs/Success.php index 894cff5..dd2125d 100644 --- a/tests/_support/Jobs/Success.php +++ b/tests/_support/Jobs/Success.php @@ -8,7 +8,7 @@ class Success extends BaseJob implements JobInterface { protected int $retryAfter = 6; - protected int $retries = 3; + protected int $tries = 3; public function process(): bool { From 96548ca451812728db23ea68a4621fbfab4b9e56 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 09:55:11 +0100 Subject: [PATCH 17/68] suppress errors referring db methods --- phpstan.neon.dist | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 42f061e..decefa1 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,6 +15,14 @@ parameters: - src/Handlers/PredisHandler.php - message: '#Access to an undefined property CodeIgniter\\I18n\\Time::\$timestamp.#' + - + message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::affectedRows\(\).#' + paths: + - src/Handlers/BaseHandler.php + - + message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::truncate\(\).#' + paths: + - src/Handlers/BaseHandler.php universalObjectCratesClasses: - CodeIgniter\Entity - CodeIgniter\Entity\Entity From 2f5c0ef041c27ac36477c8d1f8f0d4a1e860b957 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 10:50:15 +0100 Subject: [PATCH 18/68] add info about contributing --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ab9029..63c1ecf 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Queues for the CodeIgniter 4 framework. ![PHP](https://img.shields.io/badge/PHP-%5E8.1-blue) ![CodeIgniter](https://img.shields.io/badge/CodeIgniter-%5E4.3-blue) +![License](https://img.shields.io/badge/License-MIT-blue) ## Installation @@ -58,4 +59,11 @@ Run the queue worker: ## Docs -https://codeigniter4.github.io/queue/ +Read the full documentation: https://queue.codeigniter.com + +## Contributing + +We does accept and encourage contributions from the community in any shape. It doesn't matter +whether you can code, write documentation, or help find bugs, all contributions are welcome. +See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details. + From 36b9551ffd7622f12acdde2bb667a2ae6bad2d71 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 10:50:38 +0100 Subject: [PATCH 19/68] list contributors in the docs --- docs/index.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/index.md b/docs/index.md index 7d3c93a..ba3f178 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,3 +25,14 @@ Listen for queued jobs. * [Running queues](running-queues) * [Commands](commands.md) * [Troubleshooting](troubleshooting.md) + +### Acknowledgements + +Every open-source project depends on its contributors to be a success. The following users have +contributed in one manner or another in making this project: + + + Contributors + + +Made with [contrib.rocks](https://contrib.rocks). From b889b9b3d7a085a6e26f782d93fb8350d2ac3dac Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 16:25:44 +0100 Subject: [PATCH 20/68] add CNAME --- docs/CNAME | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/CNAME diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..ec5fa62 --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +queue.codeigniter.com From 6b6ce6c2e41f37d7f3e5eeade07be3f717708ebc Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 17:02:40 +0100 Subject: [PATCH 21/68] fix namespace in the publish command --- src/Commands/QueuePublish.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Commands/QueuePublish.php b/src/Commands/QueuePublish.php index 8167205..e0d1d8c 100644 --- a/src/Commands/QueuePublish.php +++ b/src/Commands/QueuePublish.php @@ -18,7 +18,7 @@ class QueuePublish extends BaseCommand */ public function run(array $params) { - $source = service('autoloader')->getNamespace('Michalsn\\CodeIgniterQueue')[0]; + $source = service('autoloader')->getNamespace('CodeIgniter\\Queue')[0]; $publisher = new Publisher($source, APPPATH); @@ -34,8 +34,8 @@ public function run(array $params) foreach ($publisher->getPublished() as $file) { $contents = file_get_contents($file); - $contents = str_replace('namespace Michalsn\\CodeIgniterQueue\\Config', 'namespace Config', $contents); - $contents = str_replace('use CodeIgniter\\Config\\BaseConfig', 'use Michalsn\\CodeIgniterQueue\\Config\\Queue as BaseQueue', $contents); + $contents = str_replace('namespace CodeIgniter\\Queue\\Config', 'namespace Config', $contents); + $contents = str_replace('use CodeIgniter\\Config\\BaseConfig', 'use CodeIgniter\\Queue\\Config\\Queue as BaseQueue', $contents); $contents = str_replace('class Queue extends BaseConfig', 'class Queue extends BaseQueue', $contents); $method = <<<'EOT' @@ -50,6 +50,8 @@ public function __construct() /** * Resolve job class name. + * + * @return class-string */ public function resolveJobClass(string $name): string { From 7175ba43c3e294200e73a34b708abf34899a1d05 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 17:36:11 +0100 Subject: [PATCH 22/68] fix typo --- src/Commands/QueueWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 50de84e..7f19bd8 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -200,7 +200,7 @@ private function readOptions(array $params, QueueConfig $config, string $queue): ]; // Options that, being defined, cannot be `true` - $keys = ['sleep', 'rest', 'maxJobs', 'maxTime', 'memory', 'priority', 'tries', 'retyAfter']; + $keys = ['sleep', 'rest', 'maxJobs', 'maxTime', 'memory', 'priority', 'tries', 'retryAfter']; foreach ($keys as $key) { if ($options[$key] === true) { From 5a1ca6b8572b4add28d2f5842a1b6dbeb6b866fe Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 06:31:52 +0900 Subject: [PATCH 23/68] fix: orderBy() does not support RawSql --- src/Models/QueueJobModel.php | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 329ed6e..14747de 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -1,9 +1,10 @@ db->DBDriver === 'SQLite3') { - $builder->orderBy(new RawSql('CASE priority ' . implode(' ', array_map(static fn ($value, $key) => "WHEN '{$value}' THEN {$key}", $priority, array_keys($priority))) . ' END')); + $builder->orderBy( + 'CASE priority ' + . implode( + ' ', + array_map(static fn ($value, $key) => "WHEN '{$value}' THEN {$key}", $priority, array_keys($priority)) + ) + . ' END', + '', + false + ); } else { - $builder->orderBy(new RawSql('FIELD(priority, ' . implode(',', array_map(static fn ($value) => "'{$value}'", $priority)) . ')')); + $builder->orderBy( + 'FIELD(priority, ' + . implode( + ',', + array_map(static fn ($value) => "'{$value}'", $priority) + ) + . ')', + '', + false + ); } } From 913c571d0c25b292de5fbd630c16b876bac93787 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 06:33:02 +0900 Subject: [PATCH 24/68] test: fix TypeError --- tests/RedisHandlerTest.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index 43350f6..ddc9f6a 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -1,5 +1,7 @@ assertSame(1_234_567_890_654_321, $result->id); $this->assertSame(0, $redis->zCard('queues:queue1:default')); - $this->assertTrue($redis->hExists('queues:queue1::reserved', $result->id)); + $this->assertTrue($redis->hExists('queues:queue1::reserved', (string) $result->id)); } public function testPopEmpty() @@ -129,13 +131,13 @@ public function testLater() $queueJob = $handler->pop('queue1', ['default']); $redis = self::getPrivateProperty($handler, 'redis'); - $this->assertTrue($redis->hExists('queues:queue1::reserved', $queueJob->id)); + $this->assertTrue($redis->hExists('queues:queue1::reserved', (string) $queueJob->id)); $this->assertSame(0, $redis->zCard('queues:queue1:default')); $result = $handler->later($queueJob, 60); $this->assertTrue($result); - $this->assertFalse($redis->hExists('queues:queue1::reserved', $queueJob->id)); + $this->assertFalse($redis->hExists('queues:queue1::reserved', (string) $queueJob->id)); $this->assertSame(1, $redis->zCard('queues:queue1:default')); } @@ -150,7 +152,7 @@ public function testFailedAndKeepJob() $redis = self::getPrivateProperty($handler, 'redis'); $this->assertTrue($result); - $this->assertFalse($redis->hExists('queues:queue1::reserved', $queueJob->id)); + $this->assertFalse($redis->hExists('queues:queue1::reserved', (string) $queueJob->id)); $this->assertSame(0, $redis->zCard('queues:queue1:default')); $this->seeInDatabase('queue_jobs_failed', [ @@ -171,7 +173,7 @@ public function testFailedAndDontKeepJob() $redis = self::getPrivateProperty($handler, 'redis'); $this->assertTrue($result); - $this->assertFalse($redis->hExists('queues:queue1::reserved', $queueJob->id)); + $this->assertFalse($redis->hExists('queues:queue1::reserved', (string) $queueJob->id)); $this->assertSame(0, $redis->zCard('queues:queue1:default')); $this->dontSeeInDatabase('queue_jobs_failed', [ @@ -191,7 +193,7 @@ public function testDoneAndKeepJob() $redis = self::getPrivateProperty($handler, 'redis'); $this->assertTrue($result); - $this->assertFalse($redis->hExists('queues:queue1::reserved', $queueJob->id)); + $this->assertFalse($redis->hExists('queues:queue1::reserved', (string) $queueJob->id)); $this->assertSame(1, $redis->lLen('queues:queue1::done')); } @@ -206,7 +208,7 @@ public function testDoneAndDontKeepJob() $result = $handler->done($queueJob, false); $this->assertTrue($result); - $this->assertFalse($redis->hExists('queues:queue1::reserved', $queueJob->id)); + $this->assertFalse($redis->hExists('queues:queue1::reserved', (string) $queueJob->id)); $this->assertSame(0, $redis->lLen('queues:queue1::done')); } From e9dd702fd38de16e94fc4a2fd7d0b4291a2e288c Mon Sep 17 00:00:00 2001 From: michalsn Date: Sun, 17 Dec 2023 20:13:29 +0100 Subject: [PATCH 25/68] fix grammar --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 63c1ecf..f003ec9 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Read the full documentation: https://queue.codeigniter.com ## Contributing -We does accept and encourage contributions from the community in any shape. It doesn't matter +We accept and encourage contributions from the community in any shape. It doesn't matter whether you can code, write documentation, or help find bugs, all contributions are welcome. See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details. From ae68bd8c604aaaf12f66c378b7089206982a9ffc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 14:24:26 +0000 Subject: [PATCH 26/68] Bump actions/setup-python from 4 to 5 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e52cbad..ecc16a2 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v5 with: python-version: 3.x - run: pip install mkdocs-material From b9df900c3cc5bb04a08314aa42514bb0a002e435 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 07:30:54 +0900 Subject: [PATCH 27/68] refactor: replace loose comparisons --- src/Commands/QueueClear.php | 3 ++- src/Commands/QueueForget.php | 3 ++- src/Commands/QueueRetry.php | 3 ++- src/Commands/QueueStop.php | 3 ++- src/Commands/QueueWork.php | 3 ++- src/Handlers/PredisHandler.php | 25 +++++++++++++++++++------ src/Handlers/RedisHandler.php | 9 +++++++-- 7 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/Commands/QueueClear.php b/src/Commands/QueueClear.php index 5d8e175..0080454 100644 --- a/src/Commands/QueueClear.php +++ b/src/Commands/QueueClear.php @@ -50,7 +50,8 @@ class QueueClear extends BaseCommand public function run(array $params) { // Read params - if (! $queue = array_shift($params)) { + $queue = array_shift($params); + if ($queue === null) { CLI::error('The queueName is not specified.'); return EXIT_ERROR; diff --git a/src/Commands/QueueForget.php b/src/Commands/QueueForget.php index 7a02ee3..df55a5d 100644 --- a/src/Commands/QueueForget.php +++ b/src/Commands/QueueForget.php @@ -50,7 +50,8 @@ class QueueForget extends BaseCommand public function run(array $params) { // Read params - if (! $id = array_shift($params)) { + $id = array_shift($params); + if ($id === null) { CLI::error('The ID of the failed job is not specified.'); return EXIT_ERROR; diff --git a/src/Commands/QueueRetry.php b/src/Commands/QueueRetry.php index 6bbb43b..ad993b5 100644 --- a/src/Commands/QueueRetry.php +++ b/src/Commands/QueueRetry.php @@ -59,7 +59,8 @@ class QueueRetry extends BaseCommand public function run(array $params) { // Read params - if (! $id = array_shift($params)) { + $id = array_shift($params); + if ($id === null) { CLI::error('The ID of the failed job is not specified.'); return EXIT_ERROR; diff --git a/src/Commands/QueueStop.php b/src/Commands/QueueStop.php index 91085d1..0becf7a 100644 --- a/src/Commands/QueueStop.php +++ b/src/Commands/QueueStop.php @@ -58,7 +58,8 @@ class QueueStop extends BaseCommand public function run(array $params) { // Read params - if (! $queue = array_shift($params)) { + $queue = array_shift($params); + if ($queue === null) { CLI::error('The queueName is not specified.'); return EXIT_ERROR; diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 7f19bd8..18235c3 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -80,7 +80,8 @@ public function run(array $params) $waiting = false; // Read queue name from params - if (! $queue = array_shift($params)) { + $queue = array_shift($params); + if ($queue === null) { CLI::error('The queueName is not specified.'); return EXIT_ERROR; diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 32f52f8..ea2267c 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -62,15 +62,22 @@ public function pop(string $queue, array $priorities): ?QueueJob $now = Time::now()->timestamp; foreach ($priorities as $priority) { - if ($tasks = $this->predis->zrangebyscore("queues:{$queue}:{$priority}", '-inf', $now, ['LIMIT' => [0, 1]])) { - if ($this->predis->zrem("queues:{$queue}:{$priority}", ...$tasks)) { + $tasks = $this->predis->zrangebyscore( + "queues:{$queue}:{$priority}", + '-inf', + $now, + ['LIMIT' => [0, 1]] + ); + if ($tasks !== []) { + $removed = $this->predis->zrem("queues:{$queue}:{$priority}", ...$tasks); + if ($removed !== 0) { break; } $tasks = []; } } - if (empty($tasks[0])) { + if ($tasks === []) { return null; } @@ -93,7 +100,11 @@ public function later(QueueJob $queueJob, int $seconds): bool $queueJob->status = Status::PENDING->value; $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; - if ($result = $this->predis->zadd("queues:{$queueJob->queue}:{$queueJob->priority}", [json_encode($queueJob) => $queueJob->available_at->timestamp])) { + $result = $this->predis->zadd( + "queues:{$queueJob->queue}:{$queueJob->priority}", + [json_encode($queueJob) => $queueJob->available_at->timestamp] + ); + if ($result !== 0) { $this->predis->hdel("queues:{$queueJob->queue}::reserved", [$queueJob->id]); } @@ -131,14 +142,16 @@ public function done(QueueJob $queueJob, bool $keepJob): bool public function clear(?string $queue = null): bool { if ($queue !== null) { - if ($keys = $this->predis->keys("queues:{$queue}:*")) { + $keys = $this->predis->keys("queues:{$queue}:*"); + if ($keys !== []) { return $this->predis->del($keys) > 0; } return true; } - if ($keys = $this->predis->keys('queues:*')) { + $keys = $this->predis->keys('queues:*'); + if ($keys !== []) { return $this->predis->del($keys) > 0; } diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index 3036893..64f5562 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -89,7 +89,7 @@ public function pop(string $queue, array $priorities): ?QueueJob } } - if (empty($tasks[0])) { + if ($tasks === []) { return null; } @@ -114,7 +114,12 @@ public function later(QueueJob $queueJob, int $seconds): bool $queueJob->status = Status::PENDING->value; $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; - if ($result = (int) $this->redis->zAdd("queues:{$queueJob->queue}:{$queueJob->priority}", $queueJob->available_at->timestamp, json_encode($queueJob))) { + $result = (int) $this->redis->zAdd( + "queues:{$queueJob->queue}:{$queueJob->priority}", + $queueJob->available_at->timestamp, + json_encode($queueJob) + ); + if ($result !== 0) { $this->redis->hDel("queues:{$queueJob->queue}::reserved", (string) $queueJob->id); } From 67f8de3dfd043126825bbff2f9cfe96b59e69da1 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 06:47:32 +0900 Subject: [PATCH 28/68] chore: add declare_strict_types and void_return --- .php-cs-fixer.dist.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 1185b9c..2446c56 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,5 +1,7 @@ exclude('build') ->append([__FILE__]); -$overrides = []; +$overrides = [ + 'declare_strict_types' => true, + 'void_return' => true, +]; $options = [ 'finder' => $finder, From fff9dff18a2db61e7932293a7fe9076f83387056 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 06:48:56 +0900 Subject: [PATCH 29/68] refactor: add declare(strict_types=1) and return void --- src/BaseJob.php | 2 + src/Commands/Generators/JobGenerator.php | 2 + src/Commands/QueueClear.php | 2 + src/Commands/QueueFailed.php | 2 + src/Commands/QueueFlush.php | 2 + src/Commands/QueueForget.php | 2 + src/Commands/QueuePublish.php | 7 ++- src/Commands/QueueRetry.php | 2 + src/Commands/QueueStop.php | 2 + src/Commands/QueueWork.php | 2 + src/Config/Queue.php | 2 + src/Config/Registrar.php | 2 + src/Config/Services.php | 2 + .../2023-10-12-112040_AddQueueTables.php | 6 ++- .../2023-11-05-064053_AddPriorityField.php | 6 ++- src/Entities/QueueJob.php | 2 + src/Entities/QueueJobFailed.php | 2 + src/Enums/Status.php | 2 + src/Exceptions/QueueException.php | 2 + src/Handlers/BaseHandler.php | 2 + src/Handlers/DatabaseHandler.php | 2 + src/Handlers/PredisHandler.php | 2 + src/Handlers/RedisHandler.php | 2 + src/Interfaces/JobInterface.php | 2 + src/Interfaces/QueueInterface.php | 2 + src/Language/en/Queue.php | 2 + src/Models/QueueJobFailedModel.php | 2 + src/Payload.php | 2 + src/Queue.php | 2 + tests/DatabaseHandlerTest.php | 52 ++++++++++--------- tests/PredisHandlerTest.php | 36 +++++++------ tests/QueueTest.php | 8 +-- tests/RedisHandlerTest.php | 34 ++++++------ tests/_support/Config/Queue.php | 2 + .../Seeds/TestDatabaseQueueSeeder.php | 2 + .../Database/Seeds/TestRedisQueueSeeder.php | 2 + tests/_support/Jobs/Failure.php | 2 + tests/_support/Jobs/Success.php | 2 + tests/_support/TestCase.php | 2 + 39 files changed, 143 insertions(+), 70 deletions(-) diff --git a/src/BaseJob.php b/src/BaseJob.php index ad67410..2a632ea 100644 --- a/src/BaseJob.php +++ b/src/BaseJob.php @@ -1,5 +1,7 @@ getNamespace('CodeIgniter\\Queue')[0]; diff --git a/src/Commands/QueueRetry.php b/src/Commands/QueueRetry.php index ad993b5..88eed8d 100644 --- a/src/Commands/QueueRetry.php +++ b/src/Commands/QueueRetry.php @@ -1,5 +1,7 @@ forge->addField([ 'id' => ['type' => 'bigint', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true], @@ -34,7 +36,7 @@ public function up() $this->forge->createTable('queue_jobs_failed', true); } - public function down() + public function down(): void { $this->forge->dropTable('queue_jobs', true); $this->forge->dropTable('queue_jobs_failed', true); diff --git a/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php b/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php index f437963..a4cf410 100644 --- a/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php +++ b/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php @@ -1,5 +1,7 @@ [ @@ -40,7 +42,7 @@ public function up() $this->forge->processIndexes('queue_jobs'); } - public function down() + public function down(): void { // Ugly fix for dropping the correct index $keys = $this->db->getIndexData('queue_jobs'); diff --git a/src/Entities/QueueJob.php b/src/Entities/QueueJob.php index 28fac48..b4c6523 100644 --- a/src/Entities/QueueJob.php +++ b/src/Entities/QueueJob.php @@ -1,5 +1,7 @@ [ 'className' => [ diff --git a/src/Models/QueueJobFailedModel.php b/src/Models/QueueJobFailedModel.php index 2087946..92270f6 100644 --- a/src/Models/QueueJobFailedModel.php +++ b/src/Models/QueueJobFailedModel.php @@ -1,5 +1,7 @@ config = config(QueueConfig::class); } - public function testDatabaseHandler() + public function testDatabaseHandler(): void { $handler = new DatabaseHandler($this->config); $this->assertInstanceOf(DatabaseHandler::class, $handler); } - public function testPriority() + public function testPriority(): void { $handler = new DatabaseHandler($this->config); $handler->setPriority('high'); @@ -45,7 +47,7 @@ public function testPriority() $this->assertSame('high', self::getPrivateProperty($handler, 'priority')); } - public function testPriorityNameException() + public function testPriorityNameException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name should consists only lowercase letters.'); @@ -54,7 +56,7 @@ public function testPriorityNameException() $handler->setPriority('high_:'); } - public function testPriorityNameLengthException() + public function testPriorityNameLengthException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name is too long. It should be no longer than 64 letters.'); @@ -66,7 +68,7 @@ public function testPriorityNameLengthException() /** * @throws ReflectionException */ - public function testPush() + public function testPush(): void { $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); @@ -81,7 +83,7 @@ public function testPush() /** * @throws ReflectionException */ - public function testPushWithPriority() + public function testPushWithPriority(): void { $handler = new DatabaseHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); @@ -94,7 +96,7 @@ public function testPushWithPriority() ]); } - public function testPushAndPopWithPriority() + public function testPushAndPopWithPriority(): void { $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key1' => 'value1']); @@ -129,7 +131,7 @@ public function testPushAndPopWithPriority() /** * @throws ReflectionException */ - public function testPushException() + public function testPushException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This job name is not defined in the $jobHandlers array.'); @@ -141,7 +143,7 @@ public function testPushException() /** * @throws ReflectionException */ - public function testPushWithPriorityException() + public function testPushWithPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This queue has incorrectly defined priority: "invalid" for the queue: "queue".'); @@ -153,7 +155,7 @@ public function testPushWithPriorityException() /** * @throws ReflectionException */ - public function testPushWithIncorrectQueueFormatException() + public function testPushWithIncorrectQueueFormatException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The queue name should consists only lowercase letters or numbers.'); @@ -165,7 +167,7 @@ public function testPushWithIncorrectQueueFormatException() /** * @throws ReflectionException */ - public function testPushWithTooLongQueueNameException() + public function testPushWithTooLongQueueNameException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The queue name is too long. It should be no longer than 64 letters.'); @@ -177,7 +179,7 @@ public function testPushWithTooLongQueueNameException() /** * @throws ReflectionException */ - public function testPop() + public function testPop(): void { $handler = new DatabaseHandler($this->config); $result = $handler->pop('queue1', ['default']); @@ -192,7 +194,7 @@ public function testPop() /** * @throws ReflectionException */ - public function testPopEmpty() + public function testPopEmpty(): void { $handler = new DatabaseHandler($this->config); $result = $handler->pop('queue123', ['default']); @@ -203,7 +205,7 @@ public function testPopEmpty() /** * @throws ReflectionException */ - public function testLater() + public function testLater(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -225,7 +227,7 @@ public function testLater() /** * @throws ReflectionException */ - public function testFailedAndKeepJob() + public function testFailedAndKeepJob(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -244,7 +246,7 @@ public function testFailedAndKeepJob() ]); } - public function testFailedAndDontKeepJob() + public function testFailedAndDontKeepJob(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -266,7 +268,7 @@ public function testFailedAndDontKeepJob() /** * @throws ReflectionException */ - public function testDoneAndKeepJob() + public function testDoneAndKeepJob(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -283,7 +285,7 @@ public function testDoneAndKeepJob() /** * @throws ReflectionException */ - public function testDoneAndDontKeepJob() + public function testDoneAndDontKeepJob(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -296,7 +298,7 @@ public function testDoneAndDontKeepJob() ]); } - public function testClear() + public function testClear(): void { $handler = new DatabaseHandler($this->config); $result = $handler->clear('queue1'); @@ -311,7 +313,7 @@ public function testClear() ]); } - public function testRetry() + public function testRetry(): void { $handler = new DatabaseHandler($this->config); $count = $handler->retry(1, 'queue1'); @@ -328,7 +330,7 @@ public function testRetry() ]); } - public function testForget() + public function testForget(): void { $handler = new DatabaseHandler($this->config); $result = $handler->forget(1); @@ -340,7 +342,7 @@ public function testForget() ]); } - public function testForgetFalse() + public function testForgetFalse(): void { $handler = new DatabaseHandler($this->config); $result = $handler->forget(1111); @@ -351,7 +353,7 @@ public function testForgetFalse() /** * @throws ReflectionException */ - public function testFlush() + public function testFlush(): void { $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -373,7 +375,7 @@ public function testFlush() ]); } - public function testFlushAll() + public function testFlushAll(): void { $handler = new DatabaseHandler($this->config); $handler->flush(null, null); @@ -382,7 +384,7 @@ public function testFlushAll() ]); } - public function testListFailed() + public function testListFailed(): void { $handler = new DatabaseHandler($this->config); $list = $handler->listFailed('queue1'); diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 6cccdfe..ba7a40c 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -1,5 +1,7 @@ config = config(QueueConfig::class); } - public function testPredisHandler() + public function testPredisHandler(): void { $handler = new PredisHandler($this->config); $this->assertInstanceOf(PredisHandler::class, $handler); } - public function testPriority() + public function testPriority(): void { $handler = new PredisHandler($this->config); $handler->setPriority('high'); @@ -44,7 +46,7 @@ public function testPriority() $this->assertSame('high', self::getPrivateProperty($handler, 'priority')); } - public function testPriorityException() + public function testPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name should consists only lowercase letters.'); @@ -56,7 +58,7 @@ public function testPriorityException() /** * @throws ReflectionException */ - public function testPush() + public function testPush(): void { $handler = new PredisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); @@ -75,7 +77,7 @@ public function testPush() /** * @throws ReflectionException */ - public function testPushWithPriority() + public function testPushWithPriority(): void { $handler = new PredisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); @@ -91,7 +93,7 @@ public function testPushWithPriority() $this->assertSame(['key' => 'value'], $queueJob->payload['data']); } - public function testPushException() + public function testPushException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This job name is not defined in the $jobHandlers array.'); @@ -100,7 +102,7 @@ public function testPushException() $handler->push('queue', 'not-exists', ['key' => 'value']); } - public function testPushWithPriorityException() + public function testPushWithPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This queue has incorrectly defined priority: "invalid" for the queue: "queue".'); @@ -112,7 +114,7 @@ public function testPushWithPriorityException() /** * @throws ReflectionException */ - public function testPop() + public function testPop(): void { $handler = new PredisHandler($this->config); $result = $handler->pop('queue1', ['default']); @@ -125,7 +127,7 @@ public function testPop() $this->assertSame(1, $predis->hexists('queues:queue1::reserved', $result->id)); } - public function testPopEmpty() + public function testPopEmpty(): void { $handler = new PredisHandler($this->config); $result = $handler->pop('queue123', ['default']); @@ -136,7 +138,7 @@ public function testPopEmpty() /** * @throws ReflectionException */ - public function testLater() + public function testLater(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -155,7 +157,7 @@ public function testLater() /** * @throws ReflectionException */ - public function testFailedAndKeepJob() + public function testFailedAndKeepJob(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -179,7 +181,7 @@ public function testFailedAndKeepJob() /** * @throws ReflectionException */ - public function testFailedAndDontKeepJob() + public function testFailedAndDontKeepJob(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -203,7 +205,7 @@ public function testFailedAndDontKeepJob() /** * @throws ReflectionException */ - public function testDoneAndKeepJob() + public function testDoneAndKeepJob(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -220,7 +222,7 @@ public function testDoneAndKeepJob() /** * @throws ReflectionException */ - public function testDoneAndDontKeepJob() + public function testDoneAndDontKeepJob(): void { $handler = new PredisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -238,7 +240,7 @@ public function testDoneAndDontKeepJob() /** * @throws ReflectionException */ - public function testClear() + public function testClear(): void { $handler = new PredisHandler($this->config); $result = $handler->clear('queue1'); @@ -255,7 +257,7 @@ public function testClear() /** * @throws ReflectionException */ - public function testClearAll() + public function testClearAll(): void { $handler = new PredisHandler($this->config); $result = $handler->clear(); @@ -272,7 +274,7 @@ public function testClearAll() /** * @throws ReflectionException */ - public function testRetry() + public function testRetry(): void { $handler = new PredisHandler($this->config); $count = $handler->retry(1, 'queue1'); diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 0bd655b..377dc43 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -1,5 +1,7 @@ config = config(QueueConfig::class); } - public function testQueue() + public function testQueue(): void { $queue = new Queue($this->config); $this->assertInstanceOf(Queue::class, $queue); } - public function testQueueException() + public function testQueueException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This queue handler is incorrect.'); @@ -41,7 +43,7 @@ public function testQueueException() $this->assertInstanceOf(Queue::class, $queue); } - public function testQueueInit() + public function testQueueInit(): void { $queue = new Queue($this->config); $this->assertInstanceOf(DatabaseHandler::class, $queue->init()); diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index ddc9f6a..8605b99 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -31,13 +31,13 @@ protected function setUp(): void $this->config = config(QueueConfig::class); } - public function testRedisHandler() + public function testRedisHandler(): void { $handler = new RedisHandler($this->config); $this->assertInstanceOf(RedisHandler::class, $handler); } - public function testPriority() + public function testPriority(): void { $handler = new RedisHandler($this->config); $handler->setPriority('high'); @@ -45,7 +45,7 @@ public function testPriority() $this->assertSame('high', self::getPrivateProperty($handler, 'priority')); } - public function testPriorityException() + public function testPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('The priority name should consists only lowercase letters.'); @@ -54,7 +54,7 @@ public function testPriorityException() $handler->setPriority('high_:'); } - public function testPush() + public function testPush(): void { $handler = new RedisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); @@ -70,7 +70,7 @@ public function testPush() $this->assertSame(['key' => 'value'], $queueJob->payload['data']); } - public function testPushWithPriority() + public function testPushWithPriority(): void { $handler = new RedisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); @@ -86,7 +86,7 @@ public function testPushWithPriority() $this->assertSame(['key' => 'value'], $queueJob->payload['data']); } - public function testPushException() + public function testPushException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This job name is not defined in the $jobHandlers array.'); @@ -95,7 +95,7 @@ public function testPushException() $handler->push('queue', 'not-exists', ['key' => 'value']); } - public function testPushWithPriorityException() + public function testPushWithPriorityException(): void { $this->expectException(QueueException::class); $this->expectExceptionMessage('This queue has incorrectly defined priority: "invalid" for the queue: "queue".'); @@ -104,7 +104,7 @@ public function testPushWithPriorityException() $handler->setPriority('invalid')->push('queue', 'success', ['key' => 'value']); } - public function testPop() + public function testPop(): void { $handler = new RedisHandler($this->config); $result = $handler->pop('queue1', ['default']); @@ -117,7 +117,7 @@ public function testPop() $this->assertTrue($redis->hExists('queues:queue1::reserved', (string) $result->id)); } - public function testPopEmpty() + public function testPopEmpty(): void { $handler = new RedisHandler($this->config); $result = $handler->pop('queue123', ['default']); @@ -125,7 +125,7 @@ public function testPopEmpty() $this->assertNull($result); } - public function testLater() + public function testLater(): void { $handler = new RedisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -141,7 +141,7 @@ public function testLater() $this->assertSame(1, $redis->zCard('queues:queue1:default')); } - public function testFailedAndKeepJob() + public function testFailedAndKeepJob(): void { $handler = new RedisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -162,7 +162,7 @@ public function testFailedAndKeepJob() ]); } - public function testFailedAndDontKeepJob() + public function testFailedAndDontKeepJob(): void { $handler = new RedisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -183,7 +183,7 @@ public function testFailedAndDontKeepJob() ]); } - public function testDoneAndKeepJob() + public function testDoneAndKeepJob(): void { $handler = new RedisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -197,7 +197,7 @@ public function testDoneAndKeepJob() $this->assertSame(1, $redis->lLen('queues:queue1::done')); } - public function testDoneAndDontKeepJob() + public function testDoneAndDontKeepJob(): void { $handler = new RedisHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -212,7 +212,7 @@ public function testDoneAndDontKeepJob() $this->assertSame(0, $redis->lLen('queues:queue1::done')); } - public function testClear() + public function testClear(): void { $handler = new RedisHandler($this->config); $result = $handler->clear('queue1'); @@ -226,7 +226,7 @@ public function testClear() $this->assertTrue($result); } - public function testClearAll() + public function testClearAll(): void { $handler = new RedisHandler($this->config); @@ -240,7 +240,7 @@ public function testClearAll() $this->assertTrue($result); } - public function testRetry() + public function testRetry(): void { $handler = new RedisHandler($this->config); $count = $handler->retry(1, 'queue1'); diff --git a/tests/_support/Config/Queue.php b/tests/_support/Config/Queue.php index d71bee9..24097e2 100644 --- a/tests/_support/Config/Queue.php +++ b/tests/_support/Config/Queue.php @@ -1,5 +1,7 @@ Date: Mon, 18 Dec 2023 06:57:41 +0900 Subject: [PATCH 30/68] chore: composer require --dev phpstan/phpstan-strict-rules --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index e6b709f..59acaa0 100644 --- a/composer.json +++ b/composer.json @@ -18,7 +18,8 @@ "require-dev": { "codeigniter4/devkit": "^1.0", "codeigniter4/framework": "^4.3", - "predis/predis": "^2.0" + "predis/predis": "^2.0", + "phpstan/phpstan-strict-rules": "^1.5" }, "minimum-stability": "dev", "prefer-stable": true, From 82c695ddd2b10d25ddff9cc0fa2756808d1e9187 Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 19:35:34 +0900 Subject: [PATCH 31/68] chore: add strictRules --- phpstan.neon.dist | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index decefa1..fba7c30 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -8,6 +8,8 @@ parameters: - vendor/codeigniter4/framework/system/Test/bootstrap.php excludePaths: ignoreErrors: + - + message: '#Call to method PHPUnit\\Framework\\Assert::assertInstanceOf\(\) with.#' - message: '#Call to deprecated function random_string\(\):#' paths: @@ -33,3 +35,10 @@ parameters: - APP_NAMESPACE - CI_DEBUG - ENVIRONMENT + strictRules: + allRules: false + disallowedLooseComparison: true + booleansInConditions: true + disallowedConstructs: true + matchingInheritedMethodNames: true + From 2c7a6c7d586b08bfbfaf535e3761d7d33d3f504b Mon Sep 17 00:00:00 2001 From: kenjis Date: Mon, 18 Dec 2023 19:41:18 +0900 Subject: [PATCH 32/68] chore: add empty rules --- rector.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rector.php b/rector.php index 6c3c25a..21a53f9 100644 --- a/rector.php +++ b/rector.php @@ -2,6 +2,7 @@ use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector; use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector; +use Rector\CodeQuality\Rector\Empty_\SimplifyEmptyCheckOnEmptyArrayRector; use Rector\CodeQuality\Rector\Expression\InlineIfToExplicitIfRector; use Rector\CodeQuality\Rector\Foreach_\UnusedForeachValueToArrayKeysRector; use Rector\CodeQuality\Rector\FuncCall\ChangeArrayPushToArrayAssignRector; @@ -12,6 +13,7 @@ use Rector\CodeQuality\Rector\If_\ShortenElseIfRector; use Rector\CodeQuality\Rector\If_\SimplifyIfElseToTernaryRector; use Rector\CodeQuality\Rector\If_\SimplifyIfReturnBoolRector; +use Rector\CodeQuality\Rector\Ternary\TernaryEmptyArrayArrayDimFetchToCoalesceRector; use Rector\CodeQuality\Rector\Ternary\UnnecessaryTernaryExpressionRector; use Rector\CodingStyle\Rector\ClassMethod\FuncGetArgsToVariadicParamRector; use Rector\CodingStyle\Rector\ClassMethod\MakeInheritedMethodVisibilitySameAsParentRector; @@ -31,6 +33,8 @@ use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector; use Rector\Set\ValueObject\LevelSetList; use Rector\Set\ValueObject\SetList; +use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector; +use Rector\TypeDeclaration\Rector\Empty_\EmptyOnNullableObjectToInstanceOfRector; use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromAssignsRector; return static function (RectorConfig $rectorConfig): void { @@ -108,6 +112,10 @@ $rectorConfig->rule(FuncGetArgsToVariadicParamRector::class); $rectorConfig->rule(MakeInheritedMethodVisibilitySameAsParentRector::class); $rectorConfig->rule(SimplifyEmptyArrayCheckRector::class); + $rectorConfig->rule(SimplifyEmptyCheckOnEmptyArrayRector::class); + $rectorConfig->rule(TernaryEmptyArrayArrayDimFetchToCoalesceRector::class); + $rectorConfig->rule(EmptyOnNullableObjectToInstanceOfRector::class); + $rectorConfig->rule(DisallowedEmptyRuleFixerRector::class); $rectorConfig ->ruleWithConfiguration(TypedPropertyFromAssignsRector::class, [ /** From 2b197392eb3a8209e6fafb748d9c4ad6c7ddf351 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 19 Dec 2023 09:33:27 +0100 Subject: [PATCH 33/68] fix type casting --- src/Commands/QueueWork.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index ec46eae..1b45902 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -161,7 +161,7 @@ public function run(array $params) CLI::print('Starting a new job: ', 'cyan'); CLI::print($work->payload['job'], 'light_cyan'); CLI::print(', with ID: ', 'cyan'); - CLI::print($work->id, 'light_cyan'); + CLI::print((string) $work->id, 'light_cyan'); $this->handleWork($work, $config, $tries, $retryAfter); From 4378c4d020762023c99c953fd3ef54e712016fc1 Mon Sep 17 00:00:00 2001 From: michalsn Date: Tue, 19 Dec 2023 10:14:33 +0100 Subject: [PATCH 34/68] more casting fixes in commands --- src/Commands/QueueFlush.php | 4 ++++ src/Commands/QueueForget.php | 2 +- src/Commands/QueueRetry.php | 4 +--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Commands/QueueFlush.php b/src/Commands/QueueFlush.php index 47e85c8..8bd8e06 100644 --- a/src/Commands/QueueFlush.php +++ b/src/Commands/QueueFlush.php @@ -56,6 +56,10 @@ public function run(array $params) $hours = $params['hours'] ?? CLI::getOption('hours'); $queue = $params['queue'] ?? CLI::getOption('queue'); + if ($hours !== null) { + $hours = (int) $hours; + } + service('queue')->flush($hours, $queue); if ($hours === null) { diff --git a/src/Commands/QueueForget.php b/src/Commands/QueueForget.php index f5ebdab..b99088d 100644 --- a/src/Commands/QueueForget.php +++ b/src/Commands/QueueForget.php @@ -59,7 +59,7 @@ public function run(array $params) return EXIT_ERROR; } - if (service('queue')->forget($id)) { + if (service('queue')->forget((int) $id)) { CLI::write(sprintf('Failed job with ID %s has been removed.', $id), 'green'); } else { CLI::write(sprintf('Could not find the failed job with ID %s', $id), 'red'); diff --git a/src/Commands/QueueRetry.php b/src/Commands/QueueRetry.php index 88eed8d..f1af6b0 100644 --- a/src/Commands/QueueRetry.php +++ b/src/Commands/QueueRetry.php @@ -68,9 +68,7 @@ public function run(array $params) return EXIT_ERROR; } - if ($id === 'all') { - $id = null; - } + $id = $id === 'all' ? null : (int) $id; $queue = $params['queue'] ?? CLI::getOption('queue'); From b289d142870f46ce870b0b18030c5bdadd3be176 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Thu, 21 Dec 2023 14:26:58 +0100 Subject: [PATCH 35/68] tests: add base tests for commands (#27) * add base tests for commands * fix config load * disable phpstan-strict-rules in rector.php * Revert "disable phpstan-strict-rules in rector.php" This reverts commit 5c5d115e2aae806f320b824fcb0799b6e316907a. * add rector separately * update rector version in the workflow * add suggestions from the the code review --- .github/workflows/rector.yml | 2 +- phpunit.xml.dist | 3 +- rector.php | 7 +- src/Commands/QueueFailed.php | 10 ++- src/Commands/QueuePublish.php | 2 +- src/Commands/QueueStop.php | 2 +- tests/Commands/QueueClearTest.php | 40 +++++++++++ tests/Commands/QueueFailedTest.php | 51 ++++++++++++++ tests/Commands/QueueFlushTest.php | 89 +++++++++++++++++++++++ tests/Commands/QueueForgetTest.php | 62 ++++++++++++++++ tests/Commands/QueuePublishTest.php | 27 +++++++ tests/Commands/QueueRetryTest.php | 62 ++++++++++++++++ tests/Commands/QueueStopTest.php | 40 +++++++++++ tests/Commands/QueueWorkTest.php | 105 ++++++++++++++++++++++++++++ tests/_support/CLITestCase.php | 62 ++++++++++++++++ 15 files changed, 555 insertions(+), 9 deletions(-) create mode 100644 tests/Commands/QueueClearTest.php create mode 100644 tests/Commands/QueueFailedTest.php create mode 100644 tests/Commands/QueueFlushTest.php create mode 100644 tests/Commands/QueueForgetTest.php create mode 100644 tests/Commands/QueuePublishTest.php create mode 100644 tests/Commands/QueueRetryTest.php create mode 100644 tests/Commands/QueueStopTest.php create mode 100644 tests/Commands/QueueWorkTest.php create mode 100644 tests/_support/CLITestCase.php diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index f4482bf..4fc0e1d 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -62,5 +62,5 @@ jobs: - name: Analyze for refactoring run: | - composer global require --dev rector/rector:^0.15.1 + composer global require --dev rector/rector:^0.18.12 rector process --dry-run --no-progress-bar diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 470e815..8b5daca 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -24,7 +24,8 @@ ./src/ - ./src/Commands + ./src/Commands/Generators + ./src/Commands/Utils ./src/Config diff --git a/rector.php b/rector.php index 21a53f9..c8b9919 100644 --- a/rector.php +++ b/rector.php @@ -63,9 +63,10 @@ realpath(getcwd()) . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', ]); - if (is_file(__DIR__ . '/phpstan.neon.dist')) { - $rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon.dist'); - } + $rectorConfig->phpstanConfigs([ + __DIR__ . '/phpstan.neon.dist', + __DIR__ . '/vendor/phpstan/phpstan-strict-rules/rules.neon', + ]); // Set the target version for refactoring $rectorConfig->phpVersion(PhpVersion::PHP_81); diff --git a/src/Commands/QueueFailed.php b/src/Commands/QueueFailed.php index 5a0b791..89c4749 100644 --- a/src/Commands/QueueFailed.php +++ b/src/Commands/QueueFailed.php @@ -56,7 +56,7 @@ public function run(array $params) $queue = $params['queue'] ?? CLI::getOption('queue'); /** @var QueueConfig $config */ - $config = config('queue'); + $config = config('Queue'); $results = service('queue')->listFailed($queue); @@ -64,7 +64,13 @@ public function run(array $params) $tbody = []; foreach ($results as $result) { - $tbody[] = [$result->id, $result->connection, $result->queue, $this->getClassName($result->payload['job'], $config), $result->failed_at]; + $tbody[] = [ + $result->id, + $result->connection, + $result->queue, + $this->getClassName($result->payload['job'], $config), + $result->failed_at, + ]; } CLI::table($tbody, $thead); diff --git a/src/Commands/QueuePublish.php b/src/Commands/QueuePublish.php index 4caddc5..2a7c1d1 100644 --- a/src/Commands/QueuePublish.php +++ b/src/Commands/QueuePublish.php @@ -13,7 +13,7 @@ class QueuePublish extends BaseCommand { protected $group = 'Queue'; protected $name = 'queue:publish'; - protected $description = 'Publish QueueJob config file into the current application.'; + protected $description = 'Publish Queue config file into the current application.'; public function run(array $params): void { diff --git a/src/Commands/QueueStop.php b/src/Commands/QueueStop.php index d9a7e87..27966a2 100644 --- a/src/Commands/QueueStop.php +++ b/src/Commands/QueueStop.php @@ -72,7 +72,7 @@ public function run(array $params) cache()->save($cacheName, $startTime, MINUTE * 10); - CLI::write('QueueJob will be stopped after the current job finish', 'yellow'); + CLI::write('Queue will be stopped after the current job finish', 'yellow'); return EXIT_SUCCESS; } diff --git a/tests/Commands/QueueClearTest.php b/tests/Commands/QueueClearTest.php new file mode 100644 index 0000000..300e3d9 --- /dev/null +++ b/tests/Commands/QueueClearTest.php @@ -0,0 +1,40 @@ +assertNotFalse(command('queue:clear')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeErrorFilter(); + + $this->assertSame('The queueName is not specified.', $output); + } + + public function testRun(): void + { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:clear test')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Queue test has been cleared.', $output); + } +} diff --git a/tests/Commands/QueueFailedTest.php b/tests/Commands/QueueFailedTest.php new file mode 100644 index 0000000..37e61b6 --- /dev/null +++ b/tests/Commands/QueueFailedTest.php @@ -0,0 +1,51 @@ + 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'exception' => 'Exception: Test error', + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:failed')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $expect = <<<'EOT' + +----+------------+-------+----------------------------+---------------------+ + | ID | Connection | Queue | Class | Failed At | + +----+------------+-------+----------------------------+---------------------+ + | 1 | database | test | Tests\Support\Jobs\Failure | 2023-12-19 14:15:16 | + +----+------------+-------+----------------------------+---------------------+ + EOT; + + $this->assertSame($expect, $output); + } +} diff --git a/tests/Commands/QueueFlushTest.php b/tests/Commands/QueueFlushTest.php new file mode 100644 index 0000000..255439b --- /dev/null +++ b/tests/Commands/QueueFlushTest.php @@ -0,0 +1,89 @@ + 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'exception' => 'Exception: Test error', + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:flush')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('All failed jobs has been removed from the queue ', $output); + } + + public function testRunWithQueue(): void + { + Time::setTestNow('2023-12-19 14:15:16'); + + fake(QueueJobFailedModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'exception' => 'Exception: Test error', + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:flush -queue default')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('All failed jobs has been removed from the queue default', $output); + } + + public function testRunWithQueueAndHour(): void + { + Time::setTestNow('2023-12-19 14:15:16'); + + fake(QueueJobFailedModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'exception' => 'Exception: Test error', + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:flush -queue default -hours 2')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('All failed jobs older than 2 hours has been removed from the queue default', $output); + } +} diff --git a/tests/Commands/QueueForgetTest.php b/tests/Commands/QueueForgetTest.php new file mode 100644 index 0000000..710eca1 --- /dev/null +++ b/tests/Commands/QueueForgetTest.php @@ -0,0 +1,62 @@ +assertNotFalse(command('queue:forget')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeErrorFilter(); + + $this->assertSame('The ID of the failed job is not specified.', $output); + } + + public function testRunFailed(): void + { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:forget 123')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Could not find the failed job with ID 123', $output); + } + + public function testRun(): void + { + fake(QueueJobFailedModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'exception' => 'Exception: Test error', + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:forget 1')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Failed job with ID 1 has been removed.', $output); + } +} diff --git a/tests/Commands/QueuePublishTest.php b/tests/Commands/QueuePublishTest.php new file mode 100644 index 0000000..a39d5bd --- /dev/null +++ b/tests/Commands/QueuePublishTest.php @@ -0,0 +1,27 @@ +assertNotFalse(command('queue:publish')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame(' Published! You can customize the configuration by editing the "app/Config/Queue.php" file.', $output); + } +} diff --git a/tests/Commands/QueueRetryTest.php b/tests/Commands/QueueRetryTest.php new file mode 100644 index 0000000..fdeafc6 --- /dev/null +++ b/tests/Commands/QueueRetryTest.php @@ -0,0 +1,62 @@ +assertNotFalse(command('queue:retry')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeErrorFilter(); + + $this->assertSame('The ID of the failed job is not specified.', $output); + } + + public function testRunFailed(): void + { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:retry all -queue test')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('No failed jobs has been restored to the queue test', $output); + } + + public function testRun(): void + { + fake(QueueJobFailedModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'exception' => 'Exception: Test error', + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:retry 1')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('1 failed job(s) has been restored to the queue ', $output); + } +} diff --git a/tests/Commands/QueueStopTest.php b/tests/Commands/QueueStopTest.php new file mode 100644 index 0000000..3324b0f --- /dev/null +++ b/tests/Commands/QueueStopTest.php @@ -0,0 +1,40 @@ +assertNotFalse(command('queue:stop')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeErrorFilter(); + + $this->assertSame('The queueName is not specified.', $output); + } + + public function testRun(): void + { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:stop test')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Queue will be stopped after the current job finish', $output); + } +} diff --git a/tests/Commands/QueueWorkTest.php b/tests/Commands/QueueWorkTest.php new file mode 100644 index 0000000..5c0993f --- /dev/null +++ b/tests/Commands/QueueWorkTest.php @@ -0,0 +1,105 @@ +assertNotFalse(command('queue:work')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeErrorFilter(); + + $this->assertSame('The queueName is not specified.', $output); + } + + public function testRunWithEmptyQueue(): void + { + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test --stop-when-empty')); + $output = $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $expect = <<<'EOT' + Listening for the jobs with the queue: test + + + No job available. Stopping. + EOT; + + $this->assertSame($expect, $output); + } + + public function testRunWithQueueFailed(): void + { + Time::setTestNow('2023-12-19 14:15:16'); + + fake(QueueJobModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => ['job' => 'failure', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'status' => 0, + 'attempts' => 0, + 'available_at' => 1_702_977_074, + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test sleep 1 --stop-when-empty')); + $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Listening for the jobs with the queue: test', $this->getLine(0)); + $this->assertSame('Starting a new job: failure, with ID: 1', $this->getLine(3)); + $this->assertSame('The processing of this job failed', $this->getLine(4)); + $this->assertSame('No job available. Stopping.', $this->getLine(7)); + } + + public function testRunWithQueueSucceed(): void + { + Time::setTestNow('2023-12-19 14:15:16'); + + fake(QueueJobModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => ['job' => 'success', 'data' => ['key' => 'value']], + 'priority' => 'default', + 'status' => 0, + 'attempts' => 0, + 'available_at' => 1_702_977_074, + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test sleep 1 --stop-when-empty')); + $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Listening for the jobs with the queue: test', $this->getLine(0)); + $this->assertSame('Starting a new job: success, with ID: 1', $this->getLine(3)); + $this->assertSame('The processing of this job was successful', $this->getLine(4)); + $this->assertSame('No job available. Stopping.', $this->getLine(7)); + } +} diff --git a/tests/_support/CLITestCase.php b/tests/_support/CLITestCase.php new file mode 100644 index 0000000..9cc05dd --- /dev/null +++ b/tests/_support/CLITestCase.php @@ -0,0 +1,62 @@ +lines = []; + $output = $this->removeColorCodes($output); + $this->lines = explode("\n", $output); + + return $output; + } + + protected function getLine(int $line = 0): ?string + { + return $this->lines[$line] ?? null; + } + + protected function getLines(): string + { + return implode('', $this->lines); + } + + protected function removeColorCodes(string $output): string + { + $colors = $this->getPrivateProperty(CLI::class, 'foreground_colors'); + $colors = array_values(array_map(static fn ($color) => "\033[" . $color . 'm', $colors)); + $colors = array_merge(["\033[0m"], $colors); + + $output = str_replace($colors, '', trim($output)); + + if (is_windows()) { + $output = str_replace("\r\n", "\n", $output); + } + + return $output; + } +} From b67f650862cff7b7ccdd227352f38e9d96c86afe Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 21 Dec 2023 14:29:02 +0100 Subject: [PATCH 36/68] hljs support for navigation.instant --- docs/assets/js/hljs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/assets/js/hljs.js b/docs/assets/js/hljs.js index 6f9098a..56159c4 100644 --- a/docs/assets/js/hljs.js +++ b/docs/assets/js/hljs.js @@ -1,3 +1,3 @@ -document.addEventListener('DOMContentLoaded', (event) => { +window.document$.subscribe(() => { hljs.highlightAll(); }); From 26ecf2f7476ef7cff9eeac1aa17cda7d41d1a1a4 Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 21 Dec 2023 14:57:55 +0100 Subject: [PATCH 37/68] update docs workflow --- .github/workflows/docs.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ecc16a2..0937a92 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,8 +5,9 @@ on: branches: - develop paths: - - 'docs/*' + - 'docs/**/*' - 'mkdocs.yml' + - '.github/workflows/docs.yml' permissions: contents: write From a7b8b2424c91f1c1c32481ff699d26d7bc3b5fbb Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 22 Dec 2023 07:27:25 +0100 Subject: [PATCH 38/68] docs: add site_url --- mkdocs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/mkdocs.yml b/mkdocs.yml index 15e9447..0576a7b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,6 +51,7 @@ extra: link: https://join.slack.com/t/codeigniterchat/shared_invite/zt-244xrrslc-l_I69AJSi5y2a2RVN~xIdQ name: Slack +site_url: https://queue.codeigniter.com/ repo_url: https://github.com/codeigniter4/queue edit_uri: edit/develop/docs/ copyright: Copyright © 2023 CodeIgniter Foundation. From e8561565cc54d3b4d7839b4025c07a0527adfde9 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 22 Dec 2023 07:27:43 +0100 Subject: [PATCH 39/68] docs: remove navigation.instant.prefetch --- mkdocs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/mkdocs.yml b/mkdocs.yml index 0576a7b..feb90ad 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,7 +24,6 @@ theme: name: Switch to light mode features: - navigation.instant - - navigation.instant.prefetch - content.code.copy - navigation.footer - content.action.edit From f15822ce848be4d3dba39686dd6b9459baf614ca Mon Sep 17 00:00:00 2001 From: kenjis Date: Fri, 22 Dec 2023 17:53:29 +0900 Subject: [PATCH 40/68] docs: add missing `.md` --- docs/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index ba3f178..128512a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -21,8 +21,8 @@ Listen for queued jobs. * [Installation](installation.md) * [Configuration](configuration.md) -* [Basic usage](basic-usage) -* [Running queues](running-queues) +* [Basic usage](basic-usage.md) +* [Running queues](running-queues.md) * [Commands](commands.md) * [Troubleshooting](troubleshooting.md) From 4f46997ad6b39fd8669c3410642b1b3c84eef91f Mon Sep 17 00:00:00 2001 From: michalsn Date: Thu, 28 Dec 2023 07:40:54 +0100 Subject: [PATCH 41/68] update phpunit workflow --- .github/workflows/phpunit.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 89829ba..3d5fdb8 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -37,7 +37,7 @@ jobs: if: "!contains(github.event.head_commit.message, '[ci skip]')" strategy: matrix: - php-versions: ['8.1', '8.2'] + php-versions: ['8.1', '8.2', '8.3'] steps: - name: Checkout @@ -91,7 +91,7 @@ jobs: coveralls: needs: [main] name: Coveralls Finished - if: github.repository_owner == 'michalsn' + if: github.repository_owner == 'codeigniter4' runs-on: ubuntu-latest steps: - name: Upload Coveralls results From 04b6b1806dcfbac7362f69cc027784c64e6ff5ba Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 29 Dec 2023 09:18:53 +0100 Subject: [PATCH 42/68] docs: add composer minimum-stability instructions --- docs/installation.md | 25 +++++++++++++++++++++++++ docs/troubleshooting.md | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/docs/installation.md b/docs/installation.md index fd9df7b..0363de1 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -10,6 +10,31 @@ The only thing you have to do is to run this command, and you're ready to go. composer require codeigniter4/queue +#### A composer error occurred? + +If you get the following error: + +```console +Could not find a version of package codeigniter4/queue matching your minimum-stability (stable). +Require it with an explicit version constraint allowing its desired stability. +``` + +1. Run the following commands to change your [minimum-stability](https://getcomposer.org/doc/articles/versions.md#minimum-stability) in your project `composer.json`: + + ```console + composer config minimum-stability dev + composer config prefer-stable true + ``` + +2. Or specify an explicit version: + + ```console + composer require codeigniter4/queue:dev-develop + ``` + + The above specifies `develop` branch. + See + ## Manual Installation In the example below we will assume, that files from this project will be located in `app/ThirdParty/queue` directory. diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 58d91bc..10cd890 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -5,3 +5,7 @@ If you want to assign an object to the queue, please make sure it implements `JsonSerializable` interface. This is how CodeIgniter [Entities](https://codeigniter.com/user_guide/models/entities.html) are handled by default. You may ask, why not just use `serialize` and `unserialize`? There are security reasons that keep us from doing so. These functions are not safe to use with user provided data. + +### I get an error when trying to install via composer. + +Please see these [instructions](installation.md/#a-composer-error-occurred). From 5aa8b3bfcea8d61ff8cc7e949c7b705e24312dd7 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 29 Dec 2023 10:37:36 +0100 Subject: [PATCH 43/68] fix phpstan issues and add more tests --- phpstan.neon.dist | 2 -- src/Commands/Generators/JobGenerator.php | 4 +-- src/Commands/QueueClear.php | 2 +- src/Commands/QueueFailed.php | 2 +- src/Commands/QueueFlush.php | 2 +- src/Commands/QueueForget.php | 2 +- src/Commands/QueueRetry.php | 4 +-- src/Commands/QueueStop.php | 4 +-- src/Commands/QueueWork.php | 4 +-- src/Handlers/DatabaseHandler.php | 4 +-- src/Handlers/PredisHandler.php | 4 +-- src/Handlers/RedisHandler.php | 6 ++-- tests/DatabaseHandlerTest.php | 43 +++++++++++++++++------- tests/_support/CLITestCase.php | 13 ------- tests/_support/TestCase.php | 13 +++++++ 15 files changed, 62 insertions(+), 47 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index fba7c30..924b3e7 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -15,8 +15,6 @@ parameters: paths: - src/Handlers/RedisHandler.php - src/Handlers/PredisHandler.php - - - message: '#Access to an undefined property CodeIgniter\\I18n\\Time::\$timestamp.#' - message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::affectedRows\(\).#' paths: diff --git a/src/Commands/Generators/JobGenerator.php b/src/Commands/Generators/JobGenerator.php index 5e525d4..d26c163 100644 --- a/src/Commands/Generators/JobGenerator.php +++ b/src/Commands/Generators/JobGenerator.php @@ -45,7 +45,7 @@ class JobGenerator extends BaseCommand /** * The Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'name' => 'The job class name.', @@ -54,7 +54,7 @@ class JobGenerator extends BaseCommand /** * The Command's Options * - * @var array + * @var array */ protected $options = [ '--namespace' => 'Set root namespace. Default: "APP_NAMESPACE".', diff --git a/src/Commands/QueueClear.php b/src/Commands/QueueClear.php index cb86539..bad5ee1 100644 --- a/src/Commands/QueueClear.php +++ b/src/Commands/QueueClear.php @@ -40,7 +40,7 @@ class QueueClear extends BaseCommand /** * The Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'queueName' => 'Name of the queue we will work with.', diff --git a/src/Commands/QueueFailed.php b/src/Commands/QueueFailed.php index 89c4749..af793e3 100644 --- a/src/Commands/QueueFailed.php +++ b/src/Commands/QueueFailed.php @@ -41,7 +41,7 @@ class QueueFailed extends BaseCommand /** * The Command's Options * - * @var array + * @var array */ protected $options = [ '-queue' => 'Queue name.', diff --git a/src/Commands/QueueFlush.php b/src/Commands/QueueFlush.php index 8bd8e06..fe9a321 100644 --- a/src/Commands/QueueFlush.php +++ b/src/Commands/QueueFlush.php @@ -40,7 +40,7 @@ class QueueFlush extends BaseCommand /** * The Command's Options * - * @var array + * @var array */ protected $options = [ '-hours' => 'Number of hours.', diff --git a/src/Commands/QueueForget.php b/src/Commands/QueueForget.php index b99088d..dc88695 100644 --- a/src/Commands/QueueForget.php +++ b/src/Commands/QueueForget.php @@ -40,7 +40,7 @@ class QueueForget extends BaseCommand /** * The Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'id' => 'ID of the failed job.', diff --git a/src/Commands/QueueRetry.php b/src/Commands/QueueRetry.php index f1af6b0..ee7759d 100644 --- a/src/Commands/QueueRetry.php +++ b/src/Commands/QueueRetry.php @@ -40,7 +40,7 @@ class QueueRetry extends BaseCommand /** * The Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'id' => 'ID of the failed job or "all" for all failed jobs.', @@ -49,7 +49,7 @@ class QueueRetry extends BaseCommand /** * The Command's Options * - * @var array + * @var array */ protected $options = [ '-queue' => 'Queue name.', diff --git a/src/Commands/QueueStop.php b/src/Commands/QueueStop.php index 27966a2..0c2e2ca 100644 --- a/src/Commands/QueueStop.php +++ b/src/Commands/QueueStop.php @@ -40,7 +40,7 @@ class QueueStop extends BaseCommand /** * The Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'queueName' => 'Name of the queue we will work with.', @@ -49,7 +49,7 @@ class QueueStop extends BaseCommand /** * The Command's Options * - * @var array + * @var array */ protected $options = [ ]; diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 1b45902..4c02bd6 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -44,7 +44,7 @@ class QueueWork extends BaseCommand /** * The Command's Arguments * - * @var array + * @var array */ protected $arguments = [ 'queueName' => 'Name of the queue we will work with.', @@ -53,7 +53,7 @@ class QueueWork extends BaseCommand /** * The Command's Options * - * @var array + * @var array */ protected $options = [ '-sleep' => 'Wait time between the next check for available job when the queue is empty. Default value: 10 (seconds).', diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 8afe7c5..152cb8b 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -39,7 +39,7 @@ public function push(string $queue, string $job, array $data): bool 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, - 'available_at' => Time::now()->timestamp, + 'available_at' => Time::now(), ]); $this->priority = null; @@ -75,7 +75,7 @@ public function pop(string $queue, array $priorities): ?QueueJob public function later(QueueJob $queueJob, int $seconds): bool { $queueJob->status = Status::PENDING->value; - $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; + $queueJob->available_at = Time::now()->addSeconds($seconds); return $this->jobModel->save($queueJob); } diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index c5a33af..f9790c3 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -45,7 +45,7 @@ public function push(string $queue, string $job, array $data): bool 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, - 'available_at' => Time::now()->timestamp, + 'available_at' => Time::now(), ]); $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => Time::now()->timestamp]); @@ -100,7 +100,7 @@ public function pop(string $queue, array $priorities): ?QueueJob public function later(QueueJob $queueJob, int $seconds): bool { $queueJob->status = Status::PENDING->value; - $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; + $queueJob->available_at = Time::now()->addSeconds($seconds); $result = $this->predis->zadd( "queues:{$queueJob->queue}:{$queueJob->priority}", diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index d435523..048b7e3 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -62,7 +62,7 @@ public function push(string $queue, string $job, array $data): bool 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, - 'available_at' => Time::now()->timestamp, + 'available_at' => Time::now(), ]); $result = (int) $this->redis->zAdd("queues:{$queue}:{$this->priority}", Time::now()->timestamp, json_encode($queueJob)); @@ -83,7 +83,7 @@ public function pop(string $queue, array $priorities): ?QueueJob $now = Time::now()->timestamp; foreach ($priorities as $priority) { - if ($tasks = $this->redis->zRangeByScore("queues:{$queue}:{$priority}", '-inf', $now, ['limit' => [0, 1]])) { + if ($tasks = $this->redis->zRangeByScore("queues:{$queue}:{$priority}", '-inf', (string) $now, ['limit' => [0, 1]])) { if ($this->redis->zRem("queues:{$queue}:{$priority}", ...$tasks)) { break; } @@ -114,7 +114,7 @@ public function pop(string $queue, array $priorities): ?QueueJob public function later(QueueJob $queueJob, int $seconds): bool { $queueJob->status = Status::PENDING->value; - $queueJob->available_at = Time::now()->addSeconds($seconds)->timestamp; + $queueJob->available_at = Time::now()->addSeconds($seconds); $result = (int) $this->redis->zAdd( "queues:{$queueJob->queue}:{$queueJob->priority}", diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 62eff81..e2887d6 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -4,6 +4,7 @@ namespace Tests; +use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; use CodeIgniter\Queue\Exceptions\QueueException; @@ -70,13 +71,16 @@ public function testPriorityNameLengthException(): void */ public function testPush(): void { + Time::setTestNow('2023-12-29 14:15:16'); + $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'queue' => 'queue', + 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'available_at' => '1703859316', ]); } @@ -85,36 +89,43 @@ public function testPush(): void */ public function testPushWithPriority(): void { + Time::setTestNow('2023-12-29 14:15:16'); + $handler = new DatabaseHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'priority' => 'high', + 'queue' => 'queue', + 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'priority' => 'high', + 'available_at' => '1703859316', ]); } public function testPushAndPopWithPriority(): void { + Time::setTestNow('2023-12-29 14:15:16'); + $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key1' => 'value1']); $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), - 'priority' => 'low', + 'queue' => 'queue', + 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), + 'priority' => 'low', + 'available_at' => '1703859316', ]); $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), - 'priority' => 'high', + 'queue' => 'queue', + 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), + 'priority' => 'high', + 'available_at' => '1703859316', ]); $result = $handler->pop('queue', ['high', 'low']); @@ -207,6 +218,8 @@ public function testPopEmpty(): void */ public function testLater(): void { + Time::setTestNow('2023-12-29 14:15:16'); + $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -219,8 +232,9 @@ public function testLater(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ - 'id' => 2, - 'status' => Status::PENDING->value, + 'id' => 2, + 'status' => Status::PENDING->value, + 'available_at' => Time::now()->addSeconds(60)->timestamp, ]); } @@ -229,6 +243,8 @@ public function testLater(): void */ public function testFailedAndKeepJob(): void { + Time::setTestNow('2023-12-29 14:15:16'); + $handler = new DatabaseHandler($this->config); $queueJob = $handler->pop('queue1', ['default']); @@ -243,6 +259,7 @@ public function testFailedAndKeepJob(): void 'id' => 2, 'connection' => 'database', 'queue' => 'queue1', + 'failed_at' => '1703859316', ]); } diff --git a/tests/_support/CLITestCase.php b/tests/_support/CLITestCase.php index 9cc05dd..4ec1e68 100644 --- a/tests/_support/CLITestCase.php +++ b/tests/_support/CLITestCase.php @@ -5,9 +5,7 @@ namespace Tests\Support; use CodeIgniter\CLI\CLI; -use CodeIgniter\I18n\Time; use CodeIgniter\Test\ReflectionHelper; -use Exception; abstract class CLITestCase extends TestCase { @@ -15,17 +13,6 @@ abstract class CLITestCase extends TestCase private array $lines = []; - /** - * @throws Exception - */ - protected function tearDown(): void - { - parent::tearDown(); - - // Reset the current time. - Time::setTestNow(); - } - protected function parseOutput(string $output): string { $this->lines = []; diff --git a/tests/_support/TestCase.php b/tests/_support/TestCase.php index 3814462..efed0d2 100644 --- a/tests/_support/TestCase.php +++ b/tests/_support/TestCase.php @@ -4,8 +4,10 @@ namespace Tests\Support; +use CodeIgniter\I18n\Time; use CodeIgniter\Test\CIUnitTestCase; use CodeIgniter\Test\DatabaseTestTrait; +use Exception; abstract class TestCase extends CIUnitTestCase { @@ -19,4 +21,15 @@ protected function setUp(): void parent::setUp(); } + + /** + * @throws Exception + */ + protected function tearDown(): void + { + parent::tearDown(); + + // Reset the current time. + Time::setTestNow(); + } } From d9be80eec0dc6b72dddcda362503a463b92ef606 Mon Sep 17 00:00:00 2001 From: michalsn Date: Fri, 29 Dec 2023 09:33:54 +0100 Subject: [PATCH 44/68] add license info to the files --- .php-cs-fixer.dist.php | 15 ++++++++++++++- src/BaseJob.php | 9 +++++++++ src/Commands/Generators/JobGenerator.php | 9 +++++++++ src/Commands/QueueClear.php | 9 +++++++++ src/Commands/QueueFailed.php | 9 +++++++++ src/Commands/QueueFlush.php | 9 +++++++++ src/Commands/QueueForget.php | 9 +++++++++ src/Commands/QueuePublish.php | 9 +++++++++ src/Commands/QueueRetry.php | 9 +++++++++ src/Commands/QueueStop.php | 9 +++++++++ src/Commands/QueueWork.php | 9 +++++++++ src/Config/Queue.php | 9 +++++++++ src/Config/Registrar.php | 9 +++++++++ src/Config/Services.php | 9 +++++++++ .../2023-10-12-112040_AddQueueTables.php | 9 +++++++++ .../2023-11-05-064053_AddPriorityField.php | 9 +++++++++ src/Entities/QueueJob.php | 9 +++++++++ src/Entities/QueueJobFailed.php | 9 +++++++++ src/Enums/Status.php | 9 +++++++++ src/Exceptions/QueueException.php | 9 +++++++++ src/Handlers/BaseHandler.php | 9 +++++++++ src/Handlers/DatabaseHandler.php | 9 +++++++++ src/Handlers/PredisHandler.php | 9 +++++++++ src/Handlers/RedisHandler.php | 9 +++++++++ src/Interfaces/JobInterface.php | 9 +++++++++ src/Interfaces/QueueInterface.php | 9 +++++++++ src/Language/en/Queue.php | 9 +++++++++ src/Models/QueueJobFailedModel.php | 9 +++++++++ src/Models/QueueJobModel.php | 9 +++++++++ src/Payload.php | 9 +++++++++ src/Queue.php | 9 +++++++++ tests/Commands/QueueClearTest.php | 9 +++++++++ tests/Commands/QueueFailedTest.php | 9 +++++++++ tests/Commands/QueueFlushTest.php | 9 +++++++++ tests/Commands/QueueForgetTest.php | 9 +++++++++ tests/Commands/QueuePublishTest.php | 9 +++++++++ tests/Commands/QueueRetryTest.php | 9 +++++++++ tests/Commands/QueueStopTest.php | 9 +++++++++ tests/Commands/QueueWorkTest.php | 9 +++++++++ tests/DatabaseHandlerTest.php | 9 +++++++++ tests/PredisHandlerTest.php | 9 +++++++++ tests/QueueTest.php | 9 +++++++++ tests/RedisHandlerTest.php | 9 +++++++++ tests/_support/CLITestCase.php | 9 +++++++++ tests/_support/Config/Queue.php | 9 +++++++++ .../Database/Seeds/TestDatabaseQueueSeeder.php | 9 +++++++++ .../Database/Seeds/TestRedisQueueSeeder.php | 9 +++++++++ tests/_support/Jobs/Failure.php | 9 +++++++++ tests/_support/Jobs/Success.php | 9 +++++++++ tests/_support/TestCase.php | 9 +++++++++ 50 files changed, 455 insertions(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 2446c56..f82bdbf 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + use CodeIgniter\CodingStandard\CodeIgniter4; use Nexus\CsConfig\Factory; use PhpCsFixer\Finder; @@ -25,4 +34,8 @@ 'cacheFile' => 'build/.php-cs-fixer.cache', ]; -return Factory::create(new CodeIgniter4(), $overrides, $options)->forProjects(); +return Factory::create(new CodeIgniter4(), $overrides, $options)->forLibrary( + 'CodeIgniter Queue', + 'CodeIgniter Foundation', + 'admin@codeigniter.com' +); diff --git a/src/BaseJob.php b/src/BaseJob.php index 2a632ea..0538783 100644 --- a/src/BaseJob.php +++ b/src/BaseJob.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue; abstract class BaseJob diff --git a/src/Commands/Generators/JobGenerator.php b/src/Commands/Generators/JobGenerator.php index d26c163..4a067e2 100644 --- a/src/Commands/Generators/JobGenerator.php +++ b/src/Commands/Generators/JobGenerator.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands\Generators; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueClear.php b/src/Commands/QueueClear.php index bad5ee1..c57ba24 100644 --- a/src/Commands/QueueClear.php +++ b/src/Commands/QueueClear.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueFailed.php b/src/Commands/QueueFailed.php index af793e3..9d7d2a1 100644 --- a/src/Commands/QueueFailed.php +++ b/src/Commands/QueueFailed.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueFlush.php b/src/Commands/QueueFlush.php index fe9a321..446dc11 100644 --- a/src/Commands/QueueFlush.php +++ b/src/Commands/QueueFlush.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueForget.php b/src/Commands/QueueForget.php index dc88695..9f930fb 100644 --- a/src/Commands/QueueForget.php +++ b/src/Commands/QueueForget.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueuePublish.php b/src/Commands/QueuePublish.php index 2a7c1d1..6e4e7bb 100644 --- a/src/Commands/QueuePublish.php +++ b/src/Commands/QueuePublish.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueRetry.php b/src/Commands/QueueRetry.php index ee7759d..c269d53 100644 --- a/src/Commands/QueueRetry.php +++ b/src/Commands/QueueRetry.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueStop.php b/src/Commands/QueueStop.php index 0c2e2ca..219db56 100644 --- a/src/Commands/QueueStop.php +++ b/src/Commands/QueueStop.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 4c02bd6..391917d 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Commands; use CodeIgniter\CLI\BaseCommand; diff --git a/src/Config/Queue.php b/src/Config/Queue.php index ab9e2e5..8380e69 100644 --- a/src/Config/Queue.php +++ b/src/Config/Queue.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Config; use CodeIgniter\Config\BaseConfig; diff --git a/src/Config/Registrar.php b/src/Config/Registrar.php index 0924f94..634837b 100644 --- a/src/Config/Registrar.php +++ b/src/Config/Registrar.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Config; class Registrar diff --git a/src/Config/Services.php b/src/Config/Services.php index 948e042..088a2a6 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Config; use CodeIgniter\Config\BaseService; diff --git a/src/Database/Migrations/2023-10-12-112040_AddQueueTables.php b/src/Database/Migrations/2023-10-12-112040_AddQueueTables.php index 3c1bf93..e593047 100644 --- a/src/Database/Migrations/2023-10-12-112040_AddQueueTables.php +++ b/src/Database/Migrations/2023-10-12-112040_AddQueueTables.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Database\Migrations; use CodeIgniter\Database\Migration; diff --git a/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php b/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php index a4cf410..82089b0 100644 --- a/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php +++ b/src/Database/Migrations/2023-11-05-064053_AddPriorityField.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Database\Migrations; use CodeIgniter\Database\BaseConnection; diff --git a/src/Entities/QueueJob.php b/src/Entities/QueueJob.php index b4c6523..670a9f8 100644 --- a/src/Entities/QueueJob.php +++ b/src/Entities/QueueJob.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Entities; use CodeIgniter\Entity\Entity; diff --git a/src/Entities/QueueJobFailed.php b/src/Entities/QueueJobFailed.php index 8d3e7c6..6b296d9 100644 --- a/src/Entities/QueueJobFailed.php +++ b/src/Entities/QueueJobFailed.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Entities; use CodeIgniter\Entity\Entity; diff --git a/src/Enums/Status.php b/src/Enums/Status.php index ddc05f9..eaacfc9 100644 --- a/src/Enums/Status.php +++ b/src/Enums/Status.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Enums; enum Status: int diff --git a/src/Exceptions/QueueException.php b/src/Exceptions/QueueException.php index d7db38a..7e3db3d 100644 --- a/src/Exceptions/QueueException.php +++ b/src/Exceptions/QueueException.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Exceptions; use RuntimeException; diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 82d55c8..42547d1 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Handlers; use CodeIgniter\I18n\Time; diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 152cb8b..6c7d949 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Handlers; use CodeIgniter\I18n\Time; diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index f9790c3..0819d0f 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Handlers; use CodeIgniter\Exceptions\CriticalError; diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index 048b7e3..953cd6e 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Handlers; use CodeIgniter\Exceptions\CriticalError; diff --git a/src/Interfaces/JobInterface.php b/src/Interfaces/JobInterface.php index cde2842..85bed8d 100644 --- a/src/Interfaces/JobInterface.php +++ b/src/Interfaces/JobInterface.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Interfaces; interface JobInterface diff --git a/src/Interfaces/QueueInterface.php b/src/Interfaces/QueueInterface.php index bddbd15..30ab6c9 100644 --- a/src/Interfaces/QueueInterface.php +++ b/src/Interfaces/QueueInterface.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Interfaces; use CodeIgniter\Queue\Entities\QueueJob; diff --git a/src/Language/en/Queue.php b/src/Language/en/Queue.php index e864b84..9989f76 100644 --- a/src/Language/en/Queue.php +++ b/src/Language/en/Queue.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + return [ 'generator' => [ 'className' => [ diff --git a/src/Models/QueueJobFailedModel.php b/src/Models/QueueJobFailedModel.php index 92270f6..0574da6 100644 --- a/src/Models/QueueJobFailedModel.php +++ b/src/Models/QueueJobFailedModel.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Models; use CodeIgniter\Model; diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 14747de..4f77a52 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue\Models; use CodeIgniter\Database\BaseBuilder; diff --git a/src/Payload.php b/src/Payload.php index 72e18ed..9937fa0 100644 --- a/src/Payload.php +++ b/src/Payload.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue; use JsonSerializable; diff --git a/src/Queue.php b/src/Queue.php index 5dbbe1c..6819586 100644 --- a/src/Queue.php +++ b/src/Queue.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace CodeIgniter\Queue; use CodeIgniter\Queue\Config\Queue as QueueConfig; diff --git a/tests/Commands/QueueClearTest.php b/tests/Commands/QueueClearTest.php index 300e3d9..6024837 100644 --- a/tests/Commands/QueueClearTest.php +++ b/tests/Commands/QueueClearTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Test\Filters\CITestStreamFilter; diff --git a/tests/Commands/QueueFailedTest.php b/tests/Commands/QueueFailedTest.php index 37e61b6..fed02df 100644 --- a/tests/Commands/QueueFailedTest.php +++ b/tests/Commands/QueueFailedTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\I18n\Time; diff --git a/tests/Commands/QueueFlushTest.php b/tests/Commands/QueueFlushTest.php index 255439b..a6395f2 100644 --- a/tests/Commands/QueueFlushTest.php +++ b/tests/Commands/QueueFlushTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\I18n\Time; diff --git a/tests/Commands/QueueForgetTest.php b/tests/Commands/QueueForgetTest.php index 710eca1..388cadc 100644 --- a/tests/Commands/QueueForgetTest.php +++ b/tests/Commands/QueueForgetTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Queue\Models\QueueJobFailedModel; diff --git a/tests/Commands/QueuePublishTest.php b/tests/Commands/QueuePublishTest.php index a39d5bd..dd456ea 100644 --- a/tests/Commands/QueuePublishTest.php +++ b/tests/Commands/QueuePublishTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Test\Filters\CITestStreamFilter; diff --git a/tests/Commands/QueueRetryTest.php b/tests/Commands/QueueRetryTest.php index fdeafc6..fe0da19 100644 --- a/tests/Commands/QueueRetryTest.php +++ b/tests/Commands/QueueRetryTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Queue\Models\QueueJobFailedModel; diff --git a/tests/Commands/QueueStopTest.php b/tests/Commands/QueueStopTest.php index 3324b0f..94e2631 100644 --- a/tests/Commands/QueueStopTest.php +++ b/tests/Commands/QueueStopTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\Test\Filters\CITestStreamFilter; diff --git a/tests/Commands/QueueWorkTest.php b/tests/Commands/QueueWorkTest.php index 5c0993f..8bbb294 100644 --- a/tests/Commands/QueueWorkTest.php +++ b/tests/Commands/QueueWorkTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Commands; use CodeIgniter\I18n\Time; diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index e2887d6..33fd4af 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests; use CodeIgniter\I18n\Time; diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index ba7a40c..471a273 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace ThirdParty\queue\tests; use CodeIgniter\I18n\Time; diff --git a/tests/QueueTest.php b/tests/QueueTest.php index 377dc43..7daa4d4 100644 --- a/tests/QueueTest.php +++ b/tests/QueueTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests; use CodeIgniter\Queue\Exceptions\QueueException; diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index 8605b99..daab676 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests; use CodeIgniter\I18n\Time; diff --git a/tests/_support/CLITestCase.php b/tests/_support/CLITestCase.php index 4ec1e68..f0d405d 100644 --- a/tests/_support/CLITestCase.php +++ b/tests/_support/CLITestCase.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support; use CodeIgniter\CLI\CLI; diff --git a/tests/_support/Config/Queue.php b/tests/_support/Config/Queue.php index 24097e2..a7a3a48 100644 --- a/tests/_support/Config/Queue.php +++ b/tests/_support/Config/Queue.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support\Config; use CodeIgniter\Queue\Config\Queue as BaseQueue; diff --git a/tests/_support/Database/Seeds/TestDatabaseQueueSeeder.php b/tests/_support/Database/Seeds/TestDatabaseQueueSeeder.php index 8bc9e14..1b04659 100644 --- a/tests/_support/Database/Seeds/TestDatabaseQueueSeeder.php +++ b/tests/_support/Database/Seeds/TestDatabaseQueueSeeder.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support\Database\Seeds; use CodeIgniter\Database\Seeder; diff --git a/tests/_support/Database/Seeds/TestRedisQueueSeeder.php b/tests/_support/Database/Seeds/TestRedisQueueSeeder.php index ec7debb..4fe4233 100644 --- a/tests/_support/Database/Seeds/TestRedisQueueSeeder.php +++ b/tests/_support/Database/Seeds/TestRedisQueueSeeder.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support\Database\Seeds; use CodeIgniter\Database\Seeder; diff --git a/tests/_support/Jobs/Failure.php b/tests/_support/Jobs/Failure.php index f3a6699..7cad334 100644 --- a/tests/_support/Jobs/Failure.php +++ b/tests/_support/Jobs/Failure.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support\Jobs; use CodeIgniter\Queue\BaseJob; diff --git a/tests/_support/Jobs/Success.php b/tests/_support/Jobs/Success.php index 21a3fb9..e32f192 100644 --- a/tests/_support/Jobs/Success.php +++ b/tests/_support/Jobs/Success.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support\Jobs; use CodeIgniter\Queue\BaseJob; diff --git a/tests/_support/TestCase.php b/tests/_support/TestCase.php index efed0d2..0b956db 100644 --- a/tests/_support/TestCase.php +++ b/tests/_support/TestCase.php @@ -2,6 +2,15 @@ declare(strict_types=1); +/** + * This file is part of CodeIgniter Queue. + * + * (c) CodeIgniter Foundation + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + namespace Tests\Support; use CodeIgniter\I18n\Time; From 11e9a37524f7307bbee6069088524a25b7917543 Mon Sep 17 00:00:00 2001 From: michalsn Date: Sat, 6 Jan 2024 23:04:55 +0100 Subject: [PATCH 45/68] fix: connection name for used handler --- src/Handlers/BaseHandler.php | 4 +++- src/Handlers/DatabaseHandler.php | 8 ++++++++ src/Handlers/PredisHandler.php | 8 ++++++++ src/Handlers/RedisHandler.php | 8 ++++++++ tests/PredisHandlerTest.php | 4 ++-- tests/RedisHandlerTest.php | 4 ++-- 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 42547d1..6e29e45 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -30,6 +30,8 @@ abstract class BaseHandler protected QueueConfig $config; protected ?string $priority = null; + abstract public function name(): string; + abstract public function push(string $queue, string $job, array $data): bool; abstract public function pop(string $queue, array $priorities): ?QueueJob; @@ -144,7 +146,7 @@ protected function logFailed(QueueJob $queueJob, Throwable $err): bool "file: {$err->getFile()}:{$err->getLine()}"; $queueJobFailed = new QueueJobFailed([ - 'connection' => 'database', + 'connection' => $this->name(), 'queue' => $queueJob->queue, 'payload' => $queueJob->payload, 'priority' => $queueJob->priority, diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 6c7d949..5c11279 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -33,6 +33,14 @@ public function __construct(protected QueueConfig $config) $this->jobModel = model(QueueJobModel::class, true, $connection); } + /** + * Name of the handler. + */ + public function name(): string + { + return 'database'; + } + /** * Add job to the queue. * diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 0819d0f..c06c9d3 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -38,6 +38,14 @@ public function __construct(protected QueueConfig $config) } } + /** + * Name of the handler. + */ + public function name(): string + { + return 'predis'; + } + /** * Add job to the queue. */ diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index 953cd6e..6d4e537 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -53,6 +53,14 @@ public function __construct(protected QueueConfig $config) } } + /** + * Name of the handler. + */ + public function name(): string + { + return 'redis'; + } + /** * Add job to the queue. * diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 471a273..4470295 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -182,7 +182,7 @@ public function testFailedAndKeepJob(): void $this->seeInDatabase('queue_jobs_failed', [ 'id' => 2, - 'connection' => 'database', + 'connection' => 'predis', 'queue' => 'queue1', ]); } @@ -206,7 +206,7 @@ public function testFailedAndDontKeepJob(): void $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 2, - 'connection' => 'database', + 'connection' => 'predis', 'queue' => 'queue1', ]); } diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index daab676..e691197 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -166,7 +166,7 @@ public function testFailedAndKeepJob(): void $this->seeInDatabase('queue_jobs_failed', [ 'id' => 2, - 'connection' => 'database', + 'connection' => 'redis', 'queue' => 'queue1', ]); } @@ -187,7 +187,7 @@ public function testFailedAndDontKeepJob(): void $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 2, - 'connection' => 'database', + 'connection' => 'redis', 'queue' => 'queue1', ]); } From 493cb2fd366c5f5cb9031333310cc35559826753 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 3 Feb 2024 18:47:15 +0900 Subject: [PATCH 46/68] chore: update rector.php from devkit --- rector.php | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/rector.php b/rector.php index c8b9919..5480c51 100644 --- a/rector.php +++ b/rector.php @@ -1,5 +1,7 @@ sets([ @@ -80,9 +84,11 @@ JsonThrowOnErrorRector::class, StringifyStrNeedlesRector::class, + YieldDataProviderRector::class, // Note: requires php 8 RemoveUnusedPromotedPropertyRector::class, + AnnotationWithValueToAttributeRector::class, // May load view files directly when detecting classes StringClassNameToClassConstantRector::class, @@ -120,7 +126,9 @@ $rectorConfig ->ruleWithConfiguration(TypedPropertyFromAssignsRector::class, [ /** - * The INLINE_PUBLIC value is default to false to avoid BC break, if you use for libraries and want to preserve BC break, you don't need to configure it, as it included in LevelSetList::UP_TO_PHP_74 + * The INLINE_PUBLIC value is default to false to avoid BC break, + * if you use for libraries and want to preserve BC break, you don't + * need to configure it, as it included in LevelSetList::UP_TO_PHP_74 * Set to true for projects that allow BC break */ TypedPropertyFromAssignsRector::INLINE_PUBLIC => false, From 85d0ed0b95c99ba09b8a8c07092da24053b79d30 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 3 Feb 2024 18:58:07 +0900 Subject: [PATCH 47/68] chore: update .github/workflows/rector.yml --- .github/workflows/rector.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 4fc0e1d..11d95a3 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -62,5 +62,5 @@ jobs: - name: Analyze for refactoring run: | - composer global require --dev rector/rector:^0.18.12 + composer global require --dev rector/rector:^0.19.5 rector process --dry-run --no-progress-bar From 492a5ea48b1574f02c5100758dbe5c987192f4c9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 3 Feb 2024 10:04:30 +0000 Subject: [PATCH 48/68] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/deptrac.yml | 4 ++-- .github/workflows/phpcsfixer.yml | 2 +- .github/workflows/phpstan.yml | 4 ++-- .github/workflows/phpunit.yml | 2 +- .github/workflows/psalm.yml | 4 ++-- .github/workflows/rector.yml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index 2021f8d..c735ed2 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -42,7 +42,7 @@ jobs: run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} @@ -52,7 +52,7 @@ jobs: run: mkdir -p build/ - name: Cache Deptrac results - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: build key: ${{ runner.os }}-deptrac-${{ github.sha }} diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml index 59c867c..9ac8b7f 100644 --- a/.github/workflows/phpcsfixer.yml +++ b/.github/workflows/phpcsfixer.yml @@ -37,7 +37,7 @@ jobs: run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml index 9433d3a..ff29de7 100644 --- a/.github/workflows/phpstan.yml +++ b/.github/workflows/phpstan.yml @@ -46,7 +46,7 @@ jobs: run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} @@ -56,7 +56,7 @@ jobs: run: mkdir -p build/phpstan - name: Cache PHPStan results - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: build/phpstan key: ${{ runner.os }}-phpstan-${{ github.sha }} diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index 3d5fdb8..d5b141d 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -57,7 +57,7 @@ jobs: run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml index b28b1fc..1d3c7cb 100644 --- a/.github/workflows/psalm.yml +++ b/.github/workflows/psalm.yml @@ -42,7 +42,7 @@ jobs: run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} @@ -52,7 +52,7 @@ jobs: run: mkdir -p build/psalm - name: Cache Psalm results - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: build/psalm key: ${{ runner.os }}-psalm-${{ github.sha }} diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 11d95a3..7530d1a 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -46,7 +46,7 @@ jobs: run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - name: Cache composer dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.COMPOSER_CACHE_FILES_DIR }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} From 3feb7cecf86a89000b66bd2ed550985645c8cde0 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Mar 2024 08:02:50 +0900 Subject: [PATCH 49/68] docs: add MySQL/MariaDB versions in Requirements --- docs/index.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 128512a..3bce17e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,8 +14,12 @@ Listen for queued jobs. ### Requirements -![PHP](https://img.shields.io/badge/PHP-%5E8.1-red) -![CodeIgniter](https://img.shields.io/badge/CodeIgniter-%5E4.3-red) +- PHP 8.1+ +- CodeIgniter 4.3+ + +If you use `database` handler: +- MySQL 8.0.1+ +- MariaDB 10.6+ ### Table of Contents From 54a2ecfd8f691dffb45c847046b797ecb8936919 Mon Sep 17 00:00:00 2001 From: kenjis Date: Sat, 16 Mar 2024 16:28:22 +0900 Subject: [PATCH 50/68] docs: add supproted DBs Co-authored-by: Michal Sniatala --- docs/index.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.md b/docs/index.md index 3bce17e..ec73024 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,9 @@ Listen for queued jobs. If you use `database` handler: - MySQL 8.0.1+ - MariaDB 10.6+ +- PostgreSQL 9.5+ +- SQL Server 2012+ +- Oracle 12.1+ ### Table of Contents From 4adad8821da29ff2d1cedd2515f11c7a993940de Mon Sep 17 00:00:00 2001 From: kenjis Date: Sun, 17 Mar 2024 16:33:51 +0900 Subject: [PATCH 51/68] docs: add SQLite3 Co-authored-by: Michal Sniatala --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index ec73024..b129521 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,6 +23,7 @@ If you use `database` handler: - PostgreSQL 9.5+ - SQL Server 2012+ - Oracle 12.1+ +- SQLite3 ### Table of Contents From 11d11640ca351d634223161d60d1497615add713 Mon Sep 17 00:00:00 2001 From: kenjis Date: Wed, 3 Apr 2024 05:47:13 +0900 Subject: [PATCH 52/68] docs: fix markdown for mkdocs --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index b129521..7f835b6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -18,6 +18,7 @@ Listen for queued jobs. - CodeIgniter 4.3+ If you use `database` handler: + - MySQL 8.0.1+ - MariaDB 10.6+ - PostgreSQL 9.5+ From 7cccd8e22130d977023f001de5cc7e25f751494f Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Wed, 18 Sep 2024 15:45:54 +0200 Subject: [PATCH 53/68] Update to PHPUnit 10 (#46) * update to phpunit 10 * fix phpunit workflow * update deptrac workflow * rector workflow update --- .github/workflows/deptrac.yml | 55 ++-------------------------------- .github/workflows/phpunit.yml | 2 +- .github/workflows/rector.yml | 15 ++++++---- .gitignore | 2 +- composer.json | 5 +++- phpunit.xml.dist | 56 ++++++++++++----------------------- rector.php | 2 -- 7 files changed, 37 insertions(+), 100 deletions(-) diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index c735ed2..ebaf4df 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -19,56 +19,5 @@ on: - '.github/workflows/deptrac.yml' jobs: - build: - name: Dependency Tracing - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.1' - tools: phive - extensions: intl, json, mbstring, xml - coverage: none - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Get composer cache directory - run: echo "COMPOSER_CACHE_FILES_DIR=$(composer config cache-files-dir)" >> $GITHUB_ENV - - - name: Cache composer dependencies - uses: actions/cache@v4 - with: - path: ${{ env.COMPOSER_CACHE_FILES_DIR }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Deptrac cache directory - run: mkdir -p build/ - - - name: Cache Deptrac results - uses: actions/cache@v4 - with: - path: build - key: ${{ runner.os }}-deptrac-${{ github.sha }} - restore-keys: ${{ runner.os }}-deptrac- - - - name: Install dependencies - run: | - if [ -f composer.lock ]; then - composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader - else - composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - fi - - - name: Trace dependencies - run: | - sudo phive --no-progress install --global --trust-gpg-keys B8F640134AB1782E,A98E898BB53EB748 qossmic/deptrac - deptrac analyze --cache-file=build/deptrac.cache - env: - GITHUB_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + deptrac: + uses: codeigniter4/.github/.github/workflows/deptrac.yml@main diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index d5b141d..f2b9a10 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -72,7 +72,7 @@ jobs: fi - name: Test with PHPUnit - run: vendor/bin/phpunit --verbose --coverage-text + run: vendor/bin/phpunit --coverage-text env: TERM: xterm-256color TACHYCARDIA_MONITOR_GA: enabled diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml index 7530d1a..cab67ce 100644 --- a/.github/workflows/rector.yml +++ b/.github/workflows/rector.yml @@ -18,15 +18,22 @@ on: - 'rector.php' - '.github/workflows/rector.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + jobs: build: name: PHP ${{ matrix.php-versions }} Rector Analysis runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" + if: (! contains(github.event.head_commit.message, '[ci skip]')) strategy: fail-fast: false matrix: - php-versions: ['8.1', '8.2'] + php-versions: ['8.1', '8.3'] steps: - name: Checkout @@ -61,6 +68,4 @@ jobs: fi - name: Analyze for refactoring - run: | - composer global require --dev rector/rector:^0.19.5 - rector process --dry-run --no-progress-bar + run: vendor/bin/rector process --dry-run --no-progress-bar diff --git a/.gitignore b/.gitignore index 74c6d0c..8a589c5 100644 --- a/.gitignore +++ b/.gitignore @@ -126,5 +126,5 @@ nb-configuration.xml /results/ /phpunit*.xml /.phpunit.*.cache - +/.phpunit.cache /.php-cs-fixer.php diff --git a/composer.json b/composer.json index 59acaa0..2c843ff 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,10 @@ "autoload": { "psr-4": { "CodeIgniter\\Queue\\": "src" - } + }, + "exclude-from-classmap": [ + "**/Database/Migrations/**" + ] }, "autoload-dev": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8b5daca..927d60a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,15 +1,10 @@ + cacheDirectory=".phpunit.cache" + beStrictAboutCoverageMetadata="true"> - - - ./src/ - - - ./src/Commands/Generators - ./src/Commands/Utils - ./src/Config - + @@ -44,27 +32,11 @@ - - - - - 0.50 - - - 30 - - - 2 - - - true - - - true - - - - + + + + + @@ -100,4 +72,14 @@ --> + + + ./src/ + + + ./src/Commands/Generators + ./src/Commands/Utils + ./src/Config + + diff --git a/rector.php b/rector.php index 5480c51..68f6940 100644 --- a/rector.php +++ b/rector.php @@ -27,7 +27,6 @@ use Rector\EarlyReturn\Rector\If_\RemoveAlwaysElseRector; use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; -use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector; use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector; use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector; @@ -82,7 +81,6 @@ $rectorConfig->skip([ __DIR__ . '/app/Views', - JsonThrowOnErrorRector::class, StringifyStrNeedlesRector::class, YieldDataProviderRector::class, From e0a704364f9d592a2336f36c416f807c42ad47bd Mon Sep 17 00:00:00 2001 From: michalsn Date: Wed, 18 Sep 2024 09:29:04 +0200 Subject: [PATCH 54/68] add a new condition for the reconnect call --- src/Models/QueueJobModel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 4f77a52..a086fad 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -51,7 +51,7 @@ public function getFromQueue(string $name, array $priority): ?QueueJob { // For SQLite3 memory database this will cause problems // so check if we're not in the testing environment first. - if ($this->db->database !== ':memory:') { + if ($this->db->database !== ':memory:' && $this->db->connID !== false) { // Make sure we still have the connection $this->db->reconnect(); } From b1bac16e0dce9cf13d1c64b6a84b28bcc43185ef Mon Sep 17 00:00:00 2001 From: michalsn Date: Wed, 18 Sep 2024 15:42:56 +0200 Subject: [PATCH 55/68] add skipLocked config --- docs/configuration.md | 1 + docs/running-queues.md | 2 +- src/Config/Queue.php | 3 ++ src/Models/QueueJobModel.php | 2 +- tests/Models/QueueJobModelTest.php | 64 ++++++++++++++++++++++++++++++ tests/_support/Config/Queue.php | 5 ++- 6 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/Models/QueueJobModelTest.php diff --git a/docs/configuration.md b/docs/configuration.md index 858713e..a710146 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -37,6 +37,7 @@ The configuration settings for `database` handler. * `dbGroup` - The database group to use. Default value: `default`. * `getShared` - Weather to use shared instance. Default value: `true`. +* `skipLocked` - Weather to use "skip locked" feature to maintain concurrency calls. Default to `true`. ### $redis diff --git a/docs/running-queues.md b/docs/running-queues.md index 10d4139..776289f 100644 --- a/docs/running-queues.md +++ b/docs/running-queues.md @@ -65,7 +65,7 @@ This way, worker will consume jobs with the `low` priority and then with `high`. ### Running many instances of the same queue -As mentioned above, sometimes we may want to have multiple instances of the same command running at the same time. The queue is safe to use in that scenario with all databases except `SQLite3` since it doesn't guarantee that the job will be selected only by one process. +As mentioned above, sometimes we may want to have multiple instances of the same command running at the same time. The queue is safe to use in that scenario with all databases if you keep the `skipLocked` to `true` in the config file. Only for SQLite3 driver this setting is not relevant. ### Handling long-running process diff --git a/src/Config/Queue.php b/src/Config/Queue.php index 8380e69..793ea85 100644 --- a/src/Config/Queue.php +++ b/src/Config/Queue.php @@ -45,6 +45,9 @@ class Queue extends BaseConfig public array $database = [ 'dbGroup' => 'default', 'getShared' => true, + // use skip locked feature to maintain concurrency calls + // this is not relevant for the SQLite3 database driver + 'skipLocked' => true, ]; /** diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index a086fad..121c8fb 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -90,7 +90,7 @@ public function getFromQueue(string $name, array $priority): ?QueueJob */ private function skipLocked(string $sql): string { - if ($this->db->DBDriver === 'SQLite3') { + if ($this->db->DBDriver === 'SQLite3' || config('Queue')->database['skipLocked'] === false) { return $sql; } diff --git a/tests/Models/QueueJobModelTest.php b/tests/Models/QueueJobModelTest.php new file mode 100644 index 0000000..01c8532 --- /dev/null +++ b/tests/Models/QueueJobModelTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Models; + +use CodeIgniter\Queue\Models\QueueJobModel; +use CodeIgniter\Test\ReflectionHelper; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class QueueJobModelTest extends TestCase +{ + use ReflectionHelper; + + public function testQueueJobModel(): void + { + $model = model(QueueJobModel::class); + $this->assertInstanceOf(QueueJobModel::class, $model); + } + + public function testSkipLocked(): void + { + $model = model(QueueJobModel::class); + $method = $this->getPrivateMethodInvoker($model, 'skipLocked'); + + $sql = 'SELECT * FROM queue_jobs WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1'; + $result = $method($sql); + + if ($model->db->DBDriver === 'SQLite3') { + $this->assertSame($sql, $result); + } elseif ($model->db->DBDriver === 'SQLSRV') { + $expected = 'SELECT * FROM queue_jobs WITH (ROWLOCK,UPDLOCK,READPAST) WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1'; + $this->assertSame($expected, $result); + } else { + $expected = 'SELECT * FROM queue_jobs WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1 FOR UPDATE SKIP LOCKED'; + $this->assertSame($expected, $result); + } + } + + public function testSkipLockedFalse(): void + { + config('Queue')->database['skipLocked'] = false; + + $model = model(QueueJobModel::class); + $method = $this->getPrivateMethodInvoker($model, 'skipLocked'); + + $sql = 'SELECT * FROM queue_jobs WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1'; + $result = $method($sql); + + $this->assertSame($sql, $result); + } +} diff --git a/tests/_support/Config/Queue.php b/tests/_support/Config/Queue.php index a7a3a48..cea913e 100644 --- a/tests/_support/Config/Queue.php +++ b/tests/_support/Config/Queue.php @@ -40,8 +40,9 @@ class Queue extends BaseQueue * Database handler config. */ public array $database = [ - 'dbGroup' => 'default', - 'getShared' => true, + 'dbGroup' => 'default', + 'getShared' => true, + 'skipLocked' => true, ]; /** From f37f8e510bc9eb09847210a18b1cebaf46ae2528 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Mon, 30 Dec 2024 21:17:14 +0100 Subject: [PATCH 56/68] Add tests for every database handler (#49) * add tests for every database handler * add permissions to the phpunit workflow * update workflow * update tests and workflow * update tests for SQLSRV * update tests * fix sqlsrv tests * update workflow * update skipLocked() method for OCI8 * update skipLocked() method for OCI8 * update tests * simplify the code as text type is deprecated in SQLSRV * fix rector * update sqlsrv extension in the workflow * froze ubuntu version * cleanup --- .github/workflows/phpunit.yml | 115 +++++++++++++- ...-110712_ChangePayloadFieldTypeInSqlsrv.php | 52 +++++++ src/Models/QueueJobModel.php | 12 +- tests/DatabaseHandlerTest.php | 12 +- tests/Models/QueueJobModelTest.php | 6 +- tests/PredisHandlerTest.php | 2 +- tests/RedisHandlerTest.php | 2 +- tests/_support/Config/Registrar.php | 143 ++++++++++++++++++ 8 files changed, 321 insertions(+), 23 deletions(-) create mode 100644 src/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php create mode 100644 tests/_support/Config/Registrar.php diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml index f2b9a10..a63634b 100644 --- a/.github/workflows/phpunit.yml +++ b/.github/workflows/phpunit.yml @@ -18,12 +18,95 @@ on: - 'phpunit*' - '.github/workflows/phpunit.yml' +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +permissions: + contents: read + +env: + NLS_LANG: 'AMERICAN_AMERICA.UTF8' + NLS_DATE_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + NLS_TIMESTAMP_TZ_FORMAT: 'YYYY-MM-DD HH24:MI:SS' + jobs: main: - name: PHP ${{ matrix.php-versions }} Unit Tests + name: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} runs-on: ubuntu-22.04 + if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + matrix: + php-versions: ['8.1', '8.2', '8.3'] + db-platforms: ['MySQLi', 'SQLite3'] + include: + # Postgre + - php-versions: '8.1' + db-platforms: Postgre + # SQLSRV + - php-versions: '8.1' + db-platforms: SQLSRV + # OCI8 + - php-versions: '8.1' + db-platforms: OCI8 services: + mysql: + image: mysql:8.0 + env: + MYSQL_ALLOW_EMPTY_PASSWORD: yes + MYSQL_DATABASE: test + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + postgres: + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + ports: + - 5432:5432 + options: >- + --health-cmd=pg_isready + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + mssql: + image: mcr.microsoft.com/mssql/server:2022-latest + env: + MSSQL_SA_PASSWORD: 1Secure*Password1 + ACCEPT_EULA: Y + MSSQL_PID: Developer + ports: + - 1433:1433 + options: >- + --health-cmd="/opt/mssql-tools18/bin/sqlcmd -C -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q 'SELECT @@VERSION'" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + oracle: + image: gvenzl/oracle-xe:21 + env: + ORACLE_RANDOM_PASSWORD: true + APP_USER: ORACLE + APP_USER_PASSWORD: ORACLE + ports: + - 1521:1521 + options: >- + --health-cmd healthcheck.sh + --health-interval 20s + --health-timeout 10s + --health-retries 10 + redis: image: redis ports: @@ -34,12 +117,27 @@ jobs: --health-timeout=5s --health-retries=3 - if: "!contains(github.event.head_commit.message, '[ci skip]')" - strategy: - matrix: - php-versions: ['8.1', '8.2', '8.3'] - steps: + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: false + docker-images: true + swap-storage: true + + - name: Create database for MSSQL Server + if: matrix.db-platforms == 'SQLSRV' + run: sqlcmd -S 127.0.0.1 -U sa -P 1Secure*Password1 -Q "CREATE DATABASE test" + - name: Checkout uses: actions/checkout@v4 @@ -48,7 +146,7 @@ jobs: with: php-version: ${{ matrix.php-versions }} tools: composer, phive, phpunit - extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3, redis + extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3, sqlsrv, oci8, pgsql coverage: xdebug env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -74,6 +172,7 @@ jobs: - name: Test with PHPUnit run: vendor/bin/phpunit --coverage-text env: + DB: ${{ matrix.db-platforms }} TERM: xterm-256color TACHYCARDIA_MONITOR_GA: enabled @@ -86,7 +185,7 @@ jobs: env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_PARALLEL: true - COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} + COVERALLS_FLAG_NAME: PHP ${{ matrix.php-versions }} - ${{ matrix.db-platforms }} coveralls: needs: [main] diff --git a/src/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php b/src/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php new file mode 100644 index 0000000..c77c4e0 --- /dev/null +++ b/src/Database/Migrations/2024-12-27-110712_ChangePayloadFieldTypeInSqlsrv.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Database\Migrations; + +use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\Migration; + +/** + * @property BaseConnection $db + */ +class ChangePayloadFieldTypeInSqlsrv extends Migration +{ + public function up(): void + { + if ($this->db->DBDriver === 'SQLSRV') { + $fields = [ + 'payload' => [ + 'name' => 'payload', + 'type' => 'NVARCHAR', + 'constraint' => 'MAX', + 'null' => false, + ], + ]; + $this->forge->modifyColumn('queue_jobs', $fields); + } + } + + public function down(): void + { + if ($this->db->DBDriver === 'SQLSRV') { + $fields = [ + 'payload' => [ + 'name' => 'payload', + 'type' => 'TEXT', // already deprecated + 'null' => false, + ], + ]; + $this->forge->modifyColumn('queue_jobs', $fields); + } + } +} diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 121c8fb..ed7d26f 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -100,7 +100,13 @@ private function skipLocked(string $sql): string return str_replace('WHERE', $replace, $sql); } - return $sql .= ' FOR UPDATE SKIP LOCKED'; + if ($this->db->DBDriver === 'OCI8') { + $sql = str_replace('SELECT *', 'SELECT "id"', $sql); + // prepare final query + $sql = sprintf('SELECT * FROM "%s" WHERE "id" = (%s)', $this->db->prefixTable($this->table), $sql); + } + + return $sql . ' FOR UPDATE SKIP LOCKED'; } /** @@ -111,9 +117,9 @@ private function setPriority(BaseBuilder $builder, array $priority): BaseBuilder $builder->whereIn('priority', $priority); if ($priority !== ['default']) { - if ($this->db->DBDriver === 'SQLite3') { + if ($this->db->DBDriver !== 'MySQLi') { $builder->orderBy( - 'CASE priority ' + sprintf('CASE %s ', $this->db->protectIdentifiers('priority')) . implode( ' ', array_map(static fn ($value, $key) => "WHEN '{$value}' THEN {$key}", $priority, array_keys($priority)) diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 33fd4af..d62d015 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -89,7 +89,7 @@ public function testPush(): void $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); } @@ -108,7 +108,7 @@ public function testPushWithPriority(): void 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), 'priority' => 'high', - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); } @@ -124,7 +124,7 @@ public function testPushAndPopWithPriority(): void 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), 'priority' => 'low', - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); @@ -134,7 +134,7 @@ public function testPushAndPopWithPriority(): void 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), 'priority' => 'high', - 'available_at' => '1703859316', + 'available_at' => 1703859316, ]); $result = $handler->pop('queue', ['high', 'low']); @@ -268,7 +268,7 @@ public function testFailedAndKeepJob(): void 'id' => 2, 'connection' => 'database', 'queue' => 'queue1', - 'failed_at' => '1703859316', + 'failed_at' => 1703859316, ]); } @@ -344,7 +344,7 @@ public function testRetry(): void $handler = new DatabaseHandler($this->config); $count = $handler->retry(1, 'queue1'); - $this->assertSame($count, 1); + $this->assertSame(1, $count); $this->seeInDatabase('queue_jobs', [ 'id' => 3, diff --git a/tests/Models/QueueJobModelTest.php b/tests/Models/QueueJobModelTest.php index 01c8532..cf829d3 100644 --- a/tests/Models/QueueJobModelTest.php +++ b/tests/Models/QueueJobModelTest.php @@ -41,11 +41,9 @@ public function testSkipLocked(): void if ($model->db->DBDriver === 'SQLite3') { $this->assertSame($sql, $result); } elseif ($model->db->DBDriver === 'SQLSRV') { - $expected = 'SELECT * FROM queue_jobs WITH (ROWLOCK,UPDLOCK,READPAST) WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1'; - $this->assertSame($expected, $result); + $this->assertStringContainsString('WITH (ROWLOCK,UPDLOCK,READPAST) WHERE', $result); } else { - $expected = 'SELECT * FROM queue_jobs WHERE queue = "test" AND status = 0 AND available_at < 123456 LIMIT 1 FOR UPDATE SKIP LOCKED'; - $this->assertSame($expected, $result); + $this->assertStringContainsString('FOR UPDATE SKIP LOCKED', $result); } } diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 4470295..51f1492 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -288,7 +288,7 @@ public function testRetry(): void $handler = new PredisHandler($this->config); $count = $handler->retry(1, 'queue1'); - $this->assertSame($count, 1); + $this->assertSame(1, $count); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(2, $predis->zcard('queues:queue1:default')); diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index e691197..96ae0d6 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -254,7 +254,7 @@ public function testRetry(): void $handler = new RedisHandler($this->config); $count = $handler->retry(1, 'queue1'); - $this->assertSame($count, 1); + $this->assertSame(1, $count); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(2, $redis->zCard('queues:queue1:default')); diff --git a/tests/_support/Config/Registrar.php b/tests/_support/Config/Registrar.php new file mode 100644 index 0000000..86a303e --- /dev/null +++ b/tests/_support/Config/Registrar.php @@ -0,0 +1,143 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Support\Config; + +/** + * Class Registrar + * + * Provides a basic registrar class for testing BaseConfig registration functions. + */ +class Registrar +{ + /** + * DB config array for testing purposes. + * + * @var array|bool|int|string>> + */ + protected static array $dbConfig = [ + 'MySQLi' => [ + 'DSN' => '', + 'hostname' => '127.0.0.1', + 'username' => 'root', + 'password' => '', + 'database' => 'test', + 'DBDriver' => 'MySQLi', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8mb4', + 'DBCollat' => 'utf8mb4_general_ci', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 3306, + ], + 'Postgre' => [ + 'DSN' => '', + 'hostname' => 'localhost', + 'username' => 'postgres', + 'password' => 'postgres', + 'database' => 'test', + 'DBDriver' => 'Postgre', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 5432, + ], + 'SQLite3' => [ + 'DSN' => '', + 'hostname' => 'localhost', + 'username' => '', + 'password' => '', + 'database' => 'database.db', + 'DBDriver' => 'SQLite3', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 3306, + 'foreignKeys' => true, + ], + 'SQLSRV' => [ + 'DSN' => '', + 'hostname' => 'localhost', + 'username' => 'sa', + 'password' => '1Secure*Password1', + 'database' => 'test', + 'DBDriver' => 'SQLSRV', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'utf8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + 'port' => 1433, + ], + 'OCI8' => [ + 'DSN' => 'localhost:1521/XEPDB1', + 'hostname' => '', + 'username' => 'ORACLE', + 'password' => 'ORACLE', + 'database' => '', + 'DBDriver' => 'OCI8', + 'DBPrefix' => 'db_', + 'pConnect' => false, + 'DBDebug' => true, + 'charset' => 'AL32UTF8', + 'DBCollat' => '', + 'swapPre' => '', + 'encrypt' => false, + 'compress' => false, + 'strictOn' => false, + 'failover' => [], + ], + ]; + + /** + * Override database config + * + * @return array|bool|int|string> + */ + public static function Database(): array + { + $config = []; + + // Under GitHub Actions, we can set an ENV var named 'DB' + // so that we can test against multiple databases. + if (($group = getenv('DB')) && isset(self::$dbConfig[$group])) { + $config['tests'] = self::$dbConfig[$group]; + } + + return $config; + } +} From 847079f28026822eaceded1faacc148694f46077 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Tue, 7 Jan 2025 09:01:59 +0100 Subject: [PATCH 57/68] Disable strict mode for transactions (#50) * disable strict mode for transactions * update docs --- docs/basic-usage.md | 46 ++++++++++++++++++++++++++++++ docs/configuration.md | 4 +++ src/Models/QueueJobFailedModel.php | 19 ++++++++++++ src/Models/QueueJobModel.php | 19 ++++++++++++ 4 files changed, 88 insertions(+) diff --git a/docs/basic-usage.md b/docs/basic-usage.md index afe403f..5622a82 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -79,6 +79,52 @@ You may be wondering what the `$this->data['message']` variable is all about. We Throwing an exception is a way to let the queue worker know that the job has failed. +#### Using transactions + +If you have to use transactions in your Job - this is a simple schema you can follow. + +!!! note + + Due to the nature of the queue worker, [Strict Mode](https://codeigniter.com/user_guide/database/transactions.html#strict-mode) is automatically disabled for the database connection assigned to the Database handler. That's because queue worker is a long-running process, and we don't want one failed transaction to affect others. + + If you use the same connection group in your Job as defined in the Database handler, then in that case, you don't need to do anything. + + On the other hand, if you are using a different group to connect to the database in your Job, then if you are using transactions, you should disable Strict Mode through the method: `$db->transStrict(false)` or by setting the `transStrict` option to `false` in your connection config group - the last option will disable Strict Mode globally. + +```php +// ... + +class Email extends BaseJob implements JobInterface +{ + /** + * @throws Exception + */ + public function process(string $data): + { + try { + $db = db_connect(); + // Disable Strict Mode + $db->transStrict(false); + $db->transBegin(); + + // Job logic goes here + // Your code should throw an exception on error + + if ($db->transStatus() === false) { + $db->transRollback(); + } else { + $db->transCommit(); + } + } catch (Exception $e) { + $db->transRollback(); + throw $e; + } + } +} +``` + +#### Other options + We can also configure some things on the job level. It's a number of tries, when the job is failing and time after the job will be retried again after failure. We can specify these options by using variables: ```php diff --git a/docs/configuration.md b/docs/configuration.md index a710146..c26eb5c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -39,6 +39,10 @@ The configuration settings for `database` handler. * `getShared` - Weather to use shared instance. Default value: `true`. * `skipLocked` - Weather to use "skip locked" feature to maintain concurrency calls. Default to `true`. +!!! note + + The [Strict Mode](https://codeigniter.com/user_guide/database/transactions.html#strict-mode) for the given `dbGroup` is automatically disabled - due to the nature of the queue worker. + ### $redis The configuration settings for `redis` handler. You need to have a [ext-redis](https://github.com/phpredis/phpredis) installed to use it. diff --git a/src/Models/QueueJobFailedModel.php b/src/Models/QueueJobFailedModel.php index 0574da6..e98b5f3 100644 --- a/src/Models/QueueJobFailedModel.php +++ b/src/Models/QueueJobFailedModel.php @@ -13,8 +13,12 @@ namespace CodeIgniter\Queue\Models; +use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\Model; use CodeIgniter\Queue\Entities\QueueJobFailed; +use CodeIgniter\Validation\ValidationInterface; +use Config\Database; class QueueJobFailedModel extends Model { @@ -37,4 +41,19 @@ class QueueJobFailedModel extends Model // Callbacks protected $allowCallbacks = false; + + public function __construct(?ConnectionInterface $db = null, ?ValidationInterface $validation = null) + { + $this->DBGroup = config('Queue')->database['dbGroup']; + + /** + * @var BaseConnection|null $db + */ + $db ??= Database::connect($this->DBGroup); + + // Turn off the Strict Mode + $db->transStrict(false); + + parent::__construct($db, $validation); + } } diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index ed7d26f..6aaf910 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -14,10 +14,14 @@ namespace CodeIgniter\Queue\Models; use CodeIgniter\Database\BaseBuilder; +use CodeIgniter\Database\BaseConnection; +use CodeIgniter\Database\ConnectionInterface; use CodeIgniter\I18n\Time; use CodeIgniter\Model; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; +use CodeIgniter\Validation\ValidationInterface; +use Config\Database; use ReflectionException; class QueueJobModel extends Model @@ -42,6 +46,21 @@ class QueueJobModel extends Model // Callbacks protected $allowCallbacks = false; + public function __construct(?ConnectionInterface $db = null, ?ValidationInterface $validation = null) + { + $this->DBGroup = config('Queue')->database['dbGroup']; + + /** + * @var BaseConnection|null $db + */ + $db ??= Database::connect($this->DBGroup); + + // Turn off the Strict Mode + $db->transStrict(false); + + parent::__construct($db, $validation); + } + /** * Get the oldest item from the queue. * From 16fd35bc83d4b23549e2885090459537c6e54fe9 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Fri, 17 Jan 2025 13:03:54 +0100 Subject: [PATCH 58/68] use Lua script to pop the task atomically (#51) * use lua script to pop the task atomically * docs update * fix phpstan * update docs * cs fix --- .php-cs-fixer.dist.php | 2 +- docs/index.md | 5 ++++ docs/running-queues.md | 6 +++-- mkdocs.yml | 2 +- src/Commands/QueueWork.php | 2 +- src/Handlers/BaseHandler.php | 10 ++++---- src/Handlers/PredisHandler.php | 44 ++++++++++++++++++---------------- src/Handlers/RedisHandler.php | 37 +++++++++++++++++----------- src/Lua/pop_task.lua | 17 +++++++++++++ src/Models/QueueJobModel.php | 8 +++---- 10 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 src/Lua/pop_task.lua diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index f82bdbf..001fd00 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -37,5 +37,5 @@ return Factory::create(new CodeIgniter4(), $overrides, $options)->forLibrary( 'CodeIgniter Queue', 'CodeIgniter Foundation', - 'admin@codeigniter.com' + 'admin@codeigniter.com', ); diff --git a/docs/index.md b/docs/index.md index 7f835b6..ab3643c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,6 +26,11 @@ If you use `database` handler: - Oracle 12.1+ - SQLite3 +If you use `Redis` (you still need a relational database to store failed jobs): + +- PHPRedis +- Predis + ### Table of Contents * [Installation](installation.md) diff --git a/docs/running-queues.md b/docs/running-queues.md index 776289f..c32d74f 100644 --- a/docs/running-queues.md +++ b/docs/running-queues.md @@ -65,11 +65,13 @@ This way, worker will consume jobs with the `low` priority and then with `high`. ### Running many instances of the same queue -As mentioned above, sometimes we may want to have multiple instances of the same command running at the same time. The queue is safe to use in that scenario with all databases if you keep the `skipLocked` to `true` in the config file. Only for SQLite3 driver this setting is not relevant. +As mentioned above, sometimes we may want to have multiple instances of the same command running at the same time. The queue is safe to use in that scenario with all databases as long as you keep the `skipLocked` to `true` in the config file. Only for SQLite3 driver, this setting is not relevant as it provides atomicity without the need for explicit concurrency control. + +The PHPRedis and Predis drivers are also safe to use with multiple instances of the same command. ### Handling long-running process -If we decide to run the long process e.g. with the command: +If we decide to run the long process, e.g., with the command: php spark queue:work emails -wait 10 diff --git a/mkdocs.yml b/mkdocs.yml index feb90ad..b93a433 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,7 @@ extra: site_url: https://queue.codeigniter.com/ repo_url: https://github.com/codeigniter4/queue edit_uri: edit/develop/docs/ -copyright: Copyright © 2023 CodeIgniter Foundation. +copyright: Copyright © 2025 CodeIgniter Foundation. markdown_extensions: - admonition diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 391917d..6a4d4fb 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -108,7 +108,7 @@ public function run(array $params) $memory, $priority, $tries, - $retryAfter + $retryAfter, ] = $this->readOptions($params, $config, $queue); if ($error !== null) { diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 6e29e45..07aeb3a 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -72,11 +72,11 @@ public function retry(?int $id, ?string $queue): int $jobs = model(QueueJobFailedModel::class) ->when( $id !== null, - static fn ($query) => $query->where('id', $id) + static fn ($query) => $query->where('id', $id), ) ->when( $queue !== null, - static fn ($query) => $query->where('queue', $queue) + static fn ($query) => $query->where('queue', $queue), ) ->findAll(); @@ -112,11 +112,11 @@ public function flush(?int $hours, ?string $queue): bool return model(QueueJobFailedModel::class) ->when( $hours !== null, - static fn ($query) => $query->where('failed_at <=', Time::now()->subHours($hours)->timestamp) + static fn ($query) => $query->where('failed_at <=', Time::now()->subHours($hours)->timestamp), ) ->when( $queue !== null, - static fn ($query) => $query->where('queue', $queue) + static fn ($query) => $query->where('queue', $queue), ) ->delete(); } @@ -129,7 +129,7 @@ public function listFailed(?string $queue): array return model(QueueJobFailedModel::class) ->when( $queue !== null, - static fn ($query) => $query->where('queue', $queue) + static fn ($query) => $query->where('queue', $queue), ) ->orderBy('failed_at', 'desc') ->findAll(); diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index c06c9d3..4c2114d 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -13,6 +13,7 @@ namespace CodeIgniter\Queue\Handlers; +use CodeIgniter\Autoloader\FileLocator; use CodeIgniter\Exceptions\CriticalError; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Config\Queue as QueueConfig; @@ -27,12 +28,20 @@ class PredisHandler extends BaseHandler implements QueueInterface { private readonly Client $predis; + private readonly string $luaScript; public function __construct(protected QueueConfig $config) { try { $this->predis = new Client($config->predis, ['prefix' => $config->predis['prefix']]); $this->predis->time(); + + $locator = new FileLocator(service('autoloader')); + $luaScript = $locator->locateFile('CodeIgniter\Queue\Lua\pop_task', null, 'lua'); + if ($luaScript === false) { + throw new CriticalError('Queue: LUA script for Predis is not available.'); + } + $this->luaScript = file_get_contents($luaScript); } catch (Exception $e) { throw new CriticalError('Queue: Predis connection refused (' . $e->getMessage() . ').'); } @@ -77,30 +86,23 @@ public function push(string $queue, string $job, array $data): bool */ public function pop(string $queue, array $priorities): ?QueueJob { - $tasks = []; - $now = Time::now()->timestamp; - - foreach ($priorities as $priority) { - $tasks = $this->predis->zrangebyscore( - "queues:{$queue}:{$priority}", - '-inf', - $now, - ['LIMIT' => [0, 1]] - ); - if ($tasks !== []) { - $removed = $this->predis->zrem("queues:{$queue}:{$priority}", ...$tasks); - if ($removed !== 0) { - break; - } - $tasks = []; - } - } + $now = (string) Time::now()->timestamp; + + // Prepare the arguments for the Lua script + $args = [ + 'queues:' . $queue, // KEYS[1] + $now, // ARGV[2] + json_encode($priorities), // ARGV[3] + ]; + + // Execute the Lua script + $task = $this->predis->eval($this->luaScript, 1, ...$args); - if ($tasks === []) { + if ($task === null) { return null; } - $queueJob = new QueueJob(json_decode((string) $tasks[0], true)); + $queueJob = new QueueJob(json_decode((string) $task, true)); // Set the actual status as in DB. $queueJob->status = Status::RESERVED->value; @@ -121,7 +123,7 @@ public function later(QueueJob $queueJob, int $seconds): bool $result = $this->predis->zadd( "queues:{$queueJob->queue}:{$queueJob->priority}", - [json_encode($queueJob) => $queueJob->available_at->timestamp] + [json_encode($queueJob) => $queueJob->available_at->timestamp], ); if ($result !== 0) { $this->predis->hdel("queues:{$queueJob->queue}::reserved", [$queueJob->id]); diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index 6d4e537..8fcf27f 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -13,6 +13,7 @@ namespace CodeIgniter\Queue\Handlers; +use CodeIgniter\Autoloader\FileLocator; use CodeIgniter\Exceptions\CriticalError; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Config\Queue as QueueConfig; @@ -27,6 +28,7 @@ class RedisHandler extends BaseHandler implements QueueInterface { private readonly Redis $redis; + private readonly string $luaScript; public function __construct(protected QueueConfig $config) { @@ -48,6 +50,13 @@ public function __construct(protected QueueConfig $config) if (isset($config->redis['prefix']) && ! $this->redis->setOption(Redis::OPT_PREFIX, $config->redis['prefix'])) { throw new CriticalError('Queue: Redis setting prefix failed.'); } + + $locator = new FileLocator(service('autoloader')); + $luaScript = $locator->locateFile('CodeIgniter\Queue\Lua\pop_task', null, 'lua'); + if ($luaScript === false) { + throw new CriticalError('Queue: LUA script for Redis is not available.'); + } + $this->luaScript = file_get_contents($luaScript); } catch (RedisException $e) { throw new CriticalError('Queue: RedisException occurred with message (' . $e->getMessage() . ').'); } @@ -96,23 +105,23 @@ public function push(string $queue, string $job, array $data): bool */ public function pop(string $queue, array $priorities): ?QueueJob { - $tasks = []; - $now = Time::now()->timestamp; - - foreach ($priorities as $priority) { - if ($tasks = $this->redis->zRangeByScore("queues:{$queue}:{$priority}", '-inf', (string) $now, ['limit' => [0, 1]])) { - if ($this->redis->zRem("queues:{$queue}:{$priority}", ...$tasks)) { - break; - } - $tasks = []; - } - } + $now = Time::now()->timestamp; + + // Prepare the arguments for the Lua script + $args = [ + 'queues:' . $queue, // KEYS[1] + $now, // ARGV[2] + json_encode($priorities), // ARGV[3] + ]; + + // Execute the Lua script + $task = $this->redis->eval($this->luaScript, $args, 1); - if ($tasks === []) { + if ($task === false) { return null; } - $queueJob = new QueueJob(json_decode((string) $tasks[0], true)); + $queueJob = new QueueJob(json_decode((string) $task, true)); // Set the actual status as in DB. $queueJob->status = Status::RESERVED->value; @@ -136,7 +145,7 @@ public function later(QueueJob $queueJob, int $seconds): bool $result = (int) $this->redis->zAdd( "queues:{$queueJob->queue}:{$queueJob->priority}", $queueJob->available_at->timestamp, - json_encode($queueJob) + json_encode($queueJob), ); if ($result !== 0) { $this->redis->hDel("queues:{$queueJob->queue}::reserved", (string) $queueJob->id); diff --git a/src/Lua/pop_task.lua b/src/Lua/pop_task.lua new file mode 100644 index 0000000..8ddeb79 --- /dev/null +++ b/src/Lua/pop_task.lua @@ -0,0 +1,17 @@ +local queue = KEYS[1] +local now = tonumber(ARGV[1]) +local priorities = cjson.decode(ARGV[2]) +local task = nil + +for _, priority in ipairs(priorities) do + local key = queue .. ':' .. priority + local tasks = redis.call('ZRANGEBYSCORE', key, '-inf', tostring(now), 'LIMIT', 0, 1) + + if #tasks > 0 then + redis.call('ZREM', key, tasks[1]) + task = tasks[1] + break + end +end + +return task diff --git a/src/Models/QueueJobModel.php b/src/Models/QueueJobModel.php index 6aaf910..4df5bd1 100644 --- a/src/Models/QueueJobModel.php +++ b/src/Models/QueueJobModel.php @@ -141,22 +141,22 @@ private function setPriority(BaseBuilder $builder, array $priority): BaseBuilder sprintf('CASE %s ', $this->db->protectIdentifiers('priority')) . implode( ' ', - array_map(static fn ($value, $key) => "WHEN '{$value}' THEN {$key}", $priority, array_keys($priority)) + array_map(static fn ($value, $key) => "WHEN '{$value}' THEN {$key}", $priority, array_keys($priority)), ) . ' END', '', - false + false, ); } else { $builder->orderBy( 'FIELD(priority, ' . implode( ',', - array_map(static fn ($value) => "'{$value}'", $priority) + array_map(static fn ($value) => "'{$value}'", $priority), ) . ')', '', - false + false, ); } } From b9ac82970dde8dab6efa0b77c5299903cf7e1341 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sun, 19 Jan 2025 17:21:06 +0100 Subject: [PATCH 59/68] docs: add clarification for queue usage scenarios (#53) * add clarification for queue usage scenarios * fix wording --- README.md | 3 +++ docs/index.md | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f003ec9..f4c6ee0 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,9 @@ Queues for the CodeIgniter 4 framework. ![CodeIgniter](https://img.shields.io/badge/CodeIgniter-%5E4.3-blue) ![License](https://img.shields.io/badge/License-MIT-blue) +> [!NOTE] +> A queue system is typically used to handle resource-intensive or time-consuming tasks (e.g., image processing, sending emails) that are to be run in the background. It can also be a way to postpone certain activities that are to be executed automatically later. + ## Installation composer require codeigniter4/queue diff --git a/docs/index.md b/docs/index.md index ab3643c..0b23235 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,11 @@ A library that helps you handle Queues in the CodeIgniter 4 framework. -Add job to the queue. +!!! info "What are queues used for?" + + A queue system is typically used to handle resource-intensive or time-consuming tasks (e.g., image processing, sending emails) that are to be run in the background. It can also be a way to postpone certain activities that are to be executed automatically later. + +Add a job to the queue. ```php service('queue')->push('queueName', 'jobName', ['array' => 'parameters']); From 3675024af715754dd22482db7a923c3cdd5900b5 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Wed, 5 Feb 2025 09:26:30 +0100 Subject: [PATCH 60/68] Add an option to delay job execution when adding to the queue (#52) * add an option to delay job execution when adding to the queue * add delay exception test * fix phpcpd * tests: make sure the job is added to the queue with the proper delay --- docs/running-queues.md | 18 +++++- phpunit.xml.dist | 2 +- src/Exceptions/QueueException.php | 5 ++ src/Handlers/BaseHandler.php | 15 +++++ src/Handlers/DatabaseHandler.php | 4 +- src/Handlers/PredisHandler.php | 8 ++- src/Handlers/RedisHandler.php | 8 ++- src/Language/en/Queue.php | 1 + tests/DatabaseHandlerTest.php | 35 ++++++++++ tests/PredisHandlerTest.php | 21 ++++++ tests/PushAndPopWithDelayTest.php | 103 ++++++++++++++++++++++++++++++ tests/RedisHandlerTest.php | 22 +++++++ 12 files changed, 232 insertions(+), 10 deletions(-) create mode 100644 tests/PushAndPopWithDelayTest.php diff --git a/docs/running-queues.md b/docs/running-queues.md index c32d74f..c32184b 100644 --- a/docs/running-queues.md +++ b/docs/running-queues.md @@ -12,7 +12,7 @@ This will cause command to check for the new jobs every 10 seconds if the queue ### With CRON -Using queues with CRON is more challenging, but definitely doable. You can use command like this: +Using queues with CRON is more challenging but definitely doable. You can use command like this: php spark queue:work emails -max-jobs 20 --stop-when-empty @@ -63,6 +63,22 @@ But we can also run the worker like this: This way, worker will consume jobs with the `low` priority and then with `high`. The order set in the config file is override. +### Delaying jobs + +Normally, when we add jobs to a queue, they are run in the order in which we added them to the queue (FIFO - first in, first out). +Of course, there are also priorities, which we described in the previous section. But what about the scenario where we want to run a job, but not earlier than in 5 minutes? + +This is where job delay comes into play. We measure the delay in seconds. + +```php +// This job will be run not sooner than in 5 minutes +service('queue')->setDelay(5 * MINUTE)->push('emails', 'email', ['message' => 'Email sent no sooner than 5 minutes from now']); +``` + +Note that there is no guarantee that the job will run exactly in 5 minutes. If many new jobs are added to the queue (without a delay), it may take a long time before the delayed job is actually executed. + +We can also combine delayed jobs with priorities. + ### Running many instances of the same queue As mentioned above, sometimes we may want to have multiple instances of the same command running at the same time. The queue is safe to use in that scenario with all databases as long as you keep the `skipLocked` to `true` in the config file. Only for SQLite3 driver, this setting is not relevant as it provides atomicity without the need for explicit concurrency control. diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 927d60a..48fe085 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -12,7 +12,7 @@ stopOnFailure="false" stopOnIncomplete="false" stopOnSkipped="false" - cacheDirectory=".phpunit.cache" + cacheDirectory="build/.phpunit.cache" beStrictAboutCoverageMetadata="true"> diff --git a/src/Exceptions/QueueException.php b/src/Exceptions/QueueException.php index 7e3db3d..542de45 100644 --- a/src/Exceptions/QueueException.php +++ b/src/Exceptions/QueueException.php @@ -51,4 +51,9 @@ public static function forIncorrectQueuePriority(string $priority, string $queue { return new self(lang('Queue.incorrectQueuePriority', [$priority, $queue])); } + + public static function forIncorrectDelayValue(): static + { + return new self(lang('Queue.incorrectDelayValue')); + } } diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 07aeb3a..63e0f87 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -29,6 +29,7 @@ abstract class BaseHandler { protected QueueConfig $config; protected ?string $priority = null; + protected ?int $delay = null; abstract public function name(): string; @@ -62,6 +63,20 @@ public function setPriority(string $priority): static return $this; } + /** + * Set delay for job queue (in seconds). + */ + public function setDelay(int $delay): static + { + if ($delay < 0) { + throw QueueException::forIncorrectDelayValue(); + } + + $this->delay = $delay; + + return $this; + } + /** * Retry failed job. * diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 5c11279..71bd497 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -56,10 +56,10 @@ public function push(string $queue, string $job, array $data): bool 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, - 'available_at' => Time::now(), + 'available_at' => Time::now()->addSeconds($this->delay ?? 0), ]); - $this->priority = null; + $this->priority = $this->delay = null; return $this->jobModel->insert($queueJob, false); } diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 4c2114d..e781955 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -64,6 +64,8 @@ public function push(string $queue, string $job, array $data): bool helper('text'); + $availableAt = Time::now()->addSeconds($this->delay ?? 0); + $queueJob = new QueueJob([ 'id' => random_string('numeric', 16), 'queue' => $queue, @@ -71,12 +73,12 @@ public function push(string $queue, string $job, array $data): bool 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, - 'available_at' => Time::now(), + 'available_at' => $availableAt, ]); - $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => Time::now()->timestamp]); + $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => $availableAt->timestamp]); - $this->priority = null; + $this->priority = $this->delay = null; return $result > 0; } diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index 8fcf27f..9fc49f2 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -81,6 +81,8 @@ public function push(string $queue, string $job, array $data): bool helper('text'); + $availableAt = Time::now()->addSeconds($this->delay ?? 0); + $queueJob = new QueueJob([ 'id' => random_string('numeric', 16), 'queue' => $queue, @@ -88,12 +90,12 @@ public function push(string $queue, string $job, array $data): bool 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, - 'available_at' => Time::now(), + 'available_at' => $availableAt, ]); - $result = (int) $this->redis->zAdd("queues:{$queue}:{$this->priority}", Time::now()->timestamp, json_encode($queueJob)); + $result = (int) $this->redis->zAdd("queues:{$queue}:{$this->priority}", $availableAt->timestamp, json_encode($queueJob)); - $this->priority = null; + $this->priority = $this->delay = null; return $result > 0; } diff --git a/src/Language/en/Queue.php b/src/Language/en/Queue.php index 9989f76..2336197 100644 --- a/src/Language/en/Queue.php +++ b/src/Language/en/Queue.php @@ -24,4 +24,5 @@ 'incorrectPriorityFormat' => 'The priority name should consists only lowercase letters.', 'tooLongPriorityName' => 'The priority name is too long. It should be no longer than 64 letters.', 'incorrectQueuePriority' => 'This queue has incorrectly defined priority: "{0}" for the queue: "{1}".', + 'incorrectDelayValue' => 'The number of seconds of delay must be a positive integer.', ]; diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index d62d015..4c5b159 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -112,6 +112,9 @@ public function testPushWithPriority(): void ]); } + /** + * @throws ReflectionException + */ public function testPushAndPopWithPriority(): void { Time::setTestNow('2023-12-29 14:15:16'); @@ -148,6 +151,38 @@ public function testPushAndPopWithPriority(): void $this->assertSame($payload, $result->payload); } + /** + * @throws Exception + */ + public function testPushWithDelay(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new DatabaseHandler($this->config); + $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); + + $this->assertTrue($result); + + $availableAt = 1703859376; + + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue-delay', + 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'available_at' => $availableAt, + ]); + + $this->assertEqualsWithDelta(MINUTE, $availableAt - Time::now()->getTimestamp(), 1); + } + + public function testPushWithDelayException(): void + { + $this->expectException(QueueException::class); + $this->expectExceptionMessage('The number of seconds of delay must be a positive integer.'); + + $handler = new DatabaseHandler($this->config); + $handler->setDelay(-60); + } + /** * @throws ReflectionException */ diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 51f1492..148adea 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -102,6 +102,27 @@ public function testPushWithPriority(): void $this->assertSame(['key' => 'value'], $queueJob->payload['data']); } + /** + * @throws ReflectionException + */ + public function testPushWithDelay(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new PredisHandler($this->config); + $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); + + $this->assertTrue($result); + + $predis = self::getPrivateProperty($handler, 'predis'); + $this->assertSame(1, $predis->zcard('queues:queue-delay:default')); + + $task = $predis->zrangebyscore('queues:queue-delay:default', '-inf', Time::now()->addSeconds(MINUTE)->timestamp, ['limit' => [0, 1]]); + $queueJob = new QueueJob(json_decode((string) $task[0], true)); + $this->assertSame('success', $queueJob->payload['job']); + $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + } + public function testPushException(): void { $this->expectException(QueueException::class); diff --git a/tests/PushAndPopWithDelayTest.php b/tests/PushAndPopWithDelayTest.php new file mode 100644 index 0000000..cb2d0c1 --- /dev/null +++ b/tests/PushAndPopWithDelayTest.php @@ -0,0 +1,103 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests; + +use CodeIgniter\I18n\Time; +use CodeIgniter\Queue\Entities\QueueJob; +use CodeIgniter\Test\ReflectionHelper; +use PHPUnit\Framework\Attributes\DataProvider; +use Tests\Support\Config\Queue as QueueConfig; +use Tests\Support\Database\Seeds\TestDatabaseQueueSeeder; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class PushAndPopWithDelayTest extends TestCase +{ + use ReflectionHelper; + + protected $seed = TestDatabaseQueueSeeder::class; + private QueueConfig $config; + + protected function setUp(): void + { + parent::setUp(); + + $this->config = config(QueueConfig::class); + } + + public static function handlerProvider(): iterable + { + return [ + [ + 'database', // name + 'CodeIgniter\Queue\Handlers\DatabaseHandler', // class + ], + [ + 'redis', + 'CodeIgniter\Queue\Handlers\RedisHandler', + ], + [ + 'predis', + 'CodeIgniter\Queue\Handlers\PredisHandler', + ], + ]; + } + + #[DataProvider('handlerProvider')] + public function testPushAndPopWithDelay(string $name, string $class): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new $class($this->config); + $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key1' => 'value1']); + + $this->assertTrue($result); + + $result = $handler->push('queue-delay', 'success', ['key2' => 'value2']); + + $this->assertTrue($result); + + if ($name === 'database') { + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue-delay', + 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), + 'available_at' => 1703859376, + ]); + + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue-delay', + 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), + 'available_at' => 1703859316, + ]); + } + + $result = $handler->pop('queue-delay', ['default']); + $this->assertInstanceOf(QueueJob::class, $result); + $payload = ['job' => 'success', 'data' => ['key2' => 'value2']]; + $this->assertSame($payload, $result->payload); + + $result = $handler->pop('queue-delay', ['default']); + $this->assertNull($result); + + // add 1 minute + Time::setTestNow('2023-12-29 14:16:16'); + + $result = $handler->pop('queue-delay', ['default']); + $this->assertInstanceOf(QueueJob::class, $result); + $payload = ['job' => 'success', 'data' => ['key1' => 'value1']]; + $this->assertSame($payload, $result->payload); + } +} diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index 96ae0d6..57178eb 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -19,6 +19,7 @@ use CodeIgniter\Queue\Handlers\RedisHandler; use CodeIgniter\Test\ReflectionHelper; use Exception; +use ReflectionException; use Tests\Support\Config\Queue as QueueConfig; use Tests\Support\Database\Seeds\TestRedisQueueSeeder; use Tests\Support\TestCase; @@ -95,6 +96,27 @@ public function testPushWithPriority(): void $this->assertSame(['key' => 'value'], $queueJob->payload['data']); } + /** + * @throws ReflectionException + */ + public function testPushWithDelay(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new RedisHandler($this->config); + $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); + + $this->assertTrue($result); + + $redis = self::getPrivateProperty($handler, 'redis'); + $this->assertSame(1, $redis->zCard('queues:queue-delay:default')); + + $task = $redis->zRangeByScore('queues:queue-delay:default', '-inf', Time::now()->addSeconds(MINUTE)->timestamp, ['limit' => [0, 1]]); + $queueJob = new QueueJob(json_decode((string) $task[0], true)); + $this->assertSame('success', $queueJob->payload['job']); + $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + } + public function testPushException(): void { $this->expectException(QueueException::class); From 9e89b19d2f6e4d6ae4bdc2de78f474c875f21eb7 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 27 Mar 2025 16:48:26 +0330 Subject: [PATCH 61/68] chore: add bug report tpl (#55) --- .github/ISSUE_TEMPLATE/bug_report.yml | 127 ++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e9318a5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,127 @@ +name: Bug report + +description: Create a report to help us improve CodeIgniter4 Queue +title: "Bug: " +labels: ['bug'] + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + + Before you begin, **please ensure that there are no existing issues, + whether still open or closed, related to your report**. + If there is, your report will be closed promptly. + + For example, if you encounter an issue with queue processing, + you can search the GitHub repository with relevant keywords. + + --- + + - type: input + id: php-version + attributes: + label: PHP Version + description: | + e.g. 8.2.0 + validations: + required: true + + - type: input + id: codeigniter-version + attributes: + label: CodeIgniter4 Version + description: | + e.g. 4.5.0 + validations: + required: true + + - type: input + id: queue-version + attributes: + label: Queue Package Version + description: | + e.g. dev:develop, 1.0.0 and ... + If you are not using the [latest version](https://github.com/codeigniter4/queue/releases), please + check to see if the problem occurs with the latest version. + validations: + required: true + + - type: dropdown + id: operating-systems + attributes: + label: Which operating systems have you tested for this bug? + description: You may select more than one. + multiple: true + options: + - macOS + - Windows + - Linux + validations: + required: true + + - type: dropdown + id: server + attributes: + label: Which server did you use? + options: + - apache + - cli + - cli-server (PHP built-in webserver) + - cgi-fcgi + - fpm-fcgi + - phpdbg + validations: + required: true + + - type: input + id: queue-driver + attributes: + label: Queue Driver + description: | + e.g. database, redis, predis + validations: + required: true + + - type: textarea + id: queue-configuration + attributes: + label: Queue Configuration + description: | + Please provide your queue configuration settings. + **Important:** Before sharing, ensure that all sensitive data such as passwords, API keys, and secrets are masked or removed. + validations: + required: true + + - type: textarea + id: description + attributes: + label: What happened? + placeholder: Tell us what you see! + validations: + required: true + + - type: textarea + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior. + validations: + required: true + + - type: textarea + attributes: + label: Expected Output + description: What do you expect to happen instead of this filed bug? + validations: + required: true + + - type: textarea + attributes: + label: Anything else? + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + + Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. + validations: + required: false \ No newline at end of file From 835ba88cfe79230f964bebbcd7e0f3eefb986b77 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Wed, 16 Apr 2025 14:14:27 +0200 Subject: [PATCH 62/68] feat: add support for chained jobs (#54) * feat: add support for chained jobs * phpcpd * workflow deptrac --- .github/workflows/deptrac.yml | 2 +- composer.json | 2 +- docs/basic-usage.md | 15 ++ docs/running-queues.md | 34 +++ phpstan.neon.dist | 4 + rector.php | 5 + src/Commands/QueueWork.php | 43 ++++ src/Handlers/BaseHandler.php | 79 +++--- src/Handlers/DatabaseHandler.php | 7 +- src/Handlers/PredisHandler.php | 7 +- src/Handlers/RedisHandler.php | 7 +- src/Payload.php | 31 --- src/Payloads/ChainBuilder.php | 78 ++++++ src/Payloads/ChainElement.php | 49 ++++ src/Payloads/Payload.php | 150 ++++++++++++ src/Payloads/PayloadCollection.php | 106 ++++++++ src/Payloads/PayloadMetadata.php | 133 ++++++++++ src/Traits/HasQueueValidation.php | 63 +++++ tests/Commands/QueueWorkTest.php | 44 ++++ tests/DatabaseHandlerTest.php | 90 ++++++- tests/Payloads/ChainBuilderTest.php | 160 ++++++++++++ tests/Payloads/ChainElementTest.php | 109 +++++++++ tests/Payloads/PayloadCollectionTest.php | 185 ++++++++++++++ tests/Payloads/PayloadMetadataTest.php | 233 ++++++++++++++++++ tests/Payloads/PayloadTest.php | 295 +++++++++++++++++++++++ tests/PredisHandlerTest.php | 88 +++++++ tests/PushAndPopWithDelayTest.php | 8 +- tests/RedisHandlerTest.php | 88 +++++++ 28 files changed, 2027 insertions(+), 88 deletions(-) delete mode 100644 src/Payload.php create mode 100644 src/Payloads/ChainBuilder.php create mode 100644 src/Payloads/ChainElement.php create mode 100644 src/Payloads/Payload.php create mode 100644 src/Payloads/PayloadCollection.php create mode 100644 src/Payloads/PayloadMetadata.php create mode 100644 src/Traits/HasQueueValidation.php create mode 100644 tests/Payloads/ChainBuilderTest.php create mode 100644 tests/Payloads/ChainElementTest.php create mode 100644 tests/Payloads/PayloadCollectionTest.php create mode 100644 tests/Payloads/PayloadMetadataTest.php create mode 100644 tests/Payloads/PayloadTest.php diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index ebaf4df..f481772 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -20,4 +20,4 @@ on: jobs: deptrac: - uses: codeigniter4/.github/.github/workflows/deptrac.yml@main + uses: codeigniter4/.github/.github/workflows/deptrac.yml@CI46 diff --git a/composer.json b/composer.json index 2c843ff..2e00ce2 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,7 @@ "cs": "php-cs-fixer fix --ansi --verbose --dry-run --diff", "cs-fix": "php-cs-fixer fix --ansi --verbose --diff", "style": "@cs-fix", - "deduplicate": "phpcpd app/ src/", + "deduplicate": "phpcpd src/ tests/", "inspect": "deptrac analyze --cache-file=build/deptrac.cache", "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", "test": "phpunit" diff --git a/docs/basic-usage.md b/docs/basic-usage.md index 5622a82..3204e39 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -160,6 +160,21 @@ service('queue')->push('emails', 'email', ['message' => 'Email message goes here We will be pushing `email` job to the `emails` queue. +### Sending chained jobs to the queue + +Sending chained jobs is also simple and lets you specify the particular order of the job execution. + +```php +service('queue')->chain(function($chain) { + $chain + ->push('reports', 'generate-report', ['userId' => 123]) + ->push('emails', 'email', ['message' => 'Email message goes here', 'userId' => 123]); +}); +``` + +In the example above, we will send jobs to the `reports` and `emails` queue. First, we will generate a report for given user with the `generate-report` job, after this, we will send an email with `email` job. +The `email` job will be executed only if the `generate-report` job was successful. + ### Consuming the queue Since we sent our sample job to queue `emails`, then we need to run the worker with the appropriate queue: diff --git a/docs/running-queues.md b/docs/running-queues.md index c32184b..5bae1c0 100644 --- a/docs/running-queues.md +++ b/docs/running-queues.md @@ -79,6 +79,40 @@ Note that there is no guarantee that the job will run exactly in 5 minutes. If m We can also combine delayed jobs with priorities. +### Chained jobs + +We can create sequences of jobs that run in a specific order. Each job in the chain will be executed after the previous job has completed successfully. + +```php +service('queue')->chain(function($chain) { + $chain + ->push('reports', 'generate-report', ['userId' => 123]) + ->setPriority('high') // optional + ->push('emails', 'email', ['message' => 'Email message goes here', 'userId' => 123]) + ->setDelay(30); // optional +}); +``` + +As you may notice, we can use the same options as in regular `push()` - we can set priority and delay, which are optional settings. + +#### Important Differences from Regular `push()` + +When using the `chain()` method, there are a few important differences compared to the regular `push()` method: + +1. **Method Order**: Unlike the regular `push()` method where you set the priority and delay before pushing the job, in a chain you must set these properties after calling `push()` for each job: + + ```php + // Regular push() - priority set before pushing + service('queue')->setPriority('high')->push('queue', 'job', []); + + // Chain push() - priority set after pushing + service('queue')->chain(function($chain) { + $chain->push('queue', 'job', [])->setPriority('high'); + }); + ``` + +2. **Configuration Scope**: Each configuration (priority, delay) only applies to the job that was just added to the chain. + ### Running many instances of the same queue As mentioned above, sometimes we may want to have multiple instances of the same command running at the same time. The queue is safe to use in that scenario with all databases as long as you keep the `skipLocked` to `true` in the config file. Only for SQLite3 driver, this setting is not relevant as it provides atomicity without the need for explicit concurrency control. diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 924b3e7..a8bbb27 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -23,6 +23,10 @@ parameters: message: '#Call to an undefined method CodeIgniter\\Queue\\Models\\QueueJobFailedModel::truncate\(\).#' paths: - src/Handlers/BaseHandler.php + - + message: '#If condition is always true.#' + paths: + - src/Commands/QueueWork.php universalObjectCratesClasses: - CodeIgniter\Entity - CodeIgniter\Entity\Entity diff --git a/rector.php b/rector.php index 68f6940..cec36f6 100644 --- a/rector.php +++ b/rector.php @@ -28,6 +28,7 @@ use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector; use Rector\Php55\Rector\String_\StringClassNameToClassConstantRector; use Rector\Php73\Rector\FuncCall\StringifyStrNeedlesRector; +use Rector\Php81\Rector\ClassMethod\NewInInitializerRector; use Rector\PHPUnit\AnnotationsToAttributes\Rector\Class_\AnnotationWithValueToAttributeRector; use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector; use Rector\PHPUnit\CodeQuality\Rector\Class_\YieldDataProviderRector; @@ -93,6 +94,10 @@ // Supported from PHPUnit 10 DataProviderAnnotationToAttributeRector::class, + + NewInInitializerRector::class => [ + 'src/Payloads/Payload.php', + ], ]); // auto import fully qualified class names diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index 6a4d4fb..bbb81ce 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -17,6 +17,7 @@ use CodeIgniter\CLI\CLI; use CodeIgniter\Queue\Config\Queue as QueueConfig; use CodeIgniter\Queue\Entities\QueueJob; +use CodeIgniter\Queue\Payloads\PayloadMetadata; use Exception; use Throwable; @@ -247,6 +248,11 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i service('queue')->done($work, $config->keepDoneJobs); CLI::write('The processing of this job was successful', 'green'); + + // Check chained jobs + if (isset($payload['metadata']) && $payload['metadata'] !== []) { + $this->processNextJobInChain($payload['metadata']); + } } catch (Throwable $err) { if (isset($job) && ++$work->attempts < ($tries ?? $job->getTries())) { // Schedule for later @@ -262,6 +268,43 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i } } + /** + * Process the next job in the chain + */ + private function processNextJobInChain(array $payloadMetadata): void + { + $payloadMetadata = PayloadMetadata::fromArray($payloadMetadata); + + if (! $payloadMetadata->hasChainedJobs()) { + return; + } + + $nextPayload = $payloadMetadata->getChainedJobs()->shift(); + $priority = $nextPayload->getPriority(); + $delay = $nextPayload->getDelay(); + + if ($priority !== null) { + service('queue')->setPriority($priority); + } + + if ($delay !== null) { + service('queue')->setDelay($delay); + } + + if ($payloadMetadata->hasChainedJobs()) { + $nextPayload->setChainedJobs($payloadMetadata->getChainedJobs()); + } + + service('queue')->push( + $nextPayload->getQueue(), + $nextPayload->getJob(), + $nextPayload->getData(), + $nextPayload->getMetadata(), + ); + + CLI::write(sprintf('Chained job: %s has been placed in the queue: %s', $nextPayload->getJob(), $nextPayload->getQueue()), 'green'); + } + private function maxJobsCheck(int $maxJobs, int $countJobs): bool { if ($maxJobs > 0 && $countJobs >= $maxJobs) { diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 63e0f87..03a6a57 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -13,12 +13,16 @@ namespace CodeIgniter\Queue\Handlers; +use Closure; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Config\Queue as QueueConfig; use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Entities\QueueJobFailed; use CodeIgniter\Queue\Exceptions\QueueException; use CodeIgniter\Queue\Models\QueueJobFailedModel; +use CodeIgniter\Queue\Payloads\ChainBuilder; +use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\Traits\HasQueueValidation; use ReflectionException; use Throwable; @@ -27,13 +31,15 @@ */ abstract class BaseHandler { + use HasQueueValidation; + protected QueueConfig $config; protected ?string $priority = null; protected ?int $delay = null; abstract public function name(): string; - abstract public function push(string $queue, string $job, array $data): bool; + abstract public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool; abstract public function pop(string $queue, array $priorities): ?QueueJob; @@ -45,38 +51,6 @@ abstract public function done(QueueJob $queueJob, bool $keepJob): bool; abstract public function clear(?string $queue = null): bool; - /** - * Set priority for job queue. - */ - public function setPriority(string $priority): static - { - if (! preg_match('/^[a-z_-]+$/', $priority)) { - throw QueueException::forIncorrectPriorityFormat(); - } - - if (strlen($priority) > 64) { - throw QueueException::forTooLongPriorityName(); - } - - $this->priority = $priority; - - return $this; - } - - /** - * Set delay for job queue (in seconds). - */ - public function setDelay(int $delay): static - { - if ($delay < 0) { - throw QueueException::forIncorrectDelayValue(); - } - - $this->delay = $delay; - - return $this; - } - /** * Retry failed job. * @@ -104,7 +78,7 @@ public function retry(?int $id, ?string $queue): int } /** - * Delete failed job by ID. + * Delete a failed job by ID. */ public function forget(int $id): bool { @@ -150,6 +124,43 @@ public function listFailed(?string $queue): array ->findAll(); } + /** + * Set delay for job queue (in seconds). + */ + public function setDelay(int $delay): static + { + $this->validateDelay($delay); + + $this->delay = $delay; + + return $this; + } + + /** + * Set priority for job queue. + */ + public function setPriority(string $priority): static + { + $this->validatePriority($priority); + + $this->priority = $priority; + + return $this; + } + + /** + * Create a job chain on the specified queue + * + * @param Closure $callback Chain definition callback + */ + public function chain(Closure $callback): bool + { + $chainBuilder = new ChainBuilder($this); + $callback($chainBuilder); + + return $chainBuilder->dispatch(); + } + /** * Log failed job. * diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 71bd497..88869b6 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -19,7 +19,8 @@ use CodeIgniter\Queue\Enums\Status; use CodeIgniter\Queue\Interfaces\QueueInterface; use CodeIgniter\Queue\Models\QueueJobModel; -use CodeIgniter\Queue\Payload; +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadMetadata; use ReflectionException; use Throwable; @@ -46,13 +47,13 @@ public function name(): string * * @throws ReflectionException */ - public function push(string $queue, string $job, array $data): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool { $this->validateJobAndPriority($queue, $job); $queueJob = new QueueJob([ 'queue' => $queue, - 'payload' => new Payload($job, $data), + 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index e781955..4b64990 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -20,7 +20,8 @@ use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; use CodeIgniter\Queue\Interfaces\QueueInterface; -use CodeIgniter\Queue\Payload; +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadMetadata; use Exception; use Predis\Client; use Throwable; @@ -58,7 +59,7 @@ public function name(): string /** * Add job to the queue. */ - public function push(string $queue, string $job, array $data): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool { $this->validateJobAndPriority($queue, $job); @@ -69,7 +70,7 @@ public function push(string $queue, string $job, array $data): bool $queueJob = new QueueJob([ 'id' => random_string('numeric', 16), 'queue' => $queue, - 'payload' => new Payload($job, $data), + 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index 9fc49f2..dc86d5b 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -20,7 +20,8 @@ use CodeIgniter\Queue\Entities\QueueJob; use CodeIgniter\Queue\Enums\Status; use CodeIgniter\Queue\Interfaces\QueueInterface; -use CodeIgniter\Queue\Payload; +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadMetadata; use Redis; use RedisException; use Throwable; @@ -75,7 +76,7 @@ public function name(): string * * @throws RedisException */ - public function push(string $queue, string $job, array $data): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool { $this->validateJobAndPriority($queue, $job); @@ -86,7 +87,7 @@ public function push(string $queue, string $job, array $data): bool $queueJob = new QueueJob([ 'id' => random_string('numeric', 16), 'queue' => $queue, - 'payload' => new Payload($job, $data), + 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, 'status' => Status::PENDING->value, 'attempts' => 0, diff --git a/src/Payload.php b/src/Payload.php deleted file mode 100644 index 9937fa0..0000000 --- a/src/Payload.php +++ /dev/null @@ -1,31 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace CodeIgniter\Queue; - -use JsonSerializable; - -class Payload implements JsonSerializable -{ - public function __construct(protected string $job, protected array $data) - { - } - - public function jsonSerialize(): array - { - return [ - 'job' => $this->job, - 'data' => $this->data, - ]; - } -} diff --git a/src/Payloads/ChainBuilder.php b/src/Payloads/ChainBuilder.php new file mode 100644 index 0000000..73479d2 --- /dev/null +++ b/src/Payloads/ChainBuilder.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Payloads; + +use CodeIgniter\Queue\Handlers\BaseHandler; + +class ChainBuilder +{ + /** + * Collection of jobs in the chain + */ + protected PayloadCollection $payloads; + + public function __construct(protected BaseHandler $handler) + { + $this->payloads = new PayloadCollection(); + } + + /** + * Add a job to the chain + */ + public function push(string $queue, string $jobName, array $data = []): ChainElement + { + $payload = new Payload($jobName, $data); + + $payload->setQueue($queue); + + $this->payloads->add($payload); + + return new ChainElement($payload, $this); + } + + /** + * Dispatch the chain of jobs + */ + public function dispatch(): bool + { + if ($this->payloads->count() === 0) { + return true; + } + + $current = $this->payloads->shift(); + $priority = $current->getPriority(); + $delay = $current->getDelay(); + + if ($priority !== null) { + $this->handler->setPriority($priority); + } + + if ($delay !== null) { + $this->handler->setDelay($delay); + } + + // Set chained jobs for the next job + if ($this->payloads->count() > 0) { + $current->setChainedJobs($this->payloads); + } + + // Push to the queue with the specified queue name + return $this->handler->push( + $current->getQueue(), + $current->getJob(), + $current->getData(), + $current->getMetadata(), + ); + } +} diff --git a/src/Payloads/ChainElement.php b/src/Payloads/ChainElement.php new file mode 100644 index 0000000..d8d7bc3 --- /dev/null +++ b/src/Payloads/ChainElement.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Payloads; + +class ChainElement +{ + public function __construct(protected Payload $payload, protected ChainBuilder $chainBuilder) + { + } + + /** + * Set priority for this specific job + */ + public function setPriority(string $priority): self + { + $this->payload->setPriority($priority); + + return $this; + } + + /** + * Set delay for this specific job + */ + public function setDelay(int $delay): self + { + $this->payload->setDelay($delay); + + return $this; + } + + /** + * Push the next job in the chain (method chaining) + */ + public function push(string $queue, string $jobName, array $data = []): ChainElement + { + return $this->chainBuilder->push($queue, $jobName, $data); + } +} diff --git a/src/Payloads/Payload.php b/src/Payloads/Payload.php new file mode 100644 index 0000000..8539a87 --- /dev/null +++ b/src/Payloads/Payload.php @@ -0,0 +1,150 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Payloads; + +use CodeIgniter\Queue\Exceptions\QueueException; +use CodeIgniter\Queue\Traits\HasQueueValidation; +use JsonSerializable; + +class Payload implements JsonSerializable +{ + use HasQueueValidation; + + /** + * Job metadata + */ + protected PayloadMetadata $metadata; + + public function __construct(protected string $job, protected array $data, ?PayloadMetadata $metadata = null) + { + $this->metadata = $metadata ?? new PayloadMetadata(); + } + + public function getJob(): string + { + return $this->job; + } + + public function getData(): array + { + return $this->data; + } + + public function getMetadata(): PayloadMetadata + { + return $this->metadata; + } + + public function setMetadata(PayloadMetadata $metadata): self + { + $this->metadata = $metadata; + + return $this; + } + + /** + * Set the queue name + * + * @throws QueueException + */ + public function setQueue(string $queue): self + { + $this->validateQueue($queue); + + $this->metadata->set('queue', $queue); + + return $this; + } + + public function getQueue(): ?string + { + return $this->metadata->get('queue'); + } + + /** + * Set the priority + * + * @throws QueueException + */ + public function setPriority(string $priority): self + { + $this->validatePriority($priority); + + $this->metadata->set('priority', $priority); + + return $this; + } + + public function getPriority(): ?string + { + return $this->metadata->get('priority'); + } + + /** + * Set the delay + * + * @throws QueueException + */ + public function setDelay(int $delay): self + { + $this->validateDelay($delay); + + $this->metadata->set('delay', $delay); + + return $this; + } + + public function getDelay(): ?int + { + return $this->metadata->get('delay'); + } + + public function setChainedJobs(PayloadCollection $payloads): self + { + $this->metadata->setChainedJobs($payloads); + + return $this; + } + + public function getChainedJobs(): ?PayloadCollection + { + return $this->metadata->getChainedJobs(); + } + + public function hasChainedJobs(): bool + { + return $this->metadata->hasChainedJobs(); + } + + public function jsonSerialize(): array + { + return [ + 'job' => $this->job, + 'data' => $this->data, + 'metadata' => $this->metadata, + ]; + } + + /** + * Create a Payload from an array + */ + public static function fromArray(array $data): self + { + $job = $data['job'] ?? ''; + $jobData = $data['data'] ?? []; + $metadata = isset($data['metadata']) ? PayloadMetadata::fromArray($data['metadata']) : null; + + return new self($job, $jobData, $metadata); + } +} diff --git a/src/Payloads/PayloadCollection.php b/src/Payloads/PayloadCollection.php new file mode 100644 index 0000000..871c96a --- /dev/null +++ b/src/Payloads/PayloadCollection.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Payloads; + +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +/** + * @template T + * + * @implements IteratorAggregate + */ +class PayloadCollection implements IteratorAggregate, Countable, JsonSerializable +{ + /** + * Create a new payload collection + * + * @param list $items + */ + public function __construct(protected array $items = []) + { + } + + /** + * Add a payload to the collection + */ + public function add(Payload $payload): self + { + $this->items[] = $payload; + + return $this; + } + + /** + * Get the first payload and remove it. + */ + public function shift(): ?Payload + { + if ($this->count() === 0) { + return null; + } + + return array_shift($this->items); + } + + /** + * Convert the collection to an array + */ + public function toArray(): array + { + $result = []; + + foreach ($this->items as $payload) { + $result[] = $payload->jsonSerialize(); + } + + return $result; + } + + public function jsonSerialize(): array + { + return $this->toArray(); + } + + public function count(): int + { + return count($this->items); + } + + /** + * @return ArrayIterator + */ + public function getIterator(): ArrayIterator + { + return new ArrayIterator($this->items); + } + + /** + * Create a new PayloadCollection from an array + */ + public static function fromArray(array $payloads): self + { + $collection = new self(); + + foreach ($payloads as $payload) { + if (isset($payload['job'], $payload['data'])) { + $collection->add(Payload::fromArray($payload)); + } + } + + return $collection; + } +} diff --git a/src/Payloads/PayloadMetadata.php b/src/Payloads/PayloadMetadata.php new file mode 100644 index 0000000..17c8833 --- /dev/null +++ b/src/Payloads/PayloadMetadata.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Payloads; + +use JsonSerializable; + +class PayloadMetadata implements JsonSerializable +{ + public function __construct(protected array $data = []) + { + } + + /** + * Set chained jobs + */ + public function setChainedJobs(?PayloadCollection $payloads): self + { + if ($payloads !== null) { + $this->data['chainedJobs'] = $payloads; + } else { + unset($this->data['chainedJobs']); + } + + return $this; + } + + /** + * Get chained jobs + */ + public function getChainedJobs(): ?PayloadCollection + { + return $this->data['chainedJobs'] ?? null; + } + + /** + * Check if has chained jobs + */ + public function hasChainedJobs(): bool + { + return isset($this->data['chainedJobs']) && $this->data['chainedJobs']->count() > 0; + } + + /** + * Set a generic metadata value + */ + public function set(string $key, mixed $value): self + { + $this->data[$key] = $value; + + return $this; + } + + /** + * Get a generic metadata value + * + * @param mixed|null $default + */ + public function get(string $key, $default = null) + { + return $this->data[$key] ?? $default; + } + + /** + * Check if a metadata key exists + */ + public function has(string $key): bool + { + return isset($this->data[$key]); + } + + /** + * Remove a metadata key + */ + public function remove(string $key): self + { + unset($this->data[$key]); + + return $this; + } + + /** + * Get all metadata as an array + */ + public function toArray(): array + { + return $this->data; + } + + /** + * JSON serialize implementation + */ + public function jsonSerialize(): array + { + return $this->data; + } + + public static function fromArray(array $data): PayloadMetadata + { + $metadata = new self(); + + foreach ($data as $key => $value) { + // Handle chainedJobs specially + if ($key === 'chainedJobs' && is_array($value)) { + $payloadCollection = new PayloadCollection(); + + foreach ($value as $jobData) { + if (isset($jobData['job'], $jobData['data'])) { + $payload = Payload::fromArray($jobData); + $payloadCollection->add($payload); + } + } + + $metadata->setChainedJobs($payloadCollection); + } else { + // Regular metadata + $metadata->set($key, $value); + } + } + + return $metadata; + } +} diff --git a/src/Traits/HasQueueValidation.php b/src/Traits/HasQueueValidation.php new file mode 100644 index 0000000..aa66593 --- /dev/null +++ b/src/Traits/HasQueueValidation.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue\Traits; + +use CodeIgniter\Queue\Exceptions\QueueException; + +trait HasQueueValidation +{ + /** + * Validate priority value. + * + * @throws QueueException + */ + protected function validatePriority(string $priority): void + { + if (! preg_match('/^[a-z_-]+$/', $priority)) { + throw QueueException::forIncorrectPriorityFormat(); + } + + if (strlen($priority) > 64) { + throw QueueException::forTooLongPriorityName(); + } + } + + /** + * Validate delay value. + * + * @throws QueueException + */ + protected function validateDelay(int $delay): void + { + if ($delay < 0) { + throw QueueException::forIncorrectDelayValue(); + } + } + + /** + * Validate queue name. + * + * @throws QueueException + */ + protected function validateQueue(string $queue): void + { + if (! preg_match('/^[a-z0-9_-]+$/', $queue)) { + throw QueueException::forIncorrectQueueFormat(); + } + + if (strlen($queue) > 64) { + throw QueueException::forTooLongQueueName(); + } + } +} diff --git a/tests/Commands/QueueWorkTest.php b/tests/Commands/QueueWorkTest.php index 8bbb294..dd5a13d 100644 --- a/tests/Commands/QueueWorkTest.php +++ b/tests/Commands/QueueWorkTest.php @@ -111,4 +111,48 @@ public function testRunWithQueueSucceed(): void $this->assertSame('The processing of this job was successful', $this->getLine(4)); $this->assertSame('No job available. Stopping.', $this->getLine(7)); } + + public function testRunWithChainedQueueSucceed(): void + { + Time::setTestNow('2023-12-19 14:15:16'); + + fake(QueueJobModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => [ + 'job' => 'success', + 'data' => ['key' => 'value'], + 'metadata' => [ + 'queue' => 'queue', + 'chainedJobs' => [ + [ + 'job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => [ + 'queue' => 'queue', + 'priority' => 'high', + 'delay' => 10, + ], + ], + ], + ], + ], + 'priority' => 'default', + 'status' => 0, + 'attempts' => 0, + 'available_at' => 1_702_977_074, + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test sleep 1 --stop-when-empty')); + $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Listening for the jobs with the queue: test', $this->getLine(0)); + $this->assertSame('Starting a new job: success, with ID: 1', $this->getLine(3)); + $this->assertSame('The processing of this job was successful', $this->getLine(4)); + $this->assertSame('Chained job: success has been placed in the queue: queue', $this->getLine(5)); + $this->assertSame('No job available. Stopping.', $this->getLine(8)); + } } diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 4c5b159..507f917 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -88,7 +88,7 @@ public function testPush(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), 'available_at' => 1703859316, ]); } @@ -106,7 +106,7 @@ public function testPushWithPriority(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), 'priority' => 'high', 'available_at' => 1703859316, ]); @@ -125,7 +125,7 @@ public function testPushAndPopWithPriority(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]), 'priority' => 'low', 'available_at' => 1703859316, ]); @@ -135,19 +135,19 @@ public function testPushAndPopWithPriority(): void $this->assertTrue($result); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', - 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]), 'priority' => 'high', 'available_at' => 1703859316, ]); $result = $handler->pop('queue', ['high', 'low']); $this->assertInstanceOf(QueueJob::class, $result); - $payload = ['job' => 'success', 'data' => ['key2' => 'value2']]; + $payload = ['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]; $this->assertSame($payload, $result->payload); $result = $handler->pop('queue', ['high', 'low']); $this->assertInstanceOf(QueueJob::class, $result); - $payload = ['job' => 'success', 'data' => ['key1' => 'value1']]; + $payload = ['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]; $this->assertSame($payload, $result->payload); } @@ -167,13 +167,87 @@ public function testPushWithDelay(): void $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue-delay', - 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), 'available_at' => $availableAt, ]); $this->assertEqualsWithDelta(MINUTE, $availableAt - Time::now()->getTimestamp(), 1); } + /** + * @throws Exception + */ + public function testChain(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new DatabaseHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->push('queue', 'success', ['key2' => 'value2']); + }); + + $this->assertTrue($result); + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue', + 'payload' => json_encode([ + 'job' => 'success', + 'data' => ['key1' => 'value1'], + 'metadata' => [ + 'queue' => 'queue', + 'chainedJobs' => [ + [ + 'job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => ['queue' => 'queue']], + ], + ], + ]), + 'available_at' => 1703859316, + ]); + } + + public function testChainWithPriorityAndDelay(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new DatabaseHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->setPriority('high') + ->setDelay(60) + ->push('queue', 'success', ['key2' => 'value2']) + ->setPriority('low') + ->setDelay(120); + }); + + $this->assertTrue($result); + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue', + 'payload' => json_encode([ + 'job' => 'success', + 'data' => ['key1' => 'value1'], + 'metadata' => [ + 'queue' => 'queue', + 'priority' => 'high', + 'delay' => 60, + 'chainedJobs' => [ + [ + 'job' => 'success', + 'data' => ['key2' => 'value2'], + 'metadata' => [ + 'queue' => 'queue', + 'priority' => 'low', + 'delay' => 120, + ], + ], + ], + ], + ]), + 'available_at' => 1703859316 + 60, // Adding delay to available_at + ]); + } + public function testPushWithDelayException(): void { $this->expectException(QueueException::class); @@ -384,7 +458,7 @@ public function testRetry(): void $this->seeInDatabase('queue_jobs', [ 'id' => 3, 'queue' => 'queue1', - 'payload' => json_encode(['job' => 'failure', 'data' => []]), + 'payload' => json_encode(['job' => 'failure', 'data' => [], 'metadata' => []]), ]); $this->dontSeeInDatabase('queue_jobs_failed', [ 'id' => 1, diff --git a/tests/Payloads/ChainBuilderTest.php b/tests/Payloads/ChainBuilderTest.php new file mode 100644 index 0000000..304f5ae --- /dev/null +++ b/tests/Payloads/ChainBuilderTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests\Payloads; + +use CodeIgniter\I18n\Time; +use CodeIgniter\Queue\Handlers\DatabaseHandler; +use CodeIgniter\Queue\Payloads\ChainBuilder; +use CodeIgniter\Queue\Payloads\ChainElement; +use Tests\Support\Config\Queue as QueueConfig; +use Tests\Support\Database\Seeds\TestDatabaseQueueSeeder; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class ChainBuilderTest extends TestCase +{ + protected $seed = TestDatabaseQueueSeeder::class; + private QueueConfig $config; + + protected function setUp(): void + { + parent::setUp(); + + $this->config = config(QueueConfig::class); + } + + public function testChainBuilder(): void + { + $handler = new DatabaseHandler($this->config); + $chainBuilder = new ChainBuilder($handler); + + $this->assertInstanceOf(ChainBuilder::class, $chainBuilder); + } + + public function testPush(): void + { + $handler = new DatabaseHandler($this->config); + $chainBuilder = new ChainBuilder($handler); + + $chainElement = $chainBuilder->push('queue', 'job', ['data' => 'value']); + + $this->assertInstanceOf(ChainElement::class, $chainElement); + } + + public function testChainWithSingleJob(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new DatabaseHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain->push('queue', 'success', ['key' => 'value']); + }); + + $this->assertTrue($result); + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue', + 'payload' => json_encode([ + 'job' => 'success', + 'data' => ['key' => 'value'], + 'metadata' => [ + 'queue' => 'queue', + ], + ]), + 'available_at' => 1703859316, + ]); + } + + public function testEmptyChain(): void + { + $handler = new DatabaseHandler($this->config); + $result = $handler->chain(static function ($chain): void { + // No jobs added + }); + + $this->assertTrue($result); + $this->seeInDatabase('queue_jobs', []); + } + + public function testMultipleDifferentQueues(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new DatabaseHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue1', 'success', ['key1' => 'value1']) + ->push('queue2', 'success', ['key2' => 'value2']); + }); + + $this->assertTrue($result); + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue1', + 'payload' => json_encode([ + 'job' => 'success', + 'data' => ['key1' => 'value1'], + 'metadata' => [ + 'queue' => 'queue1', + 'chainedJobs' => [ + [ + 'job' => 'success', + 'data' => ['key2' => 'value2'], + 'metadata' => ['queue' => 'queue2'], + ], + ], + ], + ]), + 'available_at' => 1703859316, + ]); + } + + public function testChainWithManyJobs(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new DatabaseHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->push('queue', 'success', ['key2' => 'value2']) + ->push('queue', 'success', ['key3' => 'value3']); + }); + + $this->assertTrue($result); + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue', + 'payload' => json_encode([ + 'job' => 'success', + 'data' => ['key1' => 'value1'], + 'metadata' => [ + 'queue' => 'queue', + 'chainedJobs' => [ + [ + 'job' => 'success', + 'data' => ['key2' => 'value2'], + 'metadata' => ['queue' => 'queue'], + ], + [ + 'job' => 'success', + 'data' => ['key3' => 'value3'], + 'metadata' => ['queue' => 'queue'], + ], + ], + ], + ]), + 'available_at' => 1703859316, + ]); + } +} diff --git a/tests/Payloads/ChainElementTest.php b/tests/Payloads/ChainElementTest.php new file mode 100644 index 0000000..2bb232a --- /dev/null +++ b/tests/Payloads/ChainElementTest.php @@ -0,0 +1,109 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace App\ThirdParty\queue\tests\Payloads; + +use CodeIgniter\Queue\Payloads\ChainBuilder; +use CodeIgniter\Queue\Payloads\ChainElement; +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadMetadata; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class ChainElementTest extends TestCase +{ + private Payload $payload; + private ChainBuilder $chainBuilder; + private ChainElement $chainElement; + + protected function setUp(): void + { + parent::setUp(); + + // Create a payload object + $this->payload = new Payload('job', ['key' => 'value']); + $this->payload->setQueue('queue'); + + // Create a mock ChainBuilder + $this->chainBuilder = $this->createMock(ChainBuilder::class); + + // Create the ChainElement to test + $this->chainElement = new ChainElement($this->payload, $this->chainBuilder); + } + + public function testSetPriority(): void + { + $result = $this->chainElement->setPriority('high'); + + $this->assertInstanceOf(ChainElement::class, $result); + $this->assertSame('high', $this->payload->getPriority()); + } + + public function testSetDelay(): void + { + $result = $this->chainElement->setDelay(60); + + $this->assertInstanceOf(ChainElement::class, $result); + $this->assertSame(60, $this->payload->getDelay()); + } + + public function testPush(): void + { + $nextPayload = new Payload('nextJob', ['nextKey' => 'nextValue']); + $nextElement = new ChainElement($nextPayload, $this->chainBuilder); + + /** @phpstan-ignore-next-line */ + $this->chainBuilder->expects($this->once()) + ->method('push') + ->with('queue2', 'job2', ['data' => 'value2']) + ->willReturn($nextElement); + + $result = $this->chainElement->push('queue2', 'job2', ['data' => 'value2']); + + $this->assertInstanceOf(ChainElement::class, $result); + $this->assertSame($nextElement, $result); + } + + public function testMultipleMethodChaining(): void + { + $chainBuilder = $this->createMock(ChainBuilder::class); + $chainBuilder->method('push')->willReturnSelf(); + + $payload = new Payload('job', ['key' => 'value']); + + $chainElement = new ChainElement($payload, $chainBuilder); + + $chainElement + ->setPriority('medium') + ->setDelay(30); + + $this->assertSame('medium', $payload->getPriority()); + $this->assertSame(30, $payload->getDelay()); + } + + public function testCorrectMetadataModification(): void + { + $metadata = new PayloadMetadata(); + $payload = new Payload('job', ['key' => 'value'], $metadata); + + $chainElement = new ChainElement($payload, $this->chainBuilder); + + $chainElement->setPriority('low'); + $chainElement->setDelay(120); + + $this->assertSame('low', $payload->getMetadata()->get('priority')); + $this->assertSame(120, $payload->getMetadata()->get('delay')); + } +} diff --git a/tests/Payloads/PayloadCollectionTest.php b/tests/Payloads/PayloadCollectionTest.php new file mode 100644 index 0000000..13ef3d6 --- /dev/null +++ b/tests/Payloads/PayloadCollectionTest.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace App\ThirdParty\queue\tests\Payloads; + +use ArrayIterator; +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadCollection; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class PayloadCollectionTest extends TestCase +{ + private PayloadCollection $collection; + private Payload $payload1; + private Payload $payload2; + + protected function setUp(): void + { + parent::setUp(); + + // Create sample payloads + $this->payload1 = new Payload('job1', ['key1' => 'value1']); + $this->payload1->setQueue('queue1'); + + $this->payload2 = new Payload('job2', ['key2' => 'value2']); + $this->payload2->setQueue('queue2'); + + // Create an empty collection + $this->collection = new PayloadCollection(); + } + + public function testEmptyCollectionCount(): void + { + $this->assertCount(0, $this->collection); + } + + public function testAddPayload(): void + { + $result = $this->collection->add($this->payload1); + + $this->assertInstanceOf(PayloadCollection::class, $result); + $this->assertCount(1, $this->collection); + } + + public function testAddMultiplePayloads(): void + { + $this->collection->add($this->payload1); + $this->collection->add($this->payload2); + + $this->assertCount(2, $this->collection); + } + + public function testShiftPayload(): void + { + $this->collection->add($this->payload1); + $this->collection->add($this->payload2); + + $first = $this->collection->shift(); + + $this->assertSame($this->payload1, $first); + $this->assertCount(1, $this->collection); + } + + public function testShiftFromEmptyCollection(): void + { + $result = $this->collection->shift(); + + $this->assertNull($result); + } + + public function testGetIterator(): void + { + $this->collection->add($this->payload1); + $this->collection->add($this->payload2); + + $iterator = $this->collection->getIterator(); + + $this->assertInstanceOf(ArrayIterator::class, $iterator); + $this->assertCount(2, $iterator); + } + + public function testToArray(): void + { + $this->collection->add($this->payload1); + $this->collection->add($this->payload2); + + $array = $this->collection->toArray(); + + $this->assertCount(2, $array); + + // Check array structure + $this->assertArrayHasKey('job', $array[0]); + $this->assertArrayHasKey('data', $array[0]); + $this->assertArrayHasKey('metadata', $array[0]); + + $this->assertSame('job1', $array[0]['job']); + $this->assertSame(['key1' => 'value1'], $array[0]['data']); + } + + public function testJsonSerialize(): void + { + $this->collection->add($this->payload1); + $this->collection->add($this->payload2); + + $json = json_encode($this->collection); + $decoded = json_decode($json, true); + + $this->assertIsArray($decoded); + $this->assertCount(2, $decoded); + $this->assertSame('job1', $decoded[0]['job']); + $this->assertSame('job2', $decoded[1]['job']); + } + + public function testFromArray(): void + { + $arrayData = [ + [ + 'job' => 'job1', + 'data' => ['key1' => 'value1'], + 'metadata' => ['queue' => 'queue1'], + ], + [ + 'job' => 'job2', + 'data' => ['key2' => 'value2'], + 'metadata' => ['queue' => 'queue2'], + ], + ]; + + $collection = PayloadCollection::fromArray($arrayData); + + $this->assertInstanceOf(PayloadCollection::class, $collection); + $this->assertCount(2, $collection); + + $first = $collection->shift(); + $this->assertInstanceOf(Payload::class, $first); + $this->assertSame('job1', $first->getJob()); + $this->assertSame(['key1' => 'value1'], $first->getData()); + } + + public function testInvalidDataInFromArray(): void + { + $arrayData = [ + ['invalid' => 'data'], // Missing job and data + [ + 'job' => 'job2', + 'data' => ['key2' => 'value2'], + ], + ]; + + $collection = PayloadCollection::fromArray($arrayData); + + // Should only have created one valid payload + $this->assertCount(1, $collection); + } + + public function testIteration(): void + { + $this->collection->add($this->payload1); + $this->collection->add($this->payload2); + + $count = 0; + $jobs = []; + + foreach ($this->collection as $payload) { + $count++; + $jobs[] = $payload->getJob(); + } + + $this->assertSame(2, $count); + $this->assertSame(['job1', 'job2'], $jobs); + } +} diff --git a/tests/Payloads/PayloadMetadataTest.php b/tests/Payloads/PayloadMetadataTest.php new file mode 100644 index 0000000..e0af535 --- /dev/null +++ b/tests/Payloads/PayloadMetadataTest.php @@ -0,0 +1,233 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace App\ThirdParty\queue\tests\Payloads; + +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadCollection; +use CodeIgniter\Queue\Payloads\PayloadMetadata; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class PayloadMetadataTest extends TestCase +{ + private PayloadMetadata $metadata; + + protected function setUp(): void + { + parent::setUp(); + $this->metadata = new PayloadMetadata(); + } + + public function testEmptyMetadata(): void + { + $this->assertSame([], $this->metadata->toArray()); + } + + public function testSetAndGetGenericValue(): void + { + $this->metadata->set('key', 'value'); + + $this->assertSame('value', $this->metadata->get('key')); + } + + public function testGetWithDefault(): void + { + $this->assertSame('default', $this->metadata->get('nonexistent', 'default')); + } + + public function testHasKey(): void + { + $this->metadata->set('key', 'value'); + + $this->assertTrue($this->metadata->has('key')); + $this->assertFalse($this->metadata->has('nonexistent')); + } + + public function testRemoveKey(): void + { + $this->metadata->set('key', 'value'); + $this->metadata->remove('key'); + + $this->assertFalse($this->metadata->has('key')); + } + + public function testSetAndGetChainedJobs(): void + { + $payload1 = new Payload('job1', ['key1' => 'value1']); + $payload2 = new Payload('job2', ['key2' => 'value2']); + + $payloads = new PayloadCollection(); + $payloads->add($payload1); + $payloads->add($payload2); + + $this->metadata->setChainedJobs($payloads); + + $result = $this->metadata->getChainedJobs(); + + $this->assertInstanceOf(PayloadCollection::class, $result); + $this->assertCount(2, $result); + } + + public function testSetChainedJobsToNull(): void + { + $payload = new Payload('job', ['key' => 'value']); + $payloads = new PayloadCollection(); + $payloads->add($payload); + + $this->metadata->setChainedJobs($payloads); + + // Then set to null + $this->metadata->setChainedJobs(null); + + $this->assertNull($this->metadata->getChainedJobs()); + $this->assertFalse($this->metadata->hasChainedJobs()); + } + + public function testHasChainedJobs(): void + { + $this->assertFalse($this->metadata->hasChainedJobs()); + + $payload = new Payload('job', ['key' => 'value']); + $payloads = new PayloadCollection(); + $payloads->add($payload); + + $this->metadata->setChainedJobs($payloads); + + $this->assertTrue($this->metadata->hasChainedJobs()); + } + + public function testHasChainedJobsWithEmptyCollection(): void + { + $emptyCollection = new PayloadCollection(); + $this->metadata->setChainedJobs($emptyCollection); + + $this->assertFalse($this->metadata->hasChainedJobs()); + } + + public function testJsonSerialize(): void + { + $this->metadata->set('queue', 'default'); + $this->metadata->set('priority', 'high'); + + $json = json_encode($this->metadata); + $decoded = json_decode($json, true); + + $this->assertIsArray($decoded); + $this->assertArrayHasKey('queue', $decoded); + $this->assertArrayHasKey('priority', $decoded); + $this->assertSame('default', $decoded['queue']); + $this->assertSame('high', $decoded['priority']); + } + + public function testJsonSerializeWithChainedJobs(): void + { + $payload = new Payload('job', ['key' => 'value']); + $payload->setQueue('queue'); + + $payloads = new PayloadCollection(); + $payloads->add($payload); + + $this->metadata->setChainedJobs($payloads); + + $json = json_encode($this->metadata); + $decoded = json_decode($json, true); + + $this->assertIsArray($decoded); + $this->assertArrayHasKey('chainedJobs', $decoded); + $this->assertIsArray($decoded['chainedJobs']); + $this->assertCount(1, $decoded['chainedJobs']); + $this->assertSame('job', $decoded['chainedJobs'][0]['job']); + } + + public function testFromArray(): void + { + $data = [ + 'queue' => 'default', + 'priority' => 'high', + 'delay' => 60, + ]; + + $metadata = PayloadMetadata::fromArray($data); + + $this->assertSame('default', $metadata->get('queue')); + $this->assertSame('high', $metadata->get('priority')); + $this->assertSame(60, $metadata->get('delay')); + } + + public function testFromArrayWithChainedJobs(): void + { + $data = [ + 'queue' => 'default', + 'chainedJobs' => [ + [ + 'job' => 'job1', + 'data' => ['key1' => 'value1'], + 'metadata' => ['queue' => 'queue1'], + ], + [ + 'job' => 'job2', + 'data' => ['key2' => 'value2'], + 'metadata' => ['queue' => 'queue2'], + ], + ], + ]; + + $metadata = PayloadMetadata::fromArray($data); + + $this->assertSame('default', $metadata->get('queue')); + $this->assertTrue($metadata->hasChainedJobs()); + + $chainedJobs = $metadata->getChainedJobs(); + $this->assertInstanceOf(PayloadCollection::class, $chainedJobs); + $this->assertCount(2, $chainedJobs); + + $job1 = $chainedJobs->shift(); + $this->assertSame('job1', $job1->getJob()); + $this->assertSame(['key1' => 'value1'], $job1->getData()); + $this->assertSame('queue1', $job1->getQueue()); + } + + public function testFromArrayWithInvalidChainedJobs(): void + { + $data = [ + 'chainedJobs' => [ + ['invalid' => 'data'], // Missing job and data + [ + 'job' => 'job2', + 'data' => ['key2' => 'value2'], + ], + ], + ]; + + $metadata = PayloadMetadata::fromArray($data); + + $this->assertTrue($metadata->hasChainedJobs()); + $this->assertSame(1, $metadata->getChainedJobs()->count()); + } + + public function testToArray(): void + { + $this->metadata->set('queue', 'default'); + $this->metadata->set('priority', 'high'); + + $array = $this->metadata->toArray(); + + $this->assertArrayHasKey('queue', $array); + $this->assertArrayHasKey('priority', $array); + $this->assertSame('default', $array['queue']); + $this->assertSame('high', $array['priority']); + } +} diff --git a/tests/Payloads/PayloadTest.php b/tests/Payloads/PayloadTest.php new file mode 100644 index 0000000..570cff2 --- /dev/null +++ b/tests/Payloads/PayloadTest.php @@ -0,0 +1,295 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace App\ThirdParty\queue\tests\Payloads; + +use CodeIgniter\Queue\Exceptions\QueueException; +use CodeIgniter\Queue\Payloads\Payload; +use CodeIgniter\Queue\Payloads\PayloadCollection; +use CodeIgniter\Queue\Payloads\PayloadMetadata; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class PayloadTest extends TestCase +{ + private Payload $payload; + + protected function setUp(): void + { + parent::setUp(); + $this->payload = new Payload('job', ['key' => 'value']); + } + + public function testConstructor(): void + { + $this->assertSame('job', $this->payload->getJob()); + $this->assertSame(['key' => 'value'], $this->payload->getData()); + } + + public function testConstructorWithMetadata(): void + { + $metadata = new PayloadMetadata(); + $metadata->set('priority', 'high'); + + $payload = new Payload('job', ['key' => 'value'], $metadata); + + $this->assertSame('high', $payload->getMetadata()->get('priority')); + } + + public function testGetJob(): void + { + $this->assertSame('job', $this->payload->getJob()); + } + + public function testGetData(): void + { + $this->assertSame(['key' => 'value'], $this->payload->getData()); + } + + public function testGetMetadata(): void + { + $metadata = $this->payload->getMetadata(); + + $this->assertInstanceOf(PayloadMetadata::class, $metadata); + } + + public function testSetMetadata(): void + { + $metadata = new PayloadMetadata(); + $metadata->set('priority', 'high'); + + $result = $this->payload->setMetadata($metadata); + + $this->assertInstanceOf(Payload::class, $result); + $this->assertSame('high', $this->payload->getMetadata()->get('priority')); + } + + public function testSetQueue(): void + { + $result = $this->payload->setQueue('queue'); + + $this->assertInstanceOf(Payload::class, $result); + $this->assertSame('queue', $this->payload->getQueue()); + } + + public function testSetQueueWithInvalidFormat(): void + { + $this->expectException(QueueException::class); + + $this->payload->setQueue('invalid queue name!'); + } + + public function testSetQueueWithTooLongName(): void + { + $this->expectException(QueueException::class); + + $this->payload->setQueue(str_repeat('a', 65)); // 65 characters, too long + } + + public function testGetQueue(): void + { + $this->payload->setQueue('queue'); + + $this->assertSame('queue', $this->payload->getQueue()); + } + + public function testSetPriority(): void + { + $result = $this->payload->setPriority('high'); + + $this->assertInstanceOf(Payload::class, $result); + $this->assertSame('high', $this->payload->getPriority()); + } + + public function testSetPriorityWithInvalidFormat(): void + { + $this->expectException(QueueException::class); + + $this->payload->setPriority('invalid priority!'); + } + + public function testSetPriorityWithTooLongName(): void + { + $this->expectException(QueueException::class); + + $this->payload->setPriority(str_repeat('a', 65)); // 65 characters, too long + } + + public function testGetPriority(): void + { + $this->payload->setPriority('high'); + + $this->assertSame('high', $this->payload->getPriority()); + } + + public function testSetDelay(): void + { + $result = $this->payload->setDelay(60); + + $this->assertInstanceOf(Payload::class, $result); + $this->assertSame(60, $this->payload->getDelay()); + } + + public function testSetDelayWithNegativeValue(): void + { + $this->expectException(QueueException::class); + + $this->payload->setDelay(-1); + } + + public function testGetDelay(): void + { + $this->payload->setDelay(60); + + $this->assertSame(60, $this->payload->getDelay()); + } + + public function testSetChainedJobs(): void + { + $payloads = new PayloadCollection(); + $payloads->add(new Payload('nextJob', ['nextKey' => 'nextValue'])); + + $result = $this->payload->setChainedJobs($payloads); + + $this->assertInstanceOf(Payload::class, $result); + $this->assertTrue($this->payload->hasChainedJobs()); + } + + public function testGetChainedJobs(): void + { + $payloads = new PayloadCollection(); + $payloads->add(new Payload('nextJob', ['nextKey' => 'nextValue'])); + + $this->payload->setChainedJobs($payloads); + $chainedJobs = $this->payload->getChainedJobs(); + + $this->assertInstanceOf(PayloadCollection::class, $chainedJobs); + $this->assertCount(1, $chainedJobs); + } + + public function testHasChainedJobs(): void + { + $this->assertFalse($this->payload->hasChainedJobs()); + + $payloads = new PayloadCollection(); + $payloads->add(new Payload('nextJob', ['nextKey' => 'nextValue'])); + + $this->payload->setChainedJobs($payloads); + + $this->assertTrue($this->payload->hasChainedJobs()); + } + + public function testJsonSerialize(): void + { + $this->payload->setQueue('queue'); + $this->payload->setPriority('high'); + + $json = json_encode($this->payload); + $decoded = json_decode($json, true); + + $this->assertIsArray($decoded); + $this->assertSame('job', $decoded['job']); + $this->assertSame(['key' => 'value'], $decoded['data']); + $this->assertIsArray($decoded['metadata']); + $this->assertSame('queue', $decoded['metadata']['queue']); + $this->assertSame('high', $decoded['metadata']['priority']); + } + + public function testJsonSerializeWithChainedJobs(): void + { + $this->payload->setQueue('queue'); + + $nextPayload = new Payload('nextJob', ['nextKey' => 'nextValue']); + $nextPayload->setQueue('queue'); + + $payloads = new PayloadCollection(); + $payloads->add($nextPayload); + + $this->payload->setChainedJobs($payloads); + + $json = json_encode($this->payload); + $decoded = json_decode($json, true); + + $this->assertIsArray($decoded); + $this->assertArrayHasKey('metadata', $decoded); + $this->assertArrayHasKey('chainedJobs', $decoded['metadata']); + $this->assertIsArray($decoded['metadata']['chainedJobs']); + $this->assertCount(1, $decoded['metadata']['chainedJobs']); + $this->assertSame('nextJob', $decoded['metadata']['chainedJobs'][0]['job']); + $this->assertSame(['nextKey' => 'nextValue'], $decoded['metadata']['chainedJobs'][0]['data']); + } + + public function testFromArray(): void + { + $data = [ + 'job' => 'job', + 'data' => ['key' => 'value'], + 'metadata' => [ + 'queue' => 'queue', + 'priority' => 'high', + ], + ]; + + $payload = Payload::fromArray($data); + + $this->assertSame('job', $payload->getJob()); + $this->assertSame(['key' => 'value'], $payload->getData()); + $this->assertSame('queue', $payload->getQueue()); + $this->assertSame('high', $payload->getPriority()); + } + + public function testFromArrayWithChainedJobs(): void + { + $data = [ + 'job' => 'job', + 'data' => ['key' => 'value'], + 'metadata' => [ + 'queue' => 'queue', + 'chainedJobs' => [ + [ + 'job' => 'nextJob', + 'data' => ['nextKey' => 'nextValue'], + 'metadata' => ['queue' => 'nextQueue'], + ], + ], + ], + ]; + + $payload = Payload::fromArray($data); + + $this->assertTrue($payload->hasChainedJobs()); + $chainedJobs = $payload->getChainedJobs(); + $this->assertCount(1, $chainedJobs); + + $nextJob = $chainedJobs->shift(); + $this->assertSame('nextJob', $nextJob->getJob()); + $this->assertSame(['nextKey' => 'nextValue'], $nextJob->getData()); + $this->assertSame('nextQueue', $nextJob->getQueue()); + } + + public function testMultipleValidations(): void + { + $payload = new Payload('job', ['key' => 'value']); + + // Test that all validations pass + $payload->setQueue('valid-queue'); + $payload->setPriority('valid-priority'); + $payload->setDelay(30); + + $this->assertSame('valid-queue', $payload->getQueue()); + $this->assertSame('valid-priority', $payload->getPriority()); + $this->assertSame(30, $payload->getDelay()); + } +} diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 148adea..226732b 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -81,6 +81,7 @@ public function testPush(): void $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + $this->assertSame([], $queueJob->payload['metadata']); } /** @@ -100,6 +101,7 @@ public function testPushWithPriority(): void $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + $this->assertSame([], $queueJob->payload['metadata']); } /** @@ -121,6 +123,92 @@ public function testPushWithDelay(): void $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + $this->assertSame([], $queueJob->payload['metadata']); + } + + /** + * @throws Exception + */ + public function testChain(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new PredisHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->push('queue', 'success', ['key2' => 'value2']); + }); + + $this->assertTrue($result); + + $predis = self::getPrivateProperty($handler, 'predis'); + $this->assertSame(1, $predis->zcard('queues:queue:low')); + + $task = $predis->zrangebyscore('queues:queue:low', '-inf', Time::now()->timestamp, ['limit' => [0, 1]]); + $job = new QueueJob(json_decode((string) $task[0], true)); + + $this->assertSame('success', $job->payload['job']); + $this->assertSame(['key1' => 'value1'], $job->payload['data']); + $this->assertArrayHasKey('metadata', $job->payload); + $this->assertArrayHasKey('queue', $job->payload['metadata']); + $this->assertSame('queue', $job->payload['metadata']['queue']); + $this->assertArrayHasKey('chainedJobs', $job->payload['metadata']); + + $chainedJobs = $job->payload['metadata']['chainedJobs']; + $this->assertCount(1, $chainedJobs); + $this->assertSame('success', $chainedJobs[0]['job']); + $this->assertSame(['key2' => 'value2'], $chainedJobs[0]['data']); + $this->assertSame('queue', $chainedJobs[0]['metadata']['queue']); + } + + /** + * @throws Exception + */ + public function testChainWithPriorityAndDelay(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new PredisHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->setPriority('high') + ->setDelay(60) + ->push('queue', 'success', ['key2' => 'value2']) + ->setPriority('low') + ->setDelay(120); + }); + + $this->assertTrue($result); + + $predis = self::getPrivateProperty($handler, 'predis'); + // Should be in high priority queue + $this->assertSame(1, $predis->zcard('queues:queue:high')); + + // Check with delay + $task = $predis->zrangebyscore('queues:queue:high', '-inf', Time::now()->addSeconds(61)->timestamp, ['limit' => [0, 1]]); + $queueJob = new QueueJob(json_decode((string) $task[0], true)); + + $this->assertSame('success', $queueJob->payload['job']); + $this->assertSame(['key1' => 'value1'], $queueJob->payload['data']); + $this->assertArrayHasKey('metadata', $queueJob->payload); + + // Check metadata + $meta = $queueJob->payload['metadata']; + $this->assertSame('queue', $meta['queue']); + $this->assertSame('high', $meta['priority']); + $this->assertSame(60, $meta['delay']); + + // Check a chained job with its priority and delay + $this->assertArrayHasKey('chainedJobs', $meta); + $chainedJobs = $meta['chainedJobs']; + $this->assertCount(1, $chainedJobs); + $this->assertSame('success', $chainedJobs[0]['job']); + $this->assertSame(['key2' => 'value2'], $chainedJobs[0]['data']); + $this->assertSame('queue', $chainedJobs[0]['metadata']['queue']); + $this->assertSame('low', $chainedJobs[0]['metadata']['priority']); + $this->assertSame(120, $chainedJobs[0]['metadata']['delay']); } public function testPushException(): void diff --git a/tests/PushAndPopWithDelayTest.php b/tests/PushAndPopWithDelayTest.php index cb2d0c1..db49b81 100644 --- a/tests/PushAndPopWithDelayTest.php +++ b/tests/PushAndPopWithDelayTest.php @@ -73,20 +73,20 @@ public function testPushAndPopWithDelay(string $name, string $class): void if ($name === 'database') { $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue-delay', - 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]), 'available_at' => 1703859376, ]); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue-delay', - 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2']]), + 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]), 'available_at' => 1703859316, ]); } $result = $handler->pop('queue-delay', ['default']); $this->assertInstanceOf(QueueJob::class, $result); - $payload = ['job' => 'success', 'data' => ['key2' => 'value2']]; + $payload = ['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]; $this->assertSame($payload, $result->payload); $result = $handler->pop('queue-delay', ['default']); @@ -97,7 +97,7 @@ public function testPushAndPopWithDelay(string $name, string $class): void $result = $handler->pop('queue-delay', ['default']); $this->assertInstanceOf(QueueJob::class, $result); - $payload = ['job' => 'success', 'data' => ['key1' => 'value1']]; + $payload = ['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]; $this->assertSame($payload, $result->payload); } } diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index 57178eb..aa71240 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -78,6 +78,7 @@ public function testPush(): void $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + $this->assertSame([], $queueJob->payload['metadata']); } public function testPushWithPriority(): void @@ -94,6 +95,7 @@ public function testPushWithPriority(): void $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + $this->assertSame([], $queueJob->payload['metadata']); } /** @@ -115,6 +117,92 @@ public function testPushWithDelay(): void $queueJob = new QueueJob(json_decode((string) $task[0], true)); $this->assertSame('success', $queueJob->payload['job']); $this->assertSame(['key' => 'value'], $queueJob->payload['data']); + $this->assertSame([], $queueJob->payload['metadata']); + } + + /** + * @throws Exception + */ + public function testChain(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new RedisHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->push('queue', 'success', ['key2' => 'value2']); + }); + + $this->assertTrue($result); + + $redis = self::getPrivateProperty($handler, 'redis'); + $this->assertSame(1, $redis->zCard('queues:queue:low')); + + $task = $redis->zRangeByScore('queues:queue:low', '-inf', Time::now()->timestamp, ['limit' => [0, 1]]); + $queueJob = new QueueJob(json_decode((string) $task[0], true)); + + $this->assertSame('success', $queueJob->payload['job']); + $this->assertSame(['key1' => 'value1'], $queueJob->payload['data']); + $this->assertArrayHasKey('metadata', $queueJob->payload); + $this->assertArrayHasKey('queue', $queueJob->payload['metadata']); + $this->assertSame('queue', $queueJob->payload['metadata']['queue']); + $this->assertArrayHasKey('chainedJobs', $queueJob->payload['metadata']); + + $chainedJobs = $queueJob->payload['metadata']['chainedJobs']; + $this->assertCount(1, $chainedJobs); + $this->assertSame('success', $chainedJobs[0]['job']); + $this->assertSame(['key2' => 'value2'], $chainedJobs[0]['data']); + $this->assertSame('queue', $chainedJobs[0]['metadata']['queue']); + } + + /** + * @throws Exception + */ + public function testChainWithPriorityAndDelay(): void + { + Time::setTestNow('2023-12-29 14:15:16'); + + $handler = new RedisHandler($this->config); + $result = $handler->chain(static function ($chain): void { + $chain + ->push('queue', 'success', ['key1' => 'value1']) + ->setPriority('high') + ->setDelay(60) + ->push('queue', 'success', ['key2' => 'value2']) + ->setPriority('low') + ->setDelay(120); + }); + + $this->assertTrue($result); + + $redis = self::getPrivateProperty($handler, 'redis'); + // Should be in high priority queue + $this->assertSame(1, $redis->zCard('queues:queue:high')); + + // Check with delay + $task = $redis->zRangeByScore('queues:queue:high', '-inf', Time::now()->addSeconds(61)->timestamp, ['limit' => [0, 1]]); + $queueJob = new QueueJob(json_decode((string) $task[0], true)); + + $this->assertSame('success', $queueJob->payload['job']); + $this->assertSame(['key1' => 'value1'], $queueJob->payload['data']); + $this->assertArrayHasKey('metadata', $queueJob->payload); + + // Check metadata + $metadata = $queueJob->payload['metadata']; + $this->assertSame('queue', $metadata['queue']); + $this->assertSame('high', $metadata['priority']); + $this->assertSame(60, $metadata['delay']); + + // Check a chained job with its priority and delay + $this->assertArrayHasKey('chainedJobs', $metadata); + $chainedJobs = $metadata['chainedJobs']; + $this->assertCount(1, $chainedJobs); + $this->assertSame('success', $chainedJobs[0]['job']); + $this->assertSame(['key2' => 'value2'], $chainedJobs[0]['data']); + $this->assertSame('queue', $chainedJobs[0]['metadata']['queue']); + $this->assertSame('low', $chainedJobs[0]['metadata']['priority']); + $this->assertSame(120, $chainedJobs[0]['metadata']['delay']); } public function testPushException(): void From ce601bc714f5125048fe4376ae0939538996bc79 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Mon, 5 May 2025 08:22:55 +0200 Subject: [PATCH 63/68] feat: add support for task locks (#60) * feat: add support for task locks * make phpcpd happy --- src/Commands/QueueWork.php | 53 ++++++++- tests/Commands/QueueWorkTest.php | 185 ++++++++++++++++++++++++++++++- 2 files changed, 229 insertions(+), 9 deletions(-) diff --git a/src/Commands/QueueWork.php b/src/Commands/QueueWork.php index bbb81ce..4f2bf22 100644 --- a/src/Commands/QueueWork.php +++ b/src/Commands/QueueWork.php @@ -239,7 +239,15 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i timer()->start('work'); $payload = $work->payload; + $payloadMetadata = null; + try { + // Load payload metadata + $payloadMetadata = PayloadMetadata::fromArray($payload['metadata'] ?? []); + + // Renew lock if needed + $this->renewLock($payloadMetadata); + $class = $config->resolveJobClass($payload['job']); $job = new $class($payload['data']); $job->process(); @@ -250,9 +258,7 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i CLI::write('The processing of this job was successful', 'green'); // Check chained jobs - if (isset($payload['metadata']) && $payload['metadata'] !== []) { - $this->processNextJobInChain($payload['metadata']); - } + $this->processNextJobInChain($payloadMetadata); } catch (Throwable $err) { if (isset($job) && ++$work->attempts < ($tries ?? $job->getTries())) { // Schedule for later @@ -263,6 +269,9 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i } CLI::write('The processing of this job failed', 'red'); } finally { + // Remove lock if needed + $this->clearLock($payloadMetadata); + timer()->stop('work'); CLI::write(sprintf('It took: %s sec', timer()->getElapsedTime('work')) . PHP_EOL, 'cyan'); } @@ -271,10 +280,8 @@ private function handleWork(QueueJob $work, QueueConfig $config, ?int $tries, ?i /** * Process the next job in the chain */ - private function processNextJobInChain(array $payloadMetadata): void + private function processNextJobInChain(PayloadMetadata $payloadMetadata): void { - $payloadMetadata = PayloadMetadata::fromArray($payloadMetadata); - if (! $payloadMetadata->hasChainedJobs()) { return; } @@ -305,6 +312,40 @@ private function processNextJobInChain(array $payloadMetadata): void CLI::write(sprintf('Chained job: %s has been placed in the queue: %s', $nextPayload->getJob(), $nextPayload->getQueue()), 'green'); } + /** + * Renew task lock + */ + private function renewLock(PayloadMetadata $payloadMetadata): void + { + if (! $payloadMetadata->has('taskLockTTL') || ! $payloadMetadata->has('taskLockKey')) { + return; + } + + $ttl = $payloadMetadata->get('taskLockTTL'); + $key = $payloadMetadata->get('taskLockKey'); + + // Permanent lock, no need to renew + if ($ttl === 0) { + return; + } + + cache()->save($key, [], $ttl); + } + + /** + * Remove task lock + */ + private function clearLock(PayloadMetadata $payloadMetadata): void + { + if (! $payloadMetadata->has('taskLockKey')) { + return; + } + + $key = $payloadMetadata->get('taskLockKey'); + + cache()->delete($key); + } + private function maxJobsCheck(int $maxJobs, int $countJobs): bool { if ($maxJobs > 0 && $countJobs >= $maxJobs) { diff --git a/tests/Commands/QueueWorkTest.php b/tests/Commands/QueueWorkTest.php index dd5a13d..af39092 100644 --- a/tests/Commands/QueueWorkTest.php +++ b/tests/Commands/QueueWorkTest.php @@ -13,6 +13,8 @@ namespace Tests\Commands; +use CodeIgniter\Cache\CacheInterface; +use CodeIgniter\Config\Services; use CodeIgniter\I18n\Time; use CodeIgniter\Queue\Models\QueueJobModel; use CodeIgniter\Test\Filters\CITestStreamFilter; @@ -123,13 +125,17 @@ public function testRunWithChainedQueueSucceed(): void 'job' => 'success', 'data' => ['key' => 'value'], 'metadata' => [ - 'queue' => 'queue', + 'queue' => 'test', 'chainedJobs' => [ [ - 'job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => [ + 'job' => 'success', + 'data' => [ + 'key3' => 'value3', + ], + 'metadata' => [ 'queue' => 'queue', 'priority' => 'high', - 'delay' => 10, + 'delay' => 30, ], ], ], @@ -154,5 +160,178 @@ public function testRunWithChainedQueueSucceed(): void $this->assertSame('The processing of this job was successful', $this->getLine(4)); $this->assertSame('Chained job: success has been placed in the queue: queue', $this->getLine(5)); $this->assertSame('No job available. Stopping.', $this->getLine(8)); + + $this->seeInDatabase('queue_jobs', [ + 'queue' => 'queue', + 'payload' => json_encode([ + 'job' => 'success', + 'data' => ['key3' => 'value3'], + 'metadata' => [ + 'queue' => 'queue', + 'priority' => 'high', + 'delay' => 30, + ], + ]), + ]); + } + + public function testRunWithTaskLock(): void + { + $lockKey = 'test_lock_key'; + $lockTTL = 300; // 5 minutes + + Time::setTestNow('2023-12-19 14:15:16'); + + $cache = $this->createMock(CacheInterface::class); + + // Set up expectations + $cache->expects($this->once()) + ->method('save') + ->with($lockKey, $this->anything(), $lockTTL) + ->willReturn(true); + + $cache->expects($this->once()) + ->method('delete') + ->with($lockKey) + ->willReturn(true); + + // Replace the cache service + Services::injectMock('cache', $cache); + + fake(QueueJobModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => [ + 'job' => 'success', + 'data' => ['key' => 'value'], + 'metadata' => [ + 'taskLockKey' => $lockKey, + 'taskLockTTL' => $lockTTL, + 'queue' => 'test', + ], + ], + 'priority' => 'default', + 'status' => 0, + 'attempts' => 0, + 'available_at' => 1_702_977_074, + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test sleep 1 --stop-when-empty')); + $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Listening for the jobs with the queue: test', $this->getLine(0)); + $this->assertSame('Starting a new job: success, with ID: 1', $this->getLine(3)); + $this->assertSame('The processing of this job was successful', $this->getLine(4)); + } + + public function testRunWithPermanentTaskLock(): void + { + $lockKey = 'permanent_lock_key'; + $lockTTL = 0; // Permanent lock + + Time::setTestNow('2023-12-19 14:15:16'); + + $cache = $this->createMock(CacheInterface::class); + + // For permanent lock (TTL=0), save should NOT be called + $cache->expects($this->never()) + ->method('save'); + + $cache->expects($this->once()) + ->method('delete') + ->with($lockKey) + ->willReturn(true); + + // Replace the cache service + Services::injectMock('cache', $cache); + + fake(QueueJobModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => [ + 'job' => 'success', + 'data' => ['key4' => 'value4'], + 'metadata' => [ + 'taskLockKey' => $lockKey, + 'taskLockTTL' => $lockTTL, + 'queue' => 'test', + ], + ], + 'priority' => 'default', + 'status' => 0, + 'attempts' => 0, + 'available_at' => 1_702_977_074, + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test sleep 1 --stop-when-empty')); + $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Listening for the jobs with the queue: test', $this->getLine(0)); + $this->assertSame('Starting a new job: success, with ID: 1', $this->getLine(3)); + $this->assertSame('The processing of this job was successful', $this->getLine(4)); + } + + public function testLockClearedOnFailure(): void + { + $lockKey = 'failure_lock_key'; + $lockTTL = 300; + + Time::setTestNow('2023-12-19 14:15:16'); + + $cache = $this->createMock(CacheInterface::class); + + // Set up expectations + $cache->expects($this->once()) + ->method('save') + ->with($lockKey, $this->anything(), $lockTTL) + ->willReturn(true); + + $cache->expects($this->once()) + ->method('delete') + ->with($lockKey) + ->willReturn(true); + + // Replace the cache service + Services::injectMock('cache', $cache); + + fake(QueueJobModel::class, [ + 'connection' => 'database', + 'queue' => 'test', + 'payload' => [ + 'job' => 'failure', + 'data' => ['key' => 'value'], + 'metadata' => [ + 'taskLockKey' => $lockKey, + 'taskLockTTL' => $lockTTL, + 'queue' => 'test', + ], + ], + 'priority' => 'default', + 'status' => 0, + 'attempts' => 0, + 'available_at' => 1_702_977_074, + ]); + + CITestStreamFilter::registration(); + CITestStreamFilter::addOutputFilter(); + + $this->assertNotFalse(command('queue:work test sleep 1 --stop-when-empty')); + $this->parseOutput(CITestStreamFilter::$buffer); + + CITestStreamFilter::removeOutputFilter(); + + $this->assertSame('Listening for the jobs with the queue: test', $this->getLine(0)); + $this->assertSame('Starting a new job: failure, with ID: 1', $this->getLine(3)); + $this->assertSame('The processing of this job failed', $this->getLine(4)); } } From 39f0a8beb1542f6215ece50523576616195dd3e6 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Mon, 5 May 2025 08:24:42 +0200 Subject: [PATCH 64/68] fix: remove option to use a custom config file via service (#59) --- src/Config/Services.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Config/Services.php b/src/Config/Services.php index 088a2a6..1ca828f 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -20,14 +20,14 @@ class Services extends BaseService { - public static function queue(?QueueConfig $config = null, $getShared = true): QueueInterface + public static function queue($getShared = true): QueueInterface { if ($getShared) { - return static::getSharedInstance('queue', $config); + return static::getSharedInstance('queue'); } - /** @var QueueConfig|null $config */ - $config ??= config('Queue'); + /** @var QueueConfig $config */ + $config = config('Queue'); return (new Queue($config))->init(); } From ecb790c7386dbeb71881e5b38c273c35909732fc Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Mon, 16 Jun 2025 07:53:42 +0200 Subject: [PATCH 65/68] feat: `Queue::push()` to return QueuePushResult instead of boolean (#62) * feat: return jobID after push instead of the bool value * fix phpcpd * feat: return QueuePushResult from push() instead of bool * add some PHPDocs to QueuePushResult class --- docs/basic-usage.md | 10 ++++- src/Handlers/BaseHandler.php | 5 ++- src/Handlers/DatabaseHandler.php | 17 ++++++-- src/Handlers/PredisHandler.php | 18 ++++++-- src/Handlers/RedisHandler.php | 22 +++++++--- src/Payloads/ChainBuilder.php | 5 ++- src/QueuePushResult.php | 67 ++++++++++++++++++++++++++++ tests/DatabaseHandlerTest.php | 14 +++--- tests/Payloads/ChainBuilderTest.php | 8 ++-- tests/PredisHandlerTest.php | 10 ++--- tests/PushAndPopWithDelayTest.php | 4 +- tests/QueuePushResultTest.php | 68 +++++++++++++++++++++++++++++ tests/RedisHandlerTest.php | 10 ++--- 13 files changed, 217 insertions(+), 41 deletions(-) create mode 100644 src/QueuePushResult.php create mode 100644 tests/QueuePushResultTest.php diff --git a/docs/basic-usage.md b/docs/basic-usage.md index 3204e39..b91ae37 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -160,6 +160,12 @@ service('queue')->push('emails', 'email', ['message' => 'Email message goes here We will be pushing `email` job to the `emails` queue. +As a result of calling the `push()` method, you will receive a `QueuePushResult` object, which you can inspect if needed. It provides the following information: + +- `getStatus()`: Indicates whether the job was successfully added to the queue. +- `getJobId()`: Returns the ID of the job that was added to the queue. +- `getError()`: Returns any error that occurred if the job was not added. + ### Sending chained jobs to the queue Sending chained jobs is also simple and lets you specify the particular order of the job execution. @@ -172,9 +178,11 @@ service('queue')->chain(function($chain) { }); ``` -In the example above, we will send jobs to the `reports` and `emails` queue. First, we will generate a report for given user with the `generate-report` job, after this, we will send an email with `email` job. +In the example above, we will send jobs to the `reports` and `emails` queues. First, we will generate a report for given user with the `generate-report` job, after this, we will send an email with `email` job. The `email` job will be executed only if the `generate-report` job was successful. +As with the `push()` method, calling the `chain()` method also returns a `QueuePushResult` object. + ### Consuming the queue Since we sent our sample job to queue `emails`, then we need to run the worker with the appropriate queue: diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 03a6a57..f364802 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -22,6 +22,7 @@ use CodeIgniter\Queue\Models\QueueJobFailedModel; use CodeIgniter\Queue\Payloads\ChainBuilder; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use CodeIgniter\Queue\Traits\HasQueueValidation; use ReflectionException; use Throwable; @@ -39,7 +40,7 @@ abstract class BaseHandler abstract public function name(): string; - abstract public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool; + abstract public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult; abstract public function pop(string $queue, array $priorities): ?QueueJob; @@ -153,7 +154,7 @@ public function setPriority(string $priority): static * * @param Closure $callback Chain definition callback */ - public function chain(Closure $callback): bool + public function chain(Closure $callback): QueuePushResult { $chainBuilder = new ChainBuilder($this); $callback($chainBuilder); diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 88869b6..403e7cd 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -21,6 +21,7 @@ use CodeIgniter\Queue\Models\QueueJobModel; use CodeIgniter\Queue\Payloads\Payload; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use ReflectionException; use Throwable; @@ -44,10 +45,8 @@ public function name(): string /** * Add job to the queue. - * - * @throws ReflectionException */ - public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult { $this->validateJobAndPriority($queue, $job); @@ -62,7 +61,17 @@ public function push(string $queue, string $job, array $data, ?PayloadMetadata $ $this->priority = $this->delay = null; - return $this->jobModel->insert($queueJob, false); + try { + $jobId = $this->jobModel->insert($queueJob); + } catch (Throwable $e) { + return QueuePushResult::failure($e->getMessage()); + } + + if ($jobId === 0) { + return QueuePushResult::failure('Failed to insert job into the database.'); + } + + return QueuePushResult::success($jobId); } /** diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 4b64990..22be7cf 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -22,6 +22,7 @@ use CodeIgniter\Queue\Interfaces\QueueInterface; use CodeIgniter\Queue\Payloads\Payload; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use Exception; use Predis\Client; use Throwable; @@ -59,16 +60,17 @@ public function name(): string /** * Add job to the queue. */ - public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult { $this->validateJobAndPriority($queue, $job); helper('text'); + $jobId = (int) random_string('numeric', 16); $availableAt = Time::now()->addSeconds($this->delay ?? 0); $queueJob = new QueueJob([ - 'id' => random_string('numeric', 16), + 'id' => $jobId, 'queue' => $queue, 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, @@ -77,11 +79,19 @@ public function push(string $queue, string $job, array $data, ?PayloadMetadata $ 'available_at' => $availableAt, ]); - $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => $availableAt->timestamp]); + try { + $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => $availableAt->timestamp]); + } catch (Throwable $e) { + return QueuePushResult::failure('Unexpected Redis error: ' . $e->getMessage()); + } finally { + $this->priority = $this->delay = null; + } $this->priority = $this->delay = null; - return $result > 0; + return $result > 0 + ? QueuePushResult::success($jobId) + : QueuePushResult::failure('Job already exists in the queue.'); } /** diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index dc86d5b..4676436 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -22,6 +22,7 @@ use CodeIgniter\Queue\Interfaces\QueueInterface; use CodeIgniter\Queue\Payloads\Payload; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use Redis; use RedisException; use Throwable; @@ -76,16 +77,17 @@ public function name(): string * * @throws RedisException */ - public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult { $this->validateJobAndPriority($queue, $job); helper('text'); $availableAt = Time::now()->addSeconds($this->delay ?? 0); + $jobId = (int) random_string('numeric', 16); $queueJob = new QueueJob([ - 'id' => random_string('numeric', 16), + 'id' => $jobId, 'queue' => $queue, 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, @@ -94,11 +96,21 @@ public function push(string $queue, string $job, array $data, ?PayloadMetadata $ 'available_at' => $availableAt, ]); - $result = (int) $this->redis->zAdd("queues:{$queue}:{$this->priority}", $availableAt->timestamp, json_encode($queueJob)); + try { + $result = $this->redis->zAdd("queues:{$queue}:{$this->priority}", $availableAt->timestamp, json_encode($queueJob)); + } catch (Throwable $e) { + return QueuePushResult::failure('Unexpected Redis error: ' . $e->getMessage()); + } finally { + $this->priority = $this->delay = null; + } - $this->priority = $this->delay = null; + if ($result === false) { + return QueuePushResult::failure('Failed to add job to Redis.'); + } - return $result > 0; + return (int) $result > 0 + ? QueuePushResult::success($jobId) + : QueuePushResult::failure('Job already exists in the queue.'); } /** diff --git a/src/Payloads/ChainBuilder.php b/src/Payloads/ChainBuilder.php index 73479d2..772e376 100644 --- a/src/Payloads/ChainBuilder.php +++ b/src/Payloads/ChainBuilder.php @@ -14,6 +14,7 @@ namespace CodeIgniter\Queue\Payloads; use CodeIgniter\Queue\Handlers\BaseHandler; +use CodeIgniter\Queue\QueuePushResult; class ChainBuilder { @@ -44,10 +45,10 @@ public function push(string $queue, string $jobName, array $data = []): ChainEle /** * Dispatch the chain of jobs */ - public function dispatch(): bool + public function dispatch(): QueuePushResult { if ($this->payloads->count() === 0) { - return true; + return QueuePushResult::failure('No jobs to dispatch.'); } $current = $this->payloads->shift(); diff --git a/src/QueuePushResult.php b/src/QueuePushResult.php new file mode 100644 index 0000000..efe7456 --- /dev/null +++ b/src/QueuePushResult.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue; + +/** + * Represents the result of a queue push operation. + */ +class QueuePushResult +{ + public function __construct( + protected readonly bool $success, + protected readonly ?int $jobId = null, + protected readonly ?string $error = null, + ) { + } + + /** + * Creates a successful push result. + */ + public static function success(int $jobId): self + { + return new self(true, $jobId); + } + + /** + * Creates a failed push result. + */ + public static function failure(?string $error = null): self + { + return new self(false, null, $error); + } + + /** + * Returns whether the push operation was successful. + */ + public function getStatus(): bool + { + return $this->success; + } + + /** + * Returns the job ID if the push was successful, null otherwise. + */ + public function getJobId(): ?int + { + return $this->jobId; + } + + /** + * Returns the error message if the push failed, null otherwise. + */ + public function getError(): ?string + { + return $this->error; + } +} diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 507f917..f189f78 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -85,7 +85,7 @@ public function testPush(): void $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), @@ -103,7 +103,7 @@ public function testPushWithPriority(): void $handler = new DatabaseHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), @@ -122,7 +122,7 @@ public function testPushAndPopWithPriority(): void $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key1' => 'value1']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]), @@ -132,7 +132,7 @@ public function testPushAndPopWithPriority(): void $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]), @@ -161,7 +161,7 @@ public function testPushWithDelay(): void $handler = new DatabaseHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $availableAt = 1703859376; @@ -188,7 +188,7 @@ public function testChain(): void ->push('queue', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ @@ -221,7 +221,7 @@ public function testChainWithPriorityAndDelay(): void ->setDelay(120); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ diff --git a/tests/Payloads/ChainBuilderTest.php b/tests/Payloads/ChainBuilderTest.php index 304f5ae..6aa91ba 100644 --- a/tests/Payloads/ChainBuilderTest.php +++ b/tests/Payloads/ChainBuilderTest.php @@ -63,7 +63,7 @@ public function testChainWithSingleJob(): void $chain->push('queue', 'success', ['key' => 'value']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ @@ -84,7 +84,7 @@ public function testEmptyChain(): void // No jobs added }); - $this->assertTrue($result); + $this->assertFalse($result->getStatus()); $this->seeInDatabase('queue_jobs', []); } @@ -99,7 +99,7 @@ public function testMultipleDifferentQueues(): void ->push('queue2', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue1', 'payload' => json_encode([ @@ -132,7 +132,7 @@ public function testChainWithManyJobs(): void ->push('queue', 'success', ['key3' => 'value3']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 226732b..05d79d0 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -72,7 +72,7 @@ public function testPush(): void $handler = new PredisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:low')); @@ -92,7 +92,7 @@ public function testPushWithPriority(): void $handler = new PredisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:high')); @@ -114,7 +114,7 @@ public function testPushWithDelay(): void $handler = new PredisHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue-delay:default')); @@ -140,7 +140,7 @@ public function testChain(): void ->push('queue', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:low')); @@ -180,7 +180,7 @@ public function testChainWithPriorityAndDelay(): void ->setDelay(120); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); // Should be in high priority queue diff --git a/tests/PushAndPopWithDelayTest.php b/tests/PushAndPopWithDelayTest.php index db49b81..70a85a3 100644 --- a/tests/PushAndPopWithDelayTest.php +++ b/tests/PushAndPopWithDelayTest.php @@ -64,11 +64,11 @@ public function testPushAndPopWithDelay(string $name, string $class): void $handler = new $class($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key1' => 'value1']); - $this->assertTrue($result); + $this->assertNotNull($result); $result = $handler->push('queue-delay', 'success', ['key2' => 'value2']); - $this->assertTrue($result); + $this->assertNotNull($result); if ($name === 'database') { $this->seeInDatabase('queue_jobs', [ diff --git a/tests/QueuePushResultTest.php b/tests/QueuePushResultTest.php new file mode 100644 index 0000000..f5a6dfa --- /dev/null +++ b/tests/QueuePushResultTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests; + +use CodeIgniter\Queue\QueuePushResult; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class QueuePushResultTest extends TestCase +{ + public function testConstructorSuccess(): void + { + $result = new QueuePushResult(true, 123456, null); + + $this->assertTrue($result->getStatus()); + $this->assertSame(123456, $result->getJobId()); + $this->assertNull($result->getError()); + } + + public function testConstructorFailure(): void + { + $result = new QueuePushResult(false, null, 'Something went wrong'); + + $this->assertFalse($result->getStatus()); + $this->assertNull($result->getJobId()); + $this->assertSame('Something went wrong', $result->getError()); + } + + public function testStaticSuccess(): void + { + $result = QueuePushResult::success(999888); + + $this->assertTrue($result->getStatus()); + $this->assertSame(999888, $result->getJobId()); + $this->assertNull($result->getError()); + } + + public function testStaticFailure(): void + { + $result = QueuePushResult::failure('Redis error'); + + $this->assertFalse($result->getStatus()); + $this->assertNull($result->getJobId()); + $this->assertSame('Redis error', $result->getError()); + } + + public function testStaticFailureWithoutError(): void + { + $result = QueuePushResult::failure(); + + $this->assertFalse($result->getStatus()); + $this->assertNull($result->getJobId()); + $this->assertNull($result->getError()); + } +} diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index aa71240..7707cd4 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -69,7 +69,7 @@ public function testPush(): void $handler = new RedisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue:low')); @@ -86,7 +86,7 @@ public function testPushWithPriority(): void $handler = new RedisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue:high')); @@ -108,7 +108,7 @@ public function testPushWithDelay(): void $handler = new RedisHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue-delay:default')); @@ -134,7 +134,7 @@ public function testChain(): void ->push('queue', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue:low')); @@ -174,7 +174,7 @@ public function testChainWithPriorityAndDelay(): void ->setDelay(120); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); // Should be in high priority queue From 00841c422b97cf48faa50b867933b00d98cd7e02 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sat, 12 Jul 2025 12:01:30 +0200 Subject: [PATCH 66/68] chore: update deptrac.yaml to use with deptrac/deptrac (#63) --- deptrac.yaml | 80 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/deptrac.yaml b/deptrac.yaml index 21a7a89..d4bc58c 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -9,90 +9,90 @@ parameters: collectors: - type: bool must: - - type: className - regex: .*[A-Za-z]+Model$ + - type: class + value: .*[A-Za-z]+Model$ must_not: - - type: directory - regex: vendor/.* + - type: directory + value: vendor/.* - name: Vendor Model collectors: - type: bool must: - - type: className - regex: .*[A-Za-z]+Model$ - - type: directory - regex: vendor/.* + - type: class + value: .*[A-Za-z]+Model$ + - type: directory + value: vendor/.* - name: Controller collectors: - type: bool must: - - type: className - regex: .*\/Controllers\/.* + - type: class + value: .*\/Controllers\/.* must_not: - - type: directory - regex: vendor/.* + - type: directory + value: vendor/.* - name: Vendor Controller collectors: - type: bool must: - - type: className - regex: .*\/Controllers\/.* - - type: directory - regex: vendor/.* + - type: class + value: .*\/Controllers\/.* + - type: directory + value: vendor/.* - name: Config collectors: - type: bool must: - - type: directory - regex: app/Config/.* + - type: directory + value: app/Config/.* must_not: - - type: className - regex: .*Services - - type: directory - regex: vendor/.* + - type: class + value: .*Services + - type: directory + value: vendor/.* - name: Vendor Config collectors: - type: bool must: - - type: directory - regex: vendor/.*/Config/.* + - type: directory + value: vendor/.*/Config/.* must_not: - - type: className - regex: .*Services + - type: class + value: .*Services - name: Entity collectors: - type: bool must: - - type: directory - regex: app/Entities/.* + - type: directory + value: app/Entities/.* must_not: - - type: directory - regex: vendor/.* + - type: directory + value: vendor/.* - name: Vendor Entity collectors: - type: bool must: - - type: directory - regex: vendor/.*/Entities/.* + - type: directory + value: vendor/.*/Entities/.* - name: View collectors: - type: bool must: - - type: directory - regex: app/Views/.* + - type: directory + value: app/Views/.* must_not: - - type: directory - regex: vendor/.* + - type: directory + value: vendor/.* - name: Vendor View collectors: - type: bool must: - - type: directory - regex: vendor/.*/Views/.* + - type: directory + value: vendor/.*/Views/.* - name: Service collectors: - - type: className - regex: .*Services.* + - type: class + value: .*Services.* ruleset: Entity: - Config From 2f978958db5dacfc87910a3956b7ae4915de57c8 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sat, 12 Jul 2025 12:01:41 +0200 Subject: [PATCH 67/68] chore: fix data provider name and order by cs-fix (#64) --- tests/PushAndPopWithDelayTest.php | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/tests/PushAndPopWithDelayTest.php b/tests/PushAndPopWithDelayTest.php index 70a85a3..f06b0f9 100644 --- a/tests/PushAndPopWithDelayTest.php +++ b/tests/PushAndPopWithDelayTest.php @@ -38,25 +38,7 @@ protected function setUp(): void $this->config = config(QueueConfig::class); } - public static function handlerProvider(): iterable - { - return [ - [ - 'database', // name - 'CodeIgniter\Queue\Handlers\DatabaseHandler', // class - ], - [ - 'redis', - 'CodeIgniter\Queue\Handlers\RedisHandler', - ], - [ - 'predis', - 'CodeIgniter\Queue\Handlers\PredisHandler', - ], - ]; - } - - #[DataProvider('handlerProvider')] + #[DataProvider('providePushAndPopWithDelay')] public function testPushAndPopWithDelay(string $name, string $class): void { Time::setTestNow('2023-12-29 14:15:16'); @@ -100,4 +82,22 @@ public function testPushAndPopWithDelay(string $name, string $class): void $payload = ['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]; $this->assertSame($payload, $result->payload); } + + public static function providePushAndPopWithDelay(): iterable + { + return [ + [ + 'database', // name + 'CodeIgniter\Queue\Handlers\DatabaseHandler', // class + ], + [ + 'redis', + 'CodeIgniter\Queue\Handlers\RedisHandler', + ], + [ + 'predis', + 'CodeIgniter\Queue\Handlers\PredisHandler', + ], + ]; + } } From 19f8292d56c76214a1cca40a7ae3ab0e98b71580 Mon Sep 17 00:00:00 2001 From: Michal Sniatala Date: Sat, 12 Jul 2025 14:20:41 +0200 Subject: [PATCH 68/68] chore: update deptrac.yaml for deptrac/deptrac (#65) --- .github/workflows/deptrac.yml | 4 ++-- deptrac.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deptrac.yml b/.github/workflows/deptrac.yml index f481772..78b7ecf 100644 --- a/.github/workflows/deptrac.yml +++ b/.github/workflows/deptrac.yml @@ -7,7 +7,7 @@ on: paths: - '**.php' - 'composer.*' - - 'depfile.yaml' + - 'deptrac.yaml' - '.github/workflows/deptrac.yml' push: branches: @@ -15,7 +15,7 @@ on: paths: - '**.php' - 'composer.*' - - 'depfile.yaml' + - 'deptrac.yaml' - '.github/workflows/deptrac.yml' jobs: diff --git a/deptrac.yaml b/deptrac.yaml index d4bc58c..3caa730 100644 --- a/deptrac.yaml +++ b/deptrac.yaml @@ -1,4 +1,4 @@ -parameters: +deptrac: paths: - ./src/ - ./vendor/codeigniter4/framework/system/ @@ -153,4 +153,4 @@ parameters: - Vendor Entity - Vendor Model - Vendor View - skip_violations: + skip_violations: []