Skip to content

Commit 25c92cb

Browse files
committed
Merge branch 'develop' into feature/introduce-aggregate-model
2 parents da5856f + 0897e0f commit 25c92cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+310
-251
lines changed

.github/workflows/test-unit.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ jobs:
7777
name: Unit
7878
runs-on: ubuntu-latest
7979
container:
80-
image: ghcr.io/mvorisek/image-php:${{ matrix.php }}
80+
# Alpine support for newer pdo_oci is broken, see https://github.com/mlocati/docker-php-extension-installer/issues/523
81+
# remove once config.m4 checks are working on Alpine
82+
image: ghcr.io/mvorisek/image-php:${{ matrix.php }}-debian
8183
strategy:
8284
fail-fast: false
8385
matrix:
@@ -152,6 +154,7 @@ jobs:
152154
php -r '(new PDO("mysql:host=mysql56", "root", "atk4_pass_root"))->exec("GRANT USAGE ON *.* TO '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");'
153155
php -r '(new PDO("mysql:host=mariadb", "root", "atk4_pass_root"))->exec("ALTER USER '"'"'atk4_test_user'"'"'@'"'"'%'"'"' WITH MAX_USER_CONNECTIONS 5");'
154156
php -r '(new PDO("pgsql:host=postgres;dbname=atk4_test", "atk4_test_user", "atk4_pass"))->exec("ALTER ROLE atk4_test_user CONNECTION LIMIT 1");'
157+
/usr/lib/oracle/setup.sh
155158
if [ -n "$LOG_COVERAGE" ]; then mkdir coverage; fi
156159
157160
- name: "Run tests: SQLite"
@@ -206,7 +209,7 @@ jobs:
206209
207210
- name: "Run tests: MSSQL"
208211
env:
209-
DB_DSN: "sqlsrv:host=mssql;dbname=master"
212+
DB_DSN: "sqlsrv:host=mssql;dbname=master;driverOptions[TrustServerCertificate]=1"
210213
DB_USER: sa
211214
DB_PASSWORD: atk4_pass
212215
run: |

README.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class JobReport extends Job {
149149
$timesheet->getRef('client_id')->addField('hourly_rate');
150150

151151
// calculate timesheet cost expression
152-
$timesheet->addExpression('cost', '[hours]*[hourly_rate]');
152+
$timesheet->addExpression('cost', ['expr' => '[hours] * [hourly_rate]']);
153153

154154
// build relation between Job and Timesheets
155155
$this->hasMany('Timesheets', ['model' => $timesheet])
@@ -159,10 +159,10 @@ class JobReport extends Job {
159159
);
160160

161161
// finally lets calculate profit
162-
$this->addExpression('profit', '[invoiced]-[reported]');
162+
$this->addExpression('profit', ['expr' => '[invoiced] - [reported]']);
163163

164164
// profit margin could be also useful
165-
$this->addExpression('profit_margin', 'coalesce([profit] / [invoiced], 0)');
165+
$this->addExpression('profit_margin', ['expr' => 'coalesce([profit] / [invoiced], 0)']);
166166
}
167167
}
168168
```
@@ -189,7 +189,7 @@ $chart = new \Atk4\Chart\BarChart();
189189
$data = new JobReport($db);
190190

191191
// BarChart wants aggregated data
192-
$data->addExpression('month', 'month([date])');
192+
$data->addExpression('month', ['expr' => 'month([date])']);
193193
$aggregate = new AggregateModel($data);
194194
$aggregate->setGroupBy('month', ['profit_margin' => 'sum']);
195195

docs/advanced.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ that shows how much amount is closed and `amount_due`::
549549
// define field to see closed amount on invoice
550550
$this->hasMany('InvoicePayment')
551551
->addField('total_payments', ['aggregate' => 'sum', 'field' => 'amount_closed']);
552-
$this->addExpression('amount_due', '[total]-coalesce([total_payments],0)');
552+
$this->addExpression('amount_due', ['expr' => '[total] - coalesce([total_payments], 0)']);
553553

554554
Note that I'm using coalesce because without InvoicePayments the aggregate sum
555555
will return NULL. Finally let's build allocation method, that allocates new
@@ -699,7 +699,7 @@ If you wish to use a different value instead, you can create an expression::
699699
if($m->_isset('category') && !$m->_isset('category_id')) {
700700
$c = $this->refModel('category_id');
701701
$c->addCondition($c->title_field, 'like', $m->get('category'));
702-
$m->set('category_id', $this->expr('coalesce([],[])',[
702+
$m->set('category_id', $this->expr('coalesce([], [])',[
703703
$c->action('field',['id']),
704704
$m->getField('category_id')->default
705705
]));

docs/design.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ Code::
319319
$this->addField('password_change_date');
320320

321321
$this->addExpression('is_password_expired', [
322-
'[password_change_date] < (NOW() - INTERVAL 1 MONTH)',
322+
'expr' => '[password_change_date] < (NOW() - INTERVAL 1 MONTH)',
323323
'type' => 'boolean',
324324
]);
325325
}

docs/expressions.rst

+13-13
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ use result as an output.
1515
Expressions solve this problem by adding a read-only field to your model that
1616
corresponds to an expression:
1717

18-
.. php:method:: addExpression($link, $model);
18+
.. php:method:: addExpression($name, $seed);
1919
2020
Example will calculate "total_gross" by adding up values for "net" and "vat"::
2121

2222
$m = new Model_Invoice($db);
2323
$m->addFields(['total_net', 'total_vat']);
2424

25-
$m->addExpression('total_gross', '[total_net]+[total_vat]');
25+
$m->addExpression('total_gross', ['expr' => '[total_net] + [total_vat]']);
2626
$m = $m->load(1);
2727

2828
echo $m->get('total_gross');
@@ -45,9 +45,9 @@ values for the other fields including other expressions.
4545

4646
There are other ways how you can specify expression::
4747

48-
$m->addExpression('total_gross',
49-
$m->expr('[total_net]+[total_vat] + [fee]', ['fee' => $fee])
50-
);
48+
$m->addExpression('total_gross', [
49+
'expr' => $m->expr('[total_net] + [total_vat] + [fee]', ['fee' => $fee])
50+
]);
5151

5252
This format allow you to supply additional parameters inside expression.
5353
You should always use parameters instead of appending values inside your
@@ -64,7 +64,7 @@ unite a few statistical queries. Let's start by looking a at a very basic
6464
example::
6565

6666
$m = new Model($db, ['table' => false]);
67-
$m->addExpression('now', 'now()');
67+
$m->addExpression('now', ['expr' => 'now()']);
6868
$m = $m->loadAny();
6969
echo $m->get('now');
7070

@@ -80,9 +80,9 @@ can be gained when you need to pull various statistical values from your
8080
database at once::
8181

8282
$m = new Model($db, ['table' => false]);
83-
$m->addExpression('total_orders', (new Model_Order($db))->action('count'));
84-
$m->addExpression('total_payments', (new Model_Payment($db))->action('count'));
85-
$m->addExpression('total_received', (new Model_Payment($db))->action('fx0', ['sum', 'amount']));
83+
$m->addExpression('total_orders', ['expr' => (new Model_Order($db))->action('count')]);
84+
$m->addExpression('total_payments', ['expr' => (new Model_Payment($db))->action('count')]);
85+
$m->addExpression('total_received', ['expr' => (new Model_Payment($db))->action('fx0', ['sum', 'amount'])]);
8686

8787
$data = $m->loadOne()->get();
8888

@@ -101,9 +101,9 @@ Expression Callback
101101

102102
You can use a callback method when defining expression::
103103

104-
$m->addExpression('total_gross', function($m, $q) {
105-
return '[total_net]+[total_vat]';
106-
});
104+
$m->addExpression('total_gross', ['expr' => function ($m, $q) {
105+
return '[total_net] + [total_vat]';
106+
}, 'type' => 'float']);
107107

108108
Model Reloading after Save
109109
--------------------------
@@ -123,7 +123,7 @@ the following model::
123123

124124
$this->addFields(['a', 'b']);
125125

126-
$this->addExpression('sum', '[a]+[b]');
126+
$this->addExpression('sum', ['expr' => '[a] + [b]']);
127127
}
128128
}
129129

docs/model.rst

+16-16
Original file line numberDiff line numberDiff line change
@@ -167,13 +167,13 @@ You may safely rely on `$this->persistence` property to make choices::
167167
if ($this->persistence instanceof \Atk4\Data\Persistence\Sql) {
168168

169169
// Calculating on SQL server is more efficient!!
170-
$this->addExpression('total', '[amount] + [vat]');
170+
$this->addExpression('total', ['expr' => '[amount] + [vat]']);
171171
} else {
172172

173173
// Fallback
174-
$this->addCalculatedField('total', function($m) {
174+
$this->addCalculatedField('total', ['expr' => function ($m) {
175175
return $m->get('amount') + $m->get('vat');
176-
} );
176+
}, 'type' => 'float']);
177177
}
178178

179179
To invoke code from `init()` methods of ALL models (for example soft-delete logic),
@@ -251,20 +251,20 @@ Although you may make any field read-only::
251251

252252
There are two methods for adding dynamically calculated fields.
253253

254-
.. php:method:: addExpression($name, $definition)
254+
.. php:method:: addExpression($name, $seed)
255255
256256
Defines a field as server-side expression (e.g. SQL)::
257257

258-
$this->addExpression('total', '[amount] + [vat]');
258+
$this->addExpression('total', ['expr' => '[amount] + [vat]']);
259259

260260
The above code is executed on the server (SQL) and can be very powerful.
261261
You must make sure that expression is valid for current `$this->persistence`::
262262

263-
$product->addExpression('discount', $this->refLink('category_id')->fieldQuery('default_discount'));
263+
$product->addExpression('discount', ['expr' => $this->refLink('category_id')->fieldQuery('default_discount')]);
264264
// expression as a sub-select from referenced model (Category) imported as a read-only field
265265
// of $product model
266266

267-
$product->addExpression('total', 'if([is_discounted], ([amount]+[vat])*[discount], [amount] + [vat])');
267+
$product->addExpression('total', ['expr' => 'if ([is_discounted], ([amount] + [vat])*[discount], [amount] + [vat])']);
268268
// new "total" field now contains complex logic, which is executed in SQL
269269

270270
$product->addCondition('total', '<', 10);
@@ -273,17 +273,17 @@ You must make sure that expression is valid for current `$this->persistence`::
273273

274274
For the times when you are not working with SQL persistence, you can calculate field in PHP.
275275

276-
.. php:method:: addCalculatedField($name, $callback)
276+
.. php:method:: addCalculatedField($name, ['expr' => $callback])
277277
278278
Creates new field object inside your model. Field value will be automatically
279279
calculated by your callback method right after individual record is loaded by the model::
280280

281281
$this->addField('term', ['caption' => 'Repayment term in months', 'default' => 36]);
282282
$this->addField('rate', ['caption' => 'APR %', 'default' => 5]);
283283

284-
$this->addCalculatedField('interest', function($m) {
284+
$this->addCalculatedField('interest', ['expr' => function ($m) {
285285
return $m->calculateInterest();
286-
});
286+
}, 'type' => 'float']);
287287

288288
.. important:: always use argument `$m` instead of `$this` inside your callbacks. If model is to be
289289
`clone`d, the code relying on `$this` would reference original model, but the code using
@@ -292,14 +292,14 @@ calculated by your callback method right after individual record is loaded by th
292292
This can also be useful for calculating relative times::
293293

294294
class MyModel extends Model {
295-
use HumanTiming; // See https://stackoverflow.com/questions/2915864/php-how-to-find-the-time-elapsed-since-a-date-time
295+
use HumanTiming; // see https://stackoverflow.com/questions/2915864/php-how-to-find-the-time-elapsed-since-a-date-time
296296

297297
function init(): void {
298298
parent::init();
299299

300-
$this->addCalculatedField('event_ts_human_friendly', function($m) {
300+
$this->addCalculatedField('event_ts_human_friendly', ['expr' => function ($m) {
301301
return $this->humanTiming($m->get('event_ts'));
302-
});
302+
}]);
303303

304304
}
305305
}
@@ -742,6 +742,6 @@ Setting limit and sort order
742742
You can also use \Atk4\Data\Persistence\Sql\Expression or array of expressions instead of field name here.
743743
Or even mix them together::
744744

745-
$m->setOrder($m->expr('[net]*[vat]'));
746-
$m->setOrder([$m->expr('[net]*[vat]'), $m->expr('[closing]-[opening]')]);
747-
$m->setOrder(['net', $m->expr('[net]*[vat]', 'ref_no')]);
745+
$m->setOrder($m->expr('[net] * [vat]'));
746+
$m->setOrder([$m->expr('[net] * [vat]'), $m->expr('[closing] - [opening]')]);
747+
$m->setOrder(['net', $m->expr('[net] * [vat]', 'ref_no')]);

docs/overview.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ This is a type of code which may change if you decide to switch from one
160160
persistence to another. For example, this is how you would define `gross` field
161161
for SQL::
162162

163-
$model->addExpression('gross', '[net]+[vat]');
163+
$model->addExpression('gross', ['expr' => '[net] + [vat]']);
164164

165165
If your persistence does not support expressions (e.g. you are using Redis or
166166
MongoDB), you would need to define the field differently::
@@ -180,7 +180,7 @@ you want it to work with NoSQL, then your solution might be::
180180
if ($model->hasMethod('addExpression')) {
181181

182182
// method is injected by Persistence
183-
$model->addExpression('gross', '[net]+[vat]');
183+
$model->addExpression('gross', ['expr' => '[net] + [vat]']);
184184

185185
} else {
186186

docs/persistence.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -830,13 +830,13 @@ This operation is actually consisting of 3 following operations::
830830
Here is a way how to intervene with the process::
831831

832832
$client->hasMany('Invoice');
833-
$client->addExpression('last_sale', function($m) {
833+
$client->addExpression('last_sale', ['expr' => function ($m) {
834834
return $m->refLink('Invoice')
835835
->setOrder('date desc')
836836
->setLimit(1)
837837
->action('field', ['total_gross'], 'getOne');
838838

839-
});
839+
}, 'type' => 'float']);
840840

841841
The code above uses refLink and also creates expression, but it tweaks
842842
the action used.

docs/persistence/sql/expressions.rst

+4-4
Original file line numberDiff line numberDiff line change
@@ -306,9 +306,9 @@ parts of the query. You must not call them in normal circumstances.
306306
a nasty back-ticks or commas in the field names. I generally **discourage**
307307
you from using this method. Example use would be::
308308

309-
$query->field('foo,bar'); // escapes and adds 2 fields to the query
310-
$query->field($query->escape('foo,bar')); // adds field `foo,bar` to the query
311-
$query->field(['foo,bar']); // adds single field `foo,bar`
309+
$query->field('foo, bar'); // escapes and adds 2 fields to the query
310+
$query->field($query->escape('foo, bar')); // adds field `foo, bar` to the query
311+
$query->field(['foo, bar']); // adds single field `foo, bar`
312312

313313
$query->order('foo desc'); // escapes and add `foo` desc to the query
314314
$query->field($query->escape('foo desc')); // adds field `foo desc` to the query
@@ -332,7 +332,7 @@ parts of the query. You must not call them in normal circumstances.
332332

333333
$query->escapeIdentifierSoft('first_name'); // `first_name`
334334
$query->escapeIdentifierSoft('first.name'); // `first`.`name`
335-
$query->escapeIdentifierSoft('(2+2)'); // (2+2)
335+
$query->escapeIdentifierSoft('(2 + 2)'); // (2 + 2)
336336
$query->escapeIdentifierSoft('*'); // *
337337

338338
.. php:method:: escapeParam($value)

docs/persistence/sql/queries.rst

+8-8
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ The example above will perform a select query first:
104104

105105
If a single row can be retrieved, then the update will be performed:
106106

107-
- `update user set name="John", surname="Smith", revision=revision+1 where id=123`
107+
- `update user set name="John", surname="Smith", revision=revision + 1 where id=123`
108108

109109
Otherwise an insert operation will be performed:
110110

111-
- `insert into user (name,surname,revision) values ("John", "Smith", 1)`
111+
- `insert into user (name, surname, revision) values ("John", "Smith", 1)`
112112

113113
Chaining
114114
========
@@ -151,7 +151,7 @@ This query will perform `select name from (select * from employee)`::
151151
$u = $c->dsql("[] union []", [$q1, $q2]);
152152

153153
$q = $c->dsql()
154-
->field('date,debit,credit')
154+
->field('date, debit, credit')
155155
->table($u, 'derrivedTable');
156156

157157
$q->getRows();
@@ -255,8 +255,8 @@ Basic Examples::
255255
$query->field('first_name');
256256
// SELECT `first_name` from `user`
257257

258-
$query->field('first_name,last_name');
259-
// SELECT `first_name`,`last_name` from `user`
258+
$query->field('first_name, last_name');
259+
// SELECT `first_name`, `last_name` from `user`
260260

261261
$query->field('employee.first_name')
262262
// SELECT `employee`.`first_name` from `user`
@@ -338,9 +338,9 @@ Starting with the basic examples::
338338
$q->where('id', null); // same as above
339339

340340
$q->where('now()', 1); // will not use backticks
341-
$q->where($c->expr('now()'),1); // same as above
341+
$q->where($c->expr('now()'), 1); // same as above
342342

343-
$q->where('id', [1,2]); // renders as id in (1,2)
343+
$q->where('id', [1, 2]); // renders as id in (1, 2)
344344

345345
You may call where() multiple times, and conditions are always additive (uses AND).
346346
The easiest way to supply OR condition is to specify multiple conditions
@@ -435,7 +435,7 @@ Few examples::
435435

436436
$q->group('gender');
437437

438-
$q->group('gender,age');
438+
$q->group('gender, age');
439439

440440
$q->group(['gender', 'age']);
441441

docs/persistence/sql/transactions.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ It is recommended to always use atomic() in your code.
2020
exception, whole transaction will be automatically rolled back::
2121

2222
$c->atomic(function () use ($c) {
23-
$c->dsql('user')->set('balance=balance+10')->where('id', 10)->mode('update')->execute();
24-
$c->dsql('user')->set('balance=balance-10')->where('id', 14)->mode('update')->execute();
23+
$c->dsql('user')->set('balance=balance + 10')->where('id', 10)->mode('update')->execute();
24+
$c->dsql('user')->set('balance=balance - 10')->where('id', 14)->mode('update')->execute();
2525
});
2626

2727
atomic() can be nested.

0 commit comments

Comments
 (0)