Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement QueryBuilderInterface to Updatable #15

Merged
merged 6 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# These are supported funding model platforms

github: byjg
37 changes: 37 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Contributing to byjg/php-micro-orm

First of all, thank you for taking the time to contribute!

## How to Contribute

### Issues

If you encounter any issues, have questions, or need clarification, please open an issue on our [Issues page](https://github.com/your-repo/issues). This helps us track and prioritize bug fixes and enhancements.

### Branches

We have three main branches in this project:

- **master**: Contains the latest code. It is generally stable, but we recommend using it with caution.
- **a.b**: Use this branch for creating PRs. The naming convention follows `a.b`, where `a` is the major release and `b` is the minor release of the current version. For example, if the current release is 4.9.2, use the branch `4.9` for your PR. You can also use `4.9.x-dev` in your composer for development purposes.
- **future release**: This branch is typically `(a+1).0`. For instance, if the current release is 4.9.2, the future release branch will be `5.0`.


### Code Style and Guidelines

- **Follow PSR Standards**: We follow [PSR-1](https://www.php-fig.org/psr/psr-1/), [PSR-2](https://www.php-fig.org/psr/psr-2/), and [PSR-12](https://www.php-fig.org/psr/psr-12/).
- **Write Clear Commit Messages**: Use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
- **Documentation**: Update the documentation for any new features or changes.

### Common Practices

- **Keep Pull Requests Small**: Smaller PRs are easier to review and merge. Focus on one feature or fix per PR.
- **Write Tests**: Ensure your changes are covered by tests. We aim for a high level of test coverage.
- **Respect Reviewers' Time**: Be responsive to feedback and willing to make necessary changes.

### Community

- **Be Respectful**.
- **Collaborate**: We encourage collaboration and open discussion. Don’t hesitate to ask for help or provide feedback.

Thank you for contributing to byjg/php-micro-orm! Your help is appreciated and makes a big difference.
324 changes: 12 additions & 312 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,322 +56,22 @@ These are the key components:
* DbDriverIntarce is the implementation to the Database connection.
* Repository put all this together

## Examples
## Basics

For the examples below we will use the class 'Users';
* [Defining the Model](docs/getting-started-model.md)
* [Querying the Database](docs/querying-the-database.md)
* [Updating the database](docs/updating-the-database.md)
* [Using the Mapper Object](docs/using-mapper-object.md)

```php
<?php
class Users
{
public $id;
public $name;
public $createdate;
}
```

First of all will create a Table Mapping class:

```php
<?php
// Creating the mapping
$mapper = new \ByJG\MicroOrm\Mapper(
Users::class, // The full qualified name of the class
'users', // The table that represents this entity
'id' // The primary key field
);

// Optionally you can define table mappings between the propoerties
// and the database fields;
// The example below will map the property 'createdate' to the database field 'created';
$mapper->addFieldMapping(FieldMap::create('createdate')->withFieldName('created'));
```

Then you need to create the dataset object and the repository:

```php
<?php
$dataset = \ByJG\AnyDataset\Db\Factory::getDbRelationalInstance('mysql://user:password@server/schema');

$repository = new \ByJG\MicroOrm\Repository($dataset, $mapper);
```

Some examples with the repository:

```php
<?php

// Get a single data from your ID
$users = $repository->get(10);

// Persist the entity into the database:
// Will INSERT if does not exists, and UPDATE if exists
$users->name = "New name";
$repository->save($users);
```

### Update Constraints

You can define a constraint to update a record.
If the constraint is not satisfied the update will not be performed.

```php
<?php
$updateConstraint = \ByJG\MicroOrm\UpdateConstraint()::instance()
->withAllowOnlyNewValuesForFields('name');

$users->name = "New name";
$repository->save($users, $updateConstraint);
```

## Advanced uses

Get a collection using the query object:

```php
<?php
$query = \ByJG\MicroOrm\Query::getInstance()
->table('users')
->fields(['id', 'name'])
->where('name like :part', ['part' => 'A%']);

// Will return a collection o 'Users'
$collection = $repository->getByQuery($query);
```

Returning multiples entities with a query:

```php
<?php
$query = \ByJG\MicroOrm\Query::getInstance()
->table('order')
->join('item', 'order.id = item.orderid')
->where('name like :part', ['part' => 'A%']);

// Will return a collection of Orders and Items:
// $collection = [
// [ $order, $item ],
// [ $order, $item ],
// ...
// ];
$collection = $orderRepository->getByQuery(
$query,
[
$itemRepository->getMapper()
]
);
```

## Using FieldAlias

Field alias is an alternate name for a field. This is usefull for disambiguation on join and leftjoin queries.
Imagine in the example above if both tables ITEM and ORDER have the same field called 'ID'.

In that scenario, the value of ID will be overriden. The solution is use the FieldAlias like below:

```php
<?php
// Create the Mapper and the proper fieldAlias
$orderMapper = new \ByJG\MicroOrm\Mapper(...);
$orderMapper->addFieldMapping(FieldMapping::create('id')->withFieldAlias('orderid'));
$itemMapper = new \ByJG\MicroOrm\Mapper(...);
$itemMapper->addFieldMappping(FieldMapping::create('id')->withFieldAlias('itemid'));

$query = \ByJG\MicroOrm\Query::getInstance()
->field('order.id', 'orderid')
->field('item.id', 'itemid')
/* Other fields here */
->table('order')
->join('item', 'order.id = item.orderid')
->where('name like :part', ['part' => 'A%']);

// Will return a collection of Orders and Items:
// $collection = [
// [ $order, $item ],
// [ $order, $item ],
// ...
// ];
$collection = $orderRepository->getByQuery(
$query,
[
$itemRepository->getMapper()
]
);
```

You can also add a MAPPER as a Field. In that case the MAPPER will create the field and the correct aliases.

```php
<?php
$query = \ByJG\MicroOrm\Query::getInstance()
->fields([
$orderRepository->getMapper(),
$itemRepository->getMapper,
]);
```

## Tables without auto increments fields

```php
<?php
// Creating the mapping
$mapper = new \ByJG\MicroOrm\Mapper(
Users::class, // The full qualified name of the class
'users', // The table that represents this entity
'id', // The primary key field
function () {
// calculate and return the unique ID
}
);
```
## Advanced Topics

## Applying functions for Select and Update
* [The Literal Object](docs/the-literal-object.md)
* [Observing the Database](docs/observers.md)
* [Controlling the data queried/updated](docs/controlling-the-data.md)
* [Using FieldAlias](docs/using-fieldalias.md)
* [Tables without auto increments fields](docs/tables-without-auto-increment-fields.md)
* [Using With Recursive SQL Command](docs/using-with-recursive-sql-command.md)

```php
<?php
// Creating the mapping
$mapper = new \ByJG\MicroOrm\Mapper(...);

$fieldMap = FieldMap::create('propertyname') // The property name of the entity class
// The field name of the table. if not defined will use the property name.
->withFieldName('fieldname')
// The field alias of the field in the table. if not defined will use the field name.
->withFieldAlias('alias')
// Returns the pre-processed value before UPDATE/INSERT the $field name
// If the function returns NULL this field will not be included in the UPDATE/INSERT
->withUpdateFunction(function ($field, $instance) {
return $field;
})
// Returns the field value with a post-processed value of $field AFTER query from DB
->withSelectFunction(function ($field, $instance) {
return $field;
})

$mapper->addFieldMapping($fieldMap);
```

## Observers

You can add observers to the repository.
The observer will be called after the insert, update or delete a record in the DB.

```mermaid
flowchart TD
A[MyRepository] --> |1. addObserver| B[Subject]
C[ObserverdRepository] --> |2. Notify Update| B
B --> |3. Execute Callback| A
```

```php
<?php
// This observer will be called after insert, update or delete a record on the table 'triggerTable'
$myRepository->addObserver(new class($this->infoMapper->getTable()) implements ObserverProcessorInterface {
private $table;

public function __construct($table)
{
$this->table = $table;
}

public function process(ObserverData $observerData)
{
// Do something here
}

public function getObserverdTable(): string
{
return $this->table;
}
});
```

The `ObserverData` class contains the following properties:
- `getTable()`: The table name that was affected
- `getEvent()`: The event that was triggered. Can be 'insert', 'update' or 'delete'
- `getData()`: The data that was inserted or updated. It is null in case of delete.
- `getOldData()`: The data before update. In case of insert comes null, and in case of delete comes with the param filters.
- `getRepository()`: The repository is listening to the event (the same as $myRepository)

*Note*: The observer will not be called if the insert, update or delete is called using the DBDriver object.

## Using With Recursive SQL Command

```php
<?php
$recursive = \ByJG\MicroOrm\Recursive::getInstance('test')
->field('start', 1, 'start + 10')
->field('end', 120, "end - 10")
->where('start < 100')
;

$query = \ByJG\MicroOrm\Query::getInstance()
->withRecursive($recursive)
->fields(['start', 'end']);

/*
This will produce the following SQL:

WITH RECURSIVE test(start, end) AS (
SELECT 1 as start, 120 as end
UNION ALL SELECT start + 10, end - 10 FROM test WHERE start < 100
) SELECT start, end FROM test
*/
```

## Pre-defined closures for field map

### Mapper::defaultClosure($value, $instance)

Defines the basic behavior for select and update fields;

### Mapper::doNotUpdateClosure($value, $instance)

If set in the update field map will make the field not updatable by the micro-orm.
It is usefull for fields that are pre-defined like 'Primary Key'; timestamp fields based on the
update and the creation; and others

## Before insert and update functions

You can also set closure to be applied before insert or update a record.
In this case will set in the Repository:

```php
<?php
Repository::setBeforeInsert(function ($instance) {
return $instance;
});

Repository::setBeforeUpdate(function ($instance) {
return $instance;
});
```

## TransactionManager object

It allows you to create a single database transaction with multiple repositories.
If any of the repositories fails the transaction will be rolled back for all repositories.
When you commit the transaction all repositories will be commited.

```php
<?php
$repo1 = new Repository(...);
$repo2 = new Repository(...);

// Create the TransactionManager
$transactionManager = new TransactionManager();
$transactionManager->addRepository($repo1);
$transactionManager->addRepository($repo2);

// Start the transaction
$transactionManager->beginTransaction();

//
// Do some Repository operations with the repo;
// ...

// commit (or rollback all transactions)
$transactionManager->commitTransaction();
```

## Install

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"require": {
"php": ">=7.4",
"ext-json": "*",
"byjg/anydataset-db": "4.9.*"
"byjg/anydataset-db": "4.9.x-dev"
},
"require-dev": {
"phpunit/phpunit": "5.7.*|7.4.*|^9.6"
Expand Down
Loading