diff --git a/composer.json b/composer.json index 28fcabd..392eaaa 100644 --- a/composer.json +++ b/composer.json @@ -30,9 +30,9 @@ "laravel-doctrine/orm": "^3.0" }, "require-dev": { - "beberlei/doctrineextensions": "^1.0", + "beberlei/doctrineextensions": "^1.5", "doctrine/coding-standard": "^12.0", - "gedmo/doctrine-extensions": "^2.4 | ^3.0", + "gedmo/doctrine-extensions": "^3.17", "laravel/framework": "^10.0 || ^11.0", "mockery/mockery": "^1.6", "php-parallel-lint/php-parallel-lint": "^1.4", @@ -86,4 +86,4 @@ "dealerdirect/phpcodesniffer-composer-installer": true } } -} \ No newline at end of file +} diff --git a/docs/blameable.md b/docs/blameable.md new file mode 100644 index 0000000..648b8c9 --- /dev/null +++ b/docs/blameable.md @@ -0,0 +1,113 @@ +# Blameable + +Blameable behavior will automate the update of username or user reference fields on your Entities or Documents. It works through annotations and can update fields on creation, update, property subset update, or even on specific property value change. + +* Automatic predefined user field update on creation, update, property subset update, and even on record property changes +* Specific annotations for properties, and no interface required +* Can react to specific property or relation changes to specific value +* Can be nested with other behaviors +* Annotation, Yaml and Xml mapping support for extensions + +### Installation + +Add `LaravelDoctrine\Extensions\Blameable\BlameableExtension` to `doctrine.extensions` config. + +### Property annotation + +> @Gedmo\Mapping\Annotation\Blameable + +This annotation tells that this column is blameable +by default it updates this column on update. If column is not a string field or an association +it will trigger an exception. + +Available configuration options: + +| Annotations | Description | +|--|--| +| **on** |is main option and can be **create, update, change** this tells when it should be updated| +| **field** | only valid if **on="change"** is specified, tracks property or a list of properties for changes | +| **value** | only valid if **on="change"** is specified and the tracked field is a single field (not an array), if the tracked field has this **value** then it updates the blame | + +Column is a string field: + +``` php + [ + 'carbondate' => DoctrineExtensions\Types\CarbonDateType::class, + 'carbondatetime' => DoctrineExtensions\Types\CarbonDateTimeType::class, + 'carbondatetimetz' => DoctrineExtensions\Types\CarbonDateTimeTzType::class, + 'carbontime' => DoctrineExtensions\Types\CarbonTimeType::class + ], + /* + |-------------------------------------------------------------------------- + | Doctrine custom datetime functions + |-------------------------------------------------------------------------- + */ + 'custom_datetime_functions' => [ + 'DATEADD' => DoctrineExtensions\Query\Mysql\DateAdd::class, + 'DATEDIFF' => DoctrineExtensions\Query\Mysql\DateDiff::class + ], + /* + |-------------------------------------------------------------------------- + | Doctrine custom numeric functions + |-------------------------------------------------------------------------- + */ + 'custom_numeric_functions' => [ + 'ACOS' => DoctrineExtensions\Query\Mysql\Acos::class, + 'ASIN' => DoctrineExtensions\Query\Mysql\Asin::class, + 'ATAN' => DoctrineExtensions\Query\Mysql\Atan::class, + 'ATAN2' => DoctrineExtensions\Query\Mysql\Atan2::class, + 'COS' => DoctrineExtensions\Query\Mysql\Cos::class, + 'COT' => DoctrineExtensions\Query\Mysql\Cot::class, + 'DEGREES' => DoctrineExtensions\Query\Mysql\Degrees::class, + 'RADIANS' => DoctrineExtensions\Query\Mysql\Radians::class, + 'SIN' => DoctrineExtensions\Query\Mysql\Sin::class, + 'TAN' => DoctrineExtensions\Query\Mysql\Ta::class + ], + /* + |-------------------------------------------------------------------------- + | Doctrine custom string functions + |-------------------------------------------------------------------------- + */ + 'custom_string_functions' => [ + 'CHAR_LENGTH' => DoctrineExtensions\Query\Mysql\CharLength::class, + 'CONCAT_WS' => DoctrineExtensions\Query\Mysql\ConcatWs::class, + 'FIELD' => DoctrineExtensions\Query\Mysql\Field::class, + 'FIND_IN_SET' => DoctrineExtensions\Query\Mysql\FindInSet::class, + 'REPLACE' => DoctrineExtensions\Query\Mysql\Replace::class, + 'SOUNDEX' => DoctrineExtensions\Query\Mysql\Soundex::class, + 'STR_TO_DATE' => DoctrineExtensions\Query\Mysql\StrToDate::class + ], + +]; diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..0816cec --- /dev/null +++ b/docs/index.md @@ -0,0 +1,19 @@ +- Prologue + - [Introduction](/docs/{{version}}/extensions/introduction) + - [Upgrade Guide](/docs/{{version}}/extensions/upgrade) +- Setup + - [Laravel](/docs/{{version}}/extensions/installation) + - [Lumen](/docs/{{version}}/extensions/lumen) +- Behavioral extensions + - [Blameable](/docs/{{version}}/extensions/blameable) + - [IpTraceable](/docs/{{version}}/extensions/iptraceable) + - [Loggable](/docs/{{version}}/extensions/loggable) + - [Sluggable](/docs/{{version}}/extensions/sluggable) + - [SoftDeletes](/docs/{{version}}/extensions/softdeletes) + - [Sortable](/docs/{{version}}/extensions/sortable) + - [Timestamps](/docs/{{version}}/extensions/timestamps) + - [Translatable](/docs/{{version}}/extensions/translatable) + - [Tree](/docs/{{version}}/extensions/tree) + - [Uploadable](/docs/{{version}}/extensions/uploadable) +- Query/Type extensions + - [Custom functions](/docs/{{version}}/extensions/custom-functions) diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..073789f --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,38 @@ +# Installation in Laravel 6+ + +Install this package with composer: + +``` +composer require laravel-doctrine/extensions +``` + +This package wraps extensions from [Gedmo](https://github.com/Atlantic18/DoctrineExtensions) and [Beberlei](https://github.com/beberlei/DoctrineExtensions). + +To include Gedmo extensions install them: + +``` + +composer require "gedmo/doctrine-extensions=^3.0" +``` + +If you are using an **annotation driver**, then add the Gedmo (Behavioral) extensions service provider in `config/app.php`: + +```php +LaravelDoctrine\Extensions\GedmoExtensionsServiceProvider::class, +``` + +Also be sure to enable the extensions in the `extensions` section of `config/doctrine.php`. + +To include Beberlei (Query/Type) extensions install them: + +``` + +composer require "beberlei/DoctrineExtensions=^1.0" +``` + +And then add the Beberlei extensions service provider in `config/app.php`: + + +```php +LaravelDoctrine\Extensions\BeberleiExtensionsServiceProvider::class, +``` diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 0000000..269bf77 --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,28 @@ +# Introduction + +This package contains extensions for Doctrine2 that hook into the facilities of Doctrine and offer new functionality +or tools to use Doctrine2 more efficiently. This package contains mostly used behaviors which can be easily attached to your event system +of Doctrine2 and handle the records being flushed in the behavioral way + +### Behavioral extensions (Gedmo) + +* __Blameable__ - updates string or reference fields on create, update and even property change with a string or object (e.g. user). +* __IpTraceable__ - inherited from Timestampable, sets IP address instead of timestamp +* __Loggable__ - helps tracking changes and history of objects, also supports version management. +* __Sluggable__ - urlizes your specified fields into single unique slug +* __SoftDeleteable__ - allows to implicitly remove records +* __Sortable__ - makes any document or entity sortable +* __Timestampable__ - updates date fields on create, update and even property change. +* __Translatable__ - gives you a very handy solution for translating records into different languages. Easy to setup, easier to use. +* __Tree__ - this extension automates the tree handling process and adds some tree specific functions on repository. (closure, nestedset or materialized path) +* __Uploadable__ - provides file upload handling in entity fields + +### Query/Type extensions (Beberlei) + +A set of extensions to Doctrine 2 that add support for additional queryfunctions available in MySQL and Oracle. + +| DB | Functions | +|--|---------| +| MySQL | `ACOS, ASCII, ASIN, ATAN, ATAN2, BINARY, CEIL, CHAR_LENGTH, CONCAT_WS, COS, COT, COUNTIF, CRC32, DATE, DATE_FORMAT, DATEADD, DATEDIFF, DATESUB, DAY, DAYNAME, DEGREES, FIELD, FIND_IN_SET, FLOOR, FROM_UNIXTIME, GROUP_CONCAT, HOUR, IFELSE, IFNULL, LAST_DAY, MATCH_AGAINST, MD5, MINUTE, MONTH, MONTHNAME, NULLIF, PI, POWER, QUARTER, RADIANS, RAND, REGEXP, REPLACE, ROUND, SECOND, SHA1, SHA2, SIN, SOUNDEX, STD, STRTODATE, SUBSTRING_INDEX, TAN, TIME, TIMESTAMPADD, TIMESTAMPDIFF, UUID_SHORT, WEEK, WEEKDAY, YEAR` | +| Oracle | `DAY, MONTH, NVL, TODATE, TRUNC, YEAR` | +| Sqlite | `DATE, MINUTE, HOUR, DAY, WEEK, WEEKDAY, MONTH, YEAR, STRFTIME*` | diff --git a/docs/iptraceable.md b/docs/iptraceable.md new file mode 100644 index 0000000..e326875 --- /dev/null +++ b/docs/iptraceable.md @@ -0,0 +1,86 @@ +# Ip Traceable + +IpTraceable behavior will automate the update of IP trace on your Entities or Documents. It works through annotations and can update fields on creation, update, property subset update, or even on specific property value change. + +* Automatic predefined ip field update on creation, update, property subset update, and even on record property changes +* Specific annotations for properties, and no interface required +* Can react to specific property or relation changes to specific value +* Can be nested with other behaviors +* Annotation, Yaml and Xml mapping support for extensions + +### Installation + +Add `LaravelDoctrine\Extensions\IpTraceable\IpTraceableExtension` to `doctrine.extensions` config. + +### Property annotation + +> @Gedmo\Mapping\Annotation\IpTraceable + +This annotation tells that this column is ipTraceable by default it updates this column on update. If column is not a string field it will trigger an exception. + +Available configuration options: + +| Annotations | Description | +|--|--| +| **on** |is main option and can be **create, update, change** this tells when itshould be updated| +| **field** | only valid if **on="change"** is specified, tracks property or a list of properties for changes | +| **value** | only valid if **on="change"** is specified and the tracked field is a single field (not an array), if the tracked field has this **value**then it updates the trace | + +``` php + @Gedmo\Mapping\Annotation\Loggable() + +This class annotation will store logs to optionally specified logEntryClass. + +| Annotations | Description | +|--|--| +| **logEntryClass** |optional entity which stores logs| + +### Property annotation + +>@Gedmo\Mapping\Annotation\Versioned + +This property annotation tracks annotated property for changes + +``` php +register(LaravelDoctrine\Extensions\GedmoExtensionsServiceProvider::class), +``` + +To include Beberlei (Query/Type) extensions install them: + +``` + +composer require "beberlei/DoctrineExtensions=^1.0" +``` + +And then add the Beberlei extensions service provider in `bootstrap/app.php`: + + +```php +$app->register(LaravelDoctrine\Extensions\BeberleiExtensionsServiceProvider::class), diff --git a/docs/sluggable.md b/docs/sluggable.md new file mode 100644 index 0000000..eb0b5c0 --- /dev/null +++ b/docs/sluggable.md @@ -0,0 +1,62 @@ +# Sluggable + +Sluggable behavior will build the slug of predefined fields on a given field which should store the slug + +- Automatic predefined field transformation into slug +- Slugs can be unique and styled, even with prefixes and/or suffixes +- Can be nested with other behaviors +- Annotation, Yaml and Xml mapping support for extensions +- Multiple slugs, different slugs can link to same fields + +### Installation + +Add `LaravelDoctrine\Extensions\Sluggable\SluggableExtension` to `doctrine.extensions` config. + +### Property annotation + +> @Gedmo\Mapping\Annotation\Slug + +This annotation will use the column to store slug generated fields option must be specified, an array of field names to slug + +| Annotations | Description | +|--|--| +| **fields** | array of fields that should be slugged | + +``` php + @Gedmo\Mapping\Annotation\SoftDeleteable + +This class annotation tells if a class is SoftDeleteable. It has a mandatory parameter "fieldName", which is the name of the field to be used to hold the known "deletedAt" field. It must be of any of the date types. + +| Annotations | Description | +|--|--| +| **fieldName** | The name of the field that will be used to determine if the object is removed or not (NULL means it's not removed. A date value means it was removed). NOTE: The field MUST be nullable. | + +``` php + @Gedmo\Mapping\Annotation\SortableGroup + +This annotation will be used for grouping + +> @Gedmo\Mapping\Annotation\SortablePosition + +This annotation will be used to store position index + +``` php + @Gedmo\Mapping\Annotation\Timestampable + +This annotation tells that this column is timestampable by default it updates this column on update. If column is not date, datetime or time type it will trigger an exception. + +| Annotations | Description | +|--|--| +| **on** | is main option and can be create, update, change this tells when it should be updated | +| **field** | only valid if on="change" is specified, tracks property or a list of properties for changes | +| **value** | only valid if on="change" is specified and the tracked field is a single field (not an array), if the tracked field has this value | + +```php + @Gedmo\Mapping\Annotation\Translatable + +| Annotations | Description | +|--|--| +| **class** | it will use this class to store translations generated | + +### Property annotation + +> @Gedmo\Mapping\Annotation\Translatable + +It will translate this field + +> @Gedmo\Mapping\Annotation\Locale or @Gedmo\Mapping\Annotation\Language + +This will identify this column as locale or language used to override the global locale + +```php + @Gedmo\Mapping\Annotation\Tree + +| Annotations | Description | +|--|--| +| **type** | this class annotation sets the tree strategy by using the type parameter. Currently nested, closure or materializedPath strategies are supported. An additional "activateLocking" parameter is available if you use the "Materialized Path" strategy with MongoDB. It's used to activate the locking mechanism (more on that in the corresponding section). | + +### Property annotation + +> @Gedmo\Mapping\Annotation\TreeLeft + +This field is used to store the tree left value + +> @Gedmo\Mapping\Annotation\TreeRight + +This field is used to store the tree right value + +> @Gedmo\Mapping\Annotation\TreeParent + +This will identify the column as the relation to parent node + +> @Gedmo\Mapping\Annotation\TreeLevel + +This field is used to store the tree level + +> @Gedmo\Mapping\Annotation\TreeRoot + +This field is used to store the tree root id value + +> @Gedmo\Mapping\Annotation\TreePath + +(Materialized Path only) This field is used to store the path. It has an optional parameter "separator" to define the separator used in the path. + +> @Gedmo\Mapping\Annotation\TreePathSource + +(Materialized Path only) This field is used as the source to construct the "path" + +```php + @Gedmo\Mapping\Annotation\Uploadable + +This class annotation tells if a class is Uploadable. + +| Annotations | Description | +|--|--| +|**allowOverwrite** | If this option is true, it will overwrite a file if it already exists. If you set "false", an exception will be thrown. Default: false| +|**appendNumber** | If this option is true and "allowOverwrite" is false, in the case that the file already exists, it will append a number to the filename. Example: if you're uploading a file named "test.txt", if the file already exists and this option is true, the extension will modify the name of the uploaded file to "test1.txt", where "1" could be any number. The extension will check if the file exists until it finds a filename with a number as its postfix that is not used. If you use a filename generator and this option is true, it will append a number to the filename anyway if a file with the same name already exists. Default value: false| +|**path** | This option expects a string containing the path where the files represented by this entity will be moved. Default: "". Path can be set in other ways: From the listener or from a method. More details later.| +|**pathMethod** | Similar to option "path", but this time it represents the name of a method on the entity that will return the path to which the files represented by this entity will be moved. This is useful in several cases. For example, you can set specific paths for specific entities, or you can get the path from other sources (like a framework configuration) instead of hardcoding it in the entity. Default: "". As first argument this method takes default path, so you can return path relative to default.| +|**callback** | This option allows you to set a method name. If this option is set, the method will be called after the file is moved. Default value: "". As first argument, this method can receive an array with information about the uploaded file, which includes the following keys: **fileName:**, **fileExtension**, **fileWithoutExt**, **filePath**, **fileMimeType**, **fileSize** .| +|**filenameGenerator**| This option allows you to set a filename generator for the file. There are two already included by the extension: SHA1, which generates a sha1 filename for the file, and ALPHANUMERIC, which "normalizes" the filename, leaving only alphanumeric characters in the filename, and replacing anything else with a "-". You can even create your own FilenameGenerator class (implementing the Gedmo\Uploadable\FilenameGenerator\FilenameGeneratorInterface) and set this option with the fully qualified class name. The other option available is "NONE" which, as you may guess, means no generation for the filename will occur. Default: "NONE".| +|**maxSize**| This option allows you to set a maximum size for the file in bytes. If file size exceeds the value set in this configuration, an exception of type "UploadableMaxSizeException" will be thrown. By default, its value is set to 0, meaning that no size validation will occur.| +|**allowedTypes**| With this option you can set a comma-separated list of allowed mime types for the file. The extension will use a simple mime type guesser to guess the file type, and then it will compare it to the list of allowed types. If the mime type is not valid, then an exception of type "UploadableInvalidMimeTypeException" will be thrown. If you set this option, you can't set the disallowedTypes option described next. By default, no validation of mime type occurs. If you want to use a custom mime type guesser, see this.| +|**disallowedTypes**| Similar to the option allowedTypes, but with this one you configure a "black list" of mime types. If the mime type of the file is on this list, n exception of type "UploadableInvalidMimeTypeException" will be thrown. If you set this option, you can't set the allowedTypes option described next. By default, no validation of mime type occurs. If you want to use a custom mime type guesser, see this.| + +### Property annotation + +> @Gedmo\Mapping\Annotation\UploadableFilePath + +This annotation is used to set which field will receive the path to the file. The field MUST be of type "string". Either this one or UploadableFileName annotation is REQUIRED to be set. + +> @Gedmo\Mapping\Annotation\UploadableFileName + +This annotation is used to set which field will receive the name of the file. The field MUST be of type "string". Either this one or UploadableFilePath annotation is REQUIRED to be set. + +> @Gedmo\Mapping\Annotation\UploadableFileMimeType + +This is an optional annotation used to set which field will receive the mime type of the file as its value. This field MUST be of type "string". + +> @Gedmo\Mapping\Annotation\UploadableFileSize + +This is an optional annotation used to set which field will receive the size in bytes of the file as its value. This field MUST be of type "decimal". + + +```php +addEntityFileInto($entity, $fileInfo); +``` + +Another way would be to also register the `LaravelDoctrine\Extensions\Uploadable\UploadableFacade::class` facade in the `aliases` array in the Laravel's `app.php`. Then you can use the facade to call the listener's methods. + +Example with a facade (named `Uploadable`): +``` +\Uploadable::addEntityFileInfo($entity, $fileInfo); +``` + + +For full documentation see [here.](https://github.com/Atlantic18/DoctrineExtensions/blob/master/doc/uploadable.md) diff --git a/tests/Feature/Uploadable/UploadableExtensionTest.php b/tests/Feature/Uploadable/UploadableExtensionTest.php index cb06d5a..40ca936 100644 --- a/tests/Feature/Uploadable/UploadableExtensionTest.php +++ b/tests/Feature/Uploadable/UploadableExtensionTest.php @@ -14,7 +14,7 @@ class UploadableExtensionTest extends TestCase { public function testCanRegisterExtension(): void { - $this->assertFalse(UploadableFacade::isFake()); + $this->assertInstanceOf(UploadableListener::class, UploadableFacade::getFacadeRoot()); $listener = m::mock(UploadableListener::class);