Skip to content

Commit

Permalink
Stricter Model::{addExpression, addCalculatedField} seed (#973)
Browse files Browse the repository at this point in the history
  • Loading branch information
mvorisek committed Mar 5, 2022
1 parent 3779615 commit 16cf38e
Show file tree
Hide file tree
Showing 22 changed files with 104 additions and 118 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class JobReport extends Job {
$timesheet->getRef('client_id')->addField('hourly_rate');

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

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

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

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

// BarChart wants aggregated data
$data->addExpression('month', 'month([date])');
$data->addExpression('month', ['expr' => 'month([date])']);
$aggregate = new \Atk4\Report\GroupModel($data);
$aggregate->groupBy('month', ['profit_margin' => 'sum']);

Expand Down
2 changes: 1 addition & 1 deletion docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ that shows how much amount is closed and `amount_due`::
// define field to see closed amount on invoice
$this->hasMany('InvoicePayment')
->addField('total_payments', ['aggregate' => 'sum', 'field' => 'amount_closed']);
$this->addExpression('amount_due', '[total]-coalesce([total_payments],0)');
$this->addExpression('amount_due', ['expr' => '[total] - coalesce([total_payments], 0)']);

Note that I'm using coalesce because without InvoicePayments the aggregate sum
will return NULL. Finally let's build allocation method, that allocates new
Expand Down
2 changes: 1 addition & 1 deletion docs/design.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ Code::
$this->addField('password_change_date');

$this->addExpression('is_password_expired', [
'[password_change_date] < (NOW() - INTERVAL 1 MONTH)',
'expr' => '[password_change_date] < (NOW() - INTERVAL 1 MONTH)',
'type' => 'boolean',
]);
}
Expand Down
26 changes: 13 additions & 13 deletions docs/expressions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ use result as an output.
Expressions solve this problem by adding a read-only field to your model that
corresponds to an expression:

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

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

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

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

There are other ways how you can specify expression::

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

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

$m = new Model($db, ['table' => false]);
$m->addExpression('now', 'now()');
$m->addExpression('now', ['expr' => 'now()']);
$m = $m->loadAny();
echo $m->get('now');

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

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

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

Expand All @@ -101,9 +101,9 @@ Expression Callback

You can use a callback method when defining expression::

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

Model Reloading after Save
--------------------------
Expand All @@ -123,7 +123,7 @@ the following model::

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

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

Expand Down
26 changes: 13 additions & 13 deletions docs/model.rst
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ You may safely rely on `$this->persistence` property to make choices::
if ($this->persistence instanceof \Atk4\Data\Persistence\Sql) {

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

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

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

There are two methods for adding dynamically calculated fields.

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

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

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

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

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

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

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

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

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

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

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

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

function init(): void {
parent::init();

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

}
}
Expand Down
4 changes: 2 additions & 2 deletions docs/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ This is a type of code which may change if you decide to switch from one
persistence to another. For example, this is how you would define `gross` field
for SQL::

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

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

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

} else {

Expand Down
4 changes: 2 additions & 2 deletions docs/persistence.rst
Original file line number Diff line number Diff line change
Expand Up @@ -830,13 +830,13 @@ This operation is actually consisting of 3 following operations::
Here is a way how to intervene with the process::

$client->hasMany('Invoice');
$client->addExpression('last_sale', function($m) {
$client->addExpression('last_sale', ['expr' => function ($m) {
return $m->refLink('Invoice')
->setOrder('date desc')
->setLimit(1)
->action('field', ['total_gross'], 'getOne');

});
}, 'type' => 'float']);

The code above uses refLink and also creates expression, but it tweaks
the action used.
Expand Down
28 changes: 14 additions & 14 deletions docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ It might be handy to use in-line definition of a model. Try the following
inside console::

$m = new \Atk4\Data\Model($db, 'contact_info');
$m->addFields(['address_1','address_2']);
$m->addFields(['address_1', 'address_2']);
$m->addCondition('address_1', 'not', null);
$m = $m->loadAny();
$m->get();
Expand All @@ -150,8 +150,8 @@ Next, exit and create file `src/Model_ContactInfo.php`::
{
parent::init();

$this->addFields(['address_1','address_2']);
$this->addCondition('address_1','not', null);
$this->addFields(['address_1', 'address_2']);
$this->addCondition('address_1', 'not', null);
}
}

Expand Down Expand Up @@ -208,7 +208,7 @@ conditions is your way to specify which records to operate on::
$m = new Model_User($db);
$m->addCondition('country_id', '2');

myexport($m, ['id','username','country_id']);
myexport($m, ['id', 'username', 'country_id']);

If you want to temporarily add conditions, then you can either clone the model
or use :php:meth:`Model::tryLoadBy`.
Expand Down Expand Up @@ -353,7 +353,7 @@ Lets once again load up the console for some exercises::

$m = new Model_User($db);

$m = $m->loadBy('username','john');
$m = $m->loadBy('username', 'john');
$m->get();

At this point you'll see that address has also been loaded for the user.
Expand Down Expand Up @@ -399,7 +399,7 @@ For some persistence classes, you should use constructor directly::
There are several Persistence classes that deal with different data sources.
Lets load up our console and try out a different persistence::

$a=['user' => [],'contact_info' => []];
$a=['user' => [], 'contact_info' => []];
$ar = new \Atk4\Data\Persistence\Array_($a);
$m = new Model_User($ar);
$m->set('username', 'test');
Expand Down Expand Up @@ -539,15 +539,15 @@ can be brought into the original model as fields::
$m = new Model_Client($db);
$m->getRef('Invoice')->addField('max_delivery', ['aggregate' => 'max', 'field' => 'shipping']);
$m->getRef('Payment')->addField('total_paid', ['aggregate' => 'sum', 'field' => 'amount']);
$m->export(['name','max_delivery','total_paid']);
$m->export(['name', 'max_delivery', 'total_paid']);

The above code is more concise and can be used together with reference declaration,
although this is how it works::

$m = new Model_Client($db);
$m->addExpression('max_delivery', $m->refLink('Invoice')->action('fx', ['max', 'shipping']));
$m->addExpression('total_paid', $m->refLink('Payment')->action('fx', ['sum', 'amount']));
$m->export(['name','max_delivery','total_paid']);
$m->addExpression('max_delivery', ['expr' => $m->refLink('Invoice')->action('fx', ['max', 'shipping'])]);
$m->addExpression('total_paid', ['expr' => $m->refLink('Payment')->action('fx', ['sum', 'amount'])]);
$m->export(['name', 'max_delivery', 'total_paid']);

In this example calling refLink is similar to traversing reference but instead
of calculating DataSet based on Active Record or DataSet it references the actual
Expand Down Expand Up @@ -579,7 +579,7 @@ This is useful with hasMany references::
hasMany::addField() again is a short-cut for creating expression, which you can
also build manually::

$m->addExpression('country', $m->refLink('country_id')->action('field',['name']));
$m->addExpression('country', $m->refLink('country_id')->action('field', ['name']));

Advanced Use of Actions
-----------------------
Expand All @@ -600,7 +600,7 @@ you could do this::
$m = new Model_User($db);
$m->set('username', 'peter');
$m->set('address_1', 'street 49');
$m->set('country_id', (new Model_Country($db))->addCondition('name','UK')->action('field',['id']));
$m->set('country_id', (new Model_Country($db))->addCondition('name', 'UK')->action('field', ['id']));
$m->save();

This way it will not execute any code, but instead it will provide expression
Expand All @@ -617,8 +617,8 @@ however if you're stuck with SQL you can use free-form pattern-based expressions
$m->getRef('Invoice')->addField('total_purchase', ['aggregate' => 'sum', 'field' => 'total']);
$m->getRef('Payment')->addField('total_paid', ['aggregate' => 'sum', 'field' => 'amount']);

$m->addExpression('balance','[total_purchase]+[total_paid]');
$m->export(['name','balance']);
$m->addExpression('balance', ['expr' => '[total_purchase] + [total_paid]']);
$m->export(['name', 'balance']);


Conclusion
Expand Down
6 changes: 3 additions & 3 deletions docs/sql.rst
Original file line number Diff line number Diff line change
Expand Up @@ -144,14 +144,14 @@ Expression will map into the SQL code, but will perform as read-only field other
Stores expression that you define through DSQL expression::

$model->addExpression('age', 'year(now())-[birth_year]');
$model->addExpression('age', ['expr' => 'year(now()) - [birth_year]']);
// tag [birth_year] will be automatically replaced by respective model field

.. php:method:: getDsqlExpression
SQL Expressions can be used inside other SQL expressions::

$model->addExpression('can_buy_alcohol', ['if([age] > 25, 1, 0)', 'type' => 'boolean']);
$model->addExpression('can_buy_alcohol', ['expr' => 'if([age] > 25, 1, 0)', 'type' => 'boolean']);

Adding expressions to model will make it automatically reload itself after save
as default behavior, see :php:attr:`Model::reload_after_save`.
Expand Down Expand Up @@ -397,7 +397,7 @@ fetching like this::
$this->hasOne('parent_id', ['model' => [self::class]]);
$this->addField('name');

$this->addExpression('path', 'get_path([id])');
$this->addExpression('path', ['expr' => 'get_path([id])']);
}
}

Expand Down

0 comments on commit 16cf38e

Please sign in to comment.