Monolog: Monolog is a logging library for PHP used by Symfony.
Cookbook: http://symfony.com/doc/current/cookbook/logging/monolog.html
LexikMonologBrowserBundle: https://github.com/lexik/LexikMonologBrowserBundle
This Symfony2 bundle provides a Doctrine DBAL handler for Monolog and a web UI to display log entries.
I am using an existing Doctrine configuration in config.yml:
#config.yml
default:
driver: "%database_driver%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user_prod%"
password: "%database_password_prod%"
charset: UTF8
logging: false
profiling: false
If you do not have the last two lines, you should add them to avoid circular reference error.
And also add lexik_monolog_browser config to use the existing Doctrine connection:
lexik_monolog_browser:
doctrine:
connection_name: default
table_name: monolog_entries
Generatate the table in the database:
>php app/console lexik:monolog-browser:schema-create
Add the below to config_dev.yml and/or config_prod.yml in order for Monolog to use our handler to save into database.
# app/config/config_prod.yml # or any env
monolog:
handlers:
main:
type: fingers_crossed # or buffer
level: error
handler: lexik_monolog_browser
app:
type: buffer
action_level: info
channels: app
handler: lexik_monolog_browser
deprecation:
type: buffer
action_level: warning
channels: deprecation
handler: lexik_monolog_browser
lexik_monolog_browser:
type: service
id: lexik_monolog_browser.handler.doctrine_dbal
Import routes to app/config/routing.yml:
# app/config/routing.yml
lexik_monolog_browser:
resource: "@LexikMonologBrowserBundle/Resources/config/routing.xml"
prefix: /admin/monolog
More:
It would be good to save in logs also the current user. For this you can add in your controller:
//log current user in Monolog
$logger = $this->get('logger');
$logger->info('Current user is : ' . $this->getUser()->getUsername());
Cristian Pana - personal blog on software development, PHP, Symfony Framework, web technologies.
Tuesday, February 23, 2016
Friday, February 5, 2016
Why and how to use services in Symfony
Controllers
Symfony follows the philosophy of “thin controllers and fat models”.
This means that controllers should hold just the thin layer of glue-code
needed to coordinate the different parts of the application.
As a rule of thumb, you should follow the 5-10-20 rule, where controllers should only define 5 variables or less, contain 10 actions or less and include 20 lines of code or less in each action. This isn't an exact science, but it should help you realize when code should be refactored out of the controller and into a service. Source: http://symfony.com/doc/current/best_practices/controllers.html
A unit test is a test against a single PHP class, also called a unit. If you want to test the overall behavior of your application, then use Functional Tests. Because controllers glue together the different parts of the applications they are not usually tested with Unit Tests, instead they are tested with Functional Tests.
As a rule of thumb, you should follow the 5-10-20 rule, where controllers should only define 5 variables or less, contain 10 actions or less and include 20 lines of code or less in each action. This isn't an exact science, but it should help you realize when code should be refactored out of the controller and into a service. Source: http://symfony.com/doc/current/best_practices/controllers.html
A unit test is a test against a single PHP class, also called a unit. If you want to test the overall behavior of your application, then use Functional Tests. Because controllers glue together the different parts of the applications they are not usually tested with Unit Tests, instead they are tested with Functional Tests.
Organizing Your Business Logic
Useful info: http://symfony.com/doc/current/best_practices/business-logic.html
It is very easy to start writing business logic into controller, but this way we will end up with a large function, difficult to read which will break separation of concerns principle https://en.wikipedia.org/wiki/Separation_of_concerns and makes it impossible to test only the business logic.
In order to better organize the code, additional classes should be created. Let's take as an example a class that handles file uploading. Basically it receives the $file uploaded in form and the path where to save the file with a unique name in order to avoid collisions with previous uploaded files. The function returns the name of the file saved on the disk to be persisted in database.
In your controller, the FileUpload class will be used like this:
It is very easy to start writing business logic into controller, but this way we will end up with a large function, difficult to read which will break separation of concerns principle https://en.wikipedia.org/wiki/Separation_of_concerns and makes it impossible to test only the business logic.
In order to better organize the code, additional classes should be created. Let's take as an example a class that handles file uploading. Basically it receives the $file uploaded in form and the path where to save the file with a unique name in order to avoid collisions with previous uploaded files. The function returns the name of the file saved on the disk to be persisted in database.
namespace YourBundle\Utils; Class FileUpload { public function upload(\Symfony\Component\HttpFoundation\File\UploadedFile $file, $uploadDir) { // Generate a unique name for the file before saving it $fileName = md5(uniqid()).$file->guessExtension(); // Move the file to the directory where uploads are stored $file->move($uploadDir, $fileName); // Update the 'Attachement' property to store the file name // instead of its contents return $fileName; } }The file uploading method is based on this article from documentation: http://symfony.com/doc/current/cookbook/controller/upload_file.html
In your controller, the FileUpload class will be used like this:
use YourBundle\Utils\FileUpload;
....
public function createAction(Request $request)
{
....
if ($form->isValid()) {
// $file stores the uploaded file
// @var Symfony\Component\HttpFoundation\File\UploadedFile $file
$file = $product->getBrochure();
// Obtain from parameters the directory where brochures are stored
$uploadDir = $this->container->getParameter('kernel.root_dir').'/../web/uploads/brochures';
//use FileUpload class
$uploader= new FileUpload();
$fileName = $uploader->upload($file, $uploadDir);
// Update the 'brochure' property to store the file name
// instead of its contents
$product->setBrochure($fileName);
// ... persist the $product variable or any other work
return $this->redirect($this->generateUrl('app_product_list'));
}
.....
}
OK, so now we have a testable function upload() which can be tested
independently from the rest of the controller, we just need a mock
object of type 'Symfony\Component\HttpFoundation\File\UploadedFile' and a
path.
Maintaining the code
Parameters
We are very happy with the result and we started using this class in all
controllers, just that at some point we receive a request to change
the path where to save the file uploaded. This means we need to search
in all controllers and change this line:
In order to prevent this in future we can declare a parameter in a local parameters.yml which is imported in the global one:
and use it in all controllers:
$uploadDir = $this->container->getParameter('kernel.root_dir').'/../web/uploads/brochures';
In order to prevent this in future we can declare a parameter in a local parameters.yml which is imported in the global one:
parameters:
yourbundle.upload_dir: '%kernel.root_dir%/../web/uploads/attachments/YourBundle/Upload'
and use it in all controllers:
$uploadDir = $this->container->getParameter('yourbundle.upload_dir');
Dependencies
Now we are happy again and think nothing bad can happen to our code :).
But then again there is a request to save the files to a second path, a
Back Up folder let's say. We could do this by adding a third parameter
in the upload() function:
And we have to modify in all controllers where we are using this function. We will declare this BackUp dir as a parameter also and use it like this:
So we realize that injecting dependencies directly in our function is not a solution. Usually the recommended way to inject dependencies is in constructor definition or by using setters. Let's inject dependencies in constructor:
public function upload(\Symfony\Component\HttpFoundation\File\UploadedFile $file, $uploadDir, $uploadDirBackUp)
And we have to modify in all controllers where we are using this function. We will declare this BackUp dir as a parameter also and use it like this:
$uploadDirBackUp = $this->container->getParameter('yourbundle.upload_dir_backup');
$fileName = $uploader->upload($file, $uploadDir, $uploadDirBackUp);
So we realize that injecting dependencies directly in our function is not a solution. Usually the recommended way to inject dependencies is in constructor definition or by using setters. Let's inject dependencies in constructor:
namespace YourBundle\Utils;
Class FileUpload
{
$uploadDir;
$uploadDirBackUp;
public function __construct($uploadDir, $uploadDirBackUp)
{
$this->uploadDir = $uploadDir;
$this->uploadDirBackUp = $uploadDirBackUp;
}
public function upload(\Symfony\Component\HttpFoundation\File\UploadedFile $file)
{
// Generate a unique name for the file before saving it
$fileName = md5(uniqid()).$file->guessExtension();
// Move the file to the directory where uploads are stored
$file->move($this->uploadDir, $fileName);
....
}
}
Even if the dependencies are injected in constructor, we would still have to modify all the controllers:use YourBundle\Utils\FileUpload;
....
public function createAction(Request $request)
{
....
// Move the file to the directory where brochures are stored
$uploadDir = $this->container->getParameter('yourbundle.upload_dir');
$uploadDirBackUp = $this->container->getParameter('yourbundle.upload_dir_backup');
$uploader= new FileUpload($uploadDir,$uploadDirBackUp);
$fileName = $uploader->upload($file);
.....
}
The solution? Creating a service!
What is a Service?
Put simply, a Service is any PHP object that performs some sort of
“global” task. It's a purposefully-generic name used in computer science
to describe an object that's created for a specific purpose (e.g.
delivering emails). Each service is used throughout your application
whenever you need the specific functionality it provides. You don't have
to do anything special to make a service: simply write a PHP class with
some code that accomplishes a specific task. Congratulations, you've
just created a service!
As a rule, a PHP object is a service if it is used globally in your application. A single Mailer service is used globally to send email messages whereas the many Message objects that it delivers are not services. Similarly, a Product object is not a service, but an object that persists Product objects to a database is a service.
A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects).
Source: http://symfony.com/doc/current/book/service_container.html
As a rule, a PHP object is a service if it is used globally in your application. A single Mailer service is used globally to send email messages whereas the many Message objects that it delivers are not services. Similarly, a Product object is not a service, but an object that persists Product objects to a database is a service.
A Service Container (or dependency injection container) is simply a PHP object that manages the instantiation of services (i.e. objects).
Source: http://symfony.com/doc/current/book/service_container.html
Create the service
In your bundle create a file called services.yml under Resources\config
Note: make sure that your bundle is not configured to expect a XML (services.xml) file in YourBundle\DependencyInjection\YourExtension.php:
$loader = new Loader\YamlFileLoader($container, new FileLocator(DIR__.'/../Resources/config'));
services:
yourbundle_file_upload_service:
class: YourBundle\Utils\FileUpload
arguments: ['%yourbundle.upload_dir%','%yourbundle.upload_dir_backup%' ]
Now the class will not be directly instantiated, instead we will access it from the container. In controller: $fileUpload = $this->get('yourbundle_file_upload_service');
$fileName = $fileUpload->upload($file);
What we've accoplished? Now each time we want to add or modify a
dependency we need to edit just in two places: in the services.yml file
and in the contruct() function. Note: make sure that your bundle is not configured to expect a XML (services.xml) file in YourBundle\DependencyInjection\YourExtension.php:
$loader = new Loader\YamlFileLoader($container, new FileLocator(DIR__.'/../Resources/config'));
$loader->load('services.yml');
Monday, February 1, 2016
About testing
1. Testing in Symfony
Roughly speaking, there are two types of test. Unit testing allows you
to test the input and output of specific functions. Functional testing
allows you to command a “browser” where you browse to pages on your
site, click links, fill out forms and assert that you see certain things
on the page.
1.1. Unit Tests
Unit tests are used to test your “business logic”, which should live in
classes that are independent of Symfony. For that reason, Symfony
doesn't really have an opinion on what tools you use for unit testing.
However, the most popular tools are PhpUnit and PhpSpec.
1.2. Functional Tests
Creating really good functional tests can be tough so some developers
skip these completely. Don't skip the functional tests! By defining some
simple functional tests, you can quickly spot any big errors before you
deploy them:
1.3. Testing JavaScript Functionality
The built-in functional testing client is great, but it can't be used to
test any JavaScript behavior on your pages. If you need to test this,
consider using the Mink or Selenium.
The above text is based on official Symfony documentation found here: http://symfony.com/doc/current/best_practices/tests.html
The above text is based on official Symfony documentation found here: http://symfony.com/doc/current/best_practices/tests.html
2. Acceptance testing
Acceptance testing can be performed by a non-technical person. That
person can be your tester, manager or even client. If you are developing
a web-application (and probably you are) the tester needs nothing more
than a web browser to check that your site works correctly. You can
reproduce a AcceptanceTester's actions in scenarios and run them
automatically after each site change. Codeception keeps tests clean and simple, as if they were recorded from the words of AcceptanceTester.
2.1. Functional vs Acceptance testing
Acceptance tests are front-end only and you are testing
whether you see the correct UI elements. This type of test never
explicitly checks the database with a direct call. Think of it as only
testing what an end-user would be able to see by clicking around in a
browser.
Functional tests are similar to acceptance tests but they do check the database explicitly, something like $I→seeInDatabase() or $I→seeRecord(). Functional tests generally use a DOM parser instead of a browser (or phantomJS) like acceptance tests do – this is why JS is best left to acceptance tests.
Functional tests are similar to acceptance tests but they do check the database explicitly, something like $I→seeInDatabase() or $I→seeRecord(). Functional tests generally use a DOM parser instead of a browser (or phantomJS) like acceptance tests do – this is why JS is best left to acceptance tests.
3. All in one: Codeception
With Codeception you can run unit, functional and acceptance tests..
Why should I use Codeception instead of PHPUnit?
Being the most popular unit testing framework PHPUnit has very limited features for functional testing with Selenium or other backends. Codeception is PHPUnit on steroids. Everything you need for testing is built-in and works just out of the box. No more pain in configuring Selenium, data cleanup, writing XPaths, and fixtures.
Q: It looks just like Behat
Unlike Behat, Codeception tests are written in PHP. Thus, they are more flexible and easy in writing. Also you can use variables and operators, CSS and XPath locators in your tests. These features allow you to build a solid test automation platform for testing your web application. Codeception tests are simple and readable for your developers, managers, and QA team.
Q: We are planning to use Selenium IDE. Why Codeception?
Codeception works great with Selenium. But with Codeception you can write your tests in PHP. The main reason is: Selenium IDE tests are tightly bound to XPath locators. If you ever change anything in layout tests will fall. Codeception locators are more stable. You can use names, labels, button names and CSS to match elements on page. It's much easier to support the Codeception test then Selenium's one. Selenium just can't clean data between tests, can't check database values, or generate code coverage reports.
Q: Is Codeception a tool for testing legacy projects?
Sure you can use Codeception for black-box testing of your legacy application. But in the same manner you can start testing modern web application as well. With modules that integrates with all popular PHP frameworks you can start writing functional tests in seconds. Unit tests? Write them as you do in PHPUnit with some enhancement. Codeception keeps your tests in one place, makes them structured and readable.
Source: http://codeception.com/
Why should I use Codeception instead of PHPUnit?
Being the most popular unit testing framework PHPUnit has very limited features for functional testing with Selenium or other backends. Codeception is PHPUnit on steroids. Everything you need for testing is built-in and works just out of the box. No more pain in configuring Selenium, data cleanup, writing XPaths, and fixtures.
Q: It looks just like Behat
Unlike Behat, Codeception tests are written in PHP. Thus, they are more flexible and easy in writing. Also you can use variables and operators, CSS and XPath locators in your tests. These features allow you to build a solid test automation platform for testing your web application. Codeception tests are simple and readable for your developers, managers, and QA team.
Q: We are planning to use Selenium IDE. Why Codeception?
Codeception works great with Selenium. But with Codeception you can write your tests in PHP. The main reason is: Selenium IDE tests are tightly bound to XPath locators. If you ever change anything in layout tests will fall. Codeception locators are more stable. You can use names, labels, button names and CSS to match elements on page. It's much easier to support the Codeception test then Selenium's one. Selenium just can't clean data between tests, can't check database values, or generate code coverage reports.
Q: Is Codeception a tool for testing legacy projects?
Sure you can use Codeception for black-box testing of your legacy application. But in the same manner you can start testing modern web application as well. With modules that integrates with all popular PHP frameworks you can start writing functional tests in seconds. Unit tests? Write them as you do in PHPUnit with some enhancement. Codeception keeps your tests in one place, makes them structured and readable.
Source: http://codeception.com/
Thursday, January 21, 2016
Using multiple databases with Symfony2 and Doctrine2
I was looking for documentation on how to use multiple databases with Symfony and all the results were talking about having 2 connections with 2 entity managers and adding mappings for each bundle ...too complicated for a simple thing.
But I kept on searching and I found this article: https://techpunch.co.uk/development/using-multiple-databases-with-symfony2-and-doctrine2 which is saying:
"
You will need to use only connection that spans all of your databases, if you want to build a relationship between entities then they must use the same connection. Do to this you will need a user that has access to all of the databases the you wish to access. Setup this user as per usual within your Symfony2 application, for the database name just select one of the databases, it doesn't matter which one.
No extra Doctrine config is needed to get this working, ....
The key to getting multiple databases to work is within your entity classes, you need to specify the table name of the entity with a prefix of the name of the database to which the table belongs. Here is an example using annotations:
"
I tried this solution on my project and worked fine.
You may want to look also at Doctrine's Master-Slave Connection: http://blog.alejandrocelaya.com/2014/04/18/configure-multiple-database-connections-in-doctrine-with-zend-framework-2-2/
But I kept on searching and I found this article: https://techpunch.co.uk/development/using-multiple-databases-with-symfony2-and-doctrine2 which is saying:
"
You will need to use only connection that spans all of your databases, if you want to build a relationship between entities then they must use the same connection. Do to this you will need a user that has access to all of the databases the you wish to access. Setup this user as per usual within your Symfony2 application, for the database name just select one of the databases, it doesn't matter which one.
No extra Doctrine config is needed to get this working, ....
The key to getting multiple databases to work is within your entity classes, you need to specify the table name of the entity with a prefix of the name of the database to which the table belongs. Here is an example using annotations:
<?php
namespace Demo\UserBundle\Entity;
use DoctrineORMMapping as ORM;
/**
* Demo\UserBundle\Entity\User
*
* @ORMTable(name="users.User")
*/
class User implements
{
/* ... */
"
I tried this solution on my project and worked fine.
You may want to look also at Doctrine's Master-Slave Connection: http://blog.alejandrocelaya.com/2014/04/18/configure-multiple-database-connections-in-doctrine-with-zend-framework-2-2/
Monday, January 18, 2016
Useful JavaScript libraries
Below are some JS libraries I used and they work :) :
I found this JQuery pugin http://malsup.com/jquery/form/ with decent documentation which meet the requirements.
You may want to stop the user to insert data directly in the input field manually, this can be archived by setting 'readonly' parameter to 'true'. The problem is that the field looks disabled all the time, you may encounter some users thinking that they should not click on that input field.
Solution: make the input field readonly when the user clicks on it and after closing the Datepicker the input field will be made again writable ('readonly' set to 'false').
beforeShow: function(input, inst) {
$(input).attr('readonly', true);
},
onClose: function(dateText, inst) {
$(this).attr('readonly', false);
}
https://github.com/parndt/jquery-html5-placeholder-shim
The script will automatically execute itself on the
Also Bootstrap theme for Chosen: https://github.com/dbtek/chosen-bootstrap
If you form is dynamically loaded you can use it like this:
<script>
$( document ).ajaxComplete(function() {
$.placeholder.shim();
$("#select_input").chosen();
});
</script>
1. Sending AJAX form with attachments (which works on IE9 )
I found this JQuery pugin http://malsup.com/jquery/form/ with decent documentation which meet the requirements.
Quick Start Guide
1. Add a form to your page. Just a normal form, no special markup required:
<form id="myForm" action="comment.php" method="post">
Name: <input type="text" name="name" />
Comment: <textarea name="comment"></textarea>
<input type="submit" value="Submit Comment" />
</form>
2. Include jQuery and the Form Plugin external script files and a short script to
initialize the form when the DOM is ready:
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>
<script src="http://malsup.github.com/jquery.form.js"></script>
<script>
// wait for the DOM to be loaded
$(document).ready(function() {
// bind 'myForm' and provide a simple callback function
$('#myForm').ajaxForm(function() {
alert("Thank you for your comment!");
});
});
</script>
</head>
2. Date picker with options (which works on IE9 )
The answer is Datepicker from JQuery UI: https://jqueryui.com/datepicker/You may want to stop the user to insert data directly in the input field manually, this can be archived by setting 'readonly' parameter to 'true'. The problem is that the field looks disabled all the time, you may encounter some users thinking that they should not click on that input field.
Solution: make the input field readonly when the user clicks on it and after closing the Datepicker the input field will be made again writable ('readonly' set to 'false').
beforeShow: function(input, inst) {
$(input).attr('readonly', true);
},
onClose: function(dateText, inst) {
$(this).attr('readonly', false);
}
3. HTML 5 Placeholder working on IE9 (make sure not to send default values when submitting the form)
https://github.com/parndt/jquery-html5-placeholder-shim
Usage
Just include thejquery.html-placeholder-shim.js
script into your document head like so:<head>
<script type='text/javascript' src='jquery.js'></script>
<script type='text/javascript' src='jquery.html5-placeholder-shim.js'></script>
</head>
$(document).ready
event and can be re-executed at any time (for example, to add
placeholders to text boxes created during dynamic changes to the page)
by running $.placeholder.shim();
.HTML5 placeholder Example:
<input type="search" placeholder="search the internets" name="query" />
4. Searchable Drop down list
http://harvesthq.github.io/chosen/Also Bootstrap theme for Chosen: https://github.com/dbtek/chosen-bootstrap
If you form is dynamically loaded you can use it like this:
<script>
$( document ).ajaxComplete(function() {
$.placeholder.shim();
$("#select_input").chosen();
});
</script>
Monday, January 11, 2016
CPANAGeneratorBundle
I was looking for a CRUD generator able to do more than the one from Sensio Labs, this is how I found PUGXGeneratorBundle.
PUGXGeneratorBundle is extending SensioGeneratorBundle and very important has decent documentation. Some of the functionalities I like from it:
CPANAGeneratorBundle adds to the Show view of an entity the associated objects from Bidirectional relations.
Example: there are 2 entities: Author and Book found in One-to-Many BIDIRECTIONAL relation. In 'Author' entity there is a property called 'books' of type ArrayCollection. In the author/show view after the fields related to Author there will be listed the Books associated. Also CPANAGeneratorBundle is adding buttons for Add book, view and edit.
Author
Last name: Herbert
First name: Frank
Nationality: American
Id: 1
Books
Add book
Title: Dune Chronicles
Genre : Science Fiction
Id:1
view edit
Title: Dune Mesiah
Genre: Science Fiction
Id: 2
view edit
You can find it on Github: https://github.com/cristianpana86/GeneratorBundle
and on Packagist for installing via Composer: https://packagist.org/packages/cpana/generator-bundle
PUGXGeneratorBundle is extending SensioGeneratorBundle and very important has decent documentation. Some of the functionalities I like from it:
- support for form themes (customizable)
- default templates suitable with Boostrap and Font Awesome
- nice "check" icons for boolean fields (when using Font Awesome)
- support for pagination (requires KnpPaginatorBundle)
- support for filters (requires LexikFormFilterBundle)
- support for sorting
CPANAGeneratorBundle adds to the Show view of an entity the associated objects from Bidirectional relations.
Example: there are 2 entities: Author and Book found in One-to-Many BIDIRECTIONAL relation. In 'Author' entity there is a property called 'books' of type ArrayCollection. In the author/show view after the fields related to Author there will be listed the Books associated. Also CPANAGeneratorBundle is adding buttons for Add book, view and edit.
Author
Last name: Herbert
First name: Frank
Nationality: American
Id: 1
Books
Add book
Title: Dune Chronicles
Genre : Science Fiction
Id:1
view edit
Title: Dune Mesiah
Genre: Science Fiction
Id: 2
view edit
You can find it on Github: https://github.com/cristianpana86/GeneratorBundle
and on Packagist for installing via Composer: https://packagist.org/packages/cpana/generator-bundle
Friday, January 8, 2016
Symfony custom constaint: check total size of several form attachements
The cookbook link on how to create a custom constraint is this one:
http://symfony.com/doc/master/cookbook/validation/custom_constraint.html
Request: total size of form attachments should not be bigger than a given value.
Implementation:
I've created a directory 'Validator' under my Bundle where I added two classes:
- FilesTotalSize which extends class Constaint
- FilesTotalSizeValidator extends ConstaintValidator
FilesTotalSize:
<?php
namespace Bundle\Validator;
use Symfony\Component\Validator\Constraint;
/** @Annotation */
class FilesTotalSize extends Constraint
{
public $message;
public function validatedBy()
{
return get_class($this).'Validator';
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
The name of the Validation class will be constraint class concatenated with 'Validator';
Because this constraint is checking values from several fields is considered a class validation (as opposite to field validation which can be seen in the documentation examples ).
For this reason getTargets will return CLASS_CONSTRAINT.
The actual check of the condition is made in the validator class. The variable '$value' will give access to the object to be validated. 'attachement1', 'attachement2' and 'attachement3' are the names of the fields declared in the Entity used for file uploading (file). The size is expressed in bytes, so for 3MB I have 3 millions of bytes. The method 'addViolation' receive as parameter the error message to be displayed.
.
FilesTotalSizeValidator:
<?php
namespace Bundle\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class FilesTotalSizeValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$size_att1=0;
$size_att2=0;
$size_att3=0;
if (property_exists($value, 'attachement1')){
if(!is_null($value->getAttachement1())){
$size_att1= $value->getAttachement1()->getClientSize();
}
}
if (property_exists($value, 'attachement2')){
if(!is_null($value->getAttachement2())){
$size_att2= $value->getAttachement2()->getClientSize();
}
}
if (property_exists($value, 'attachement3')){
if(!is_null($value->getAttachement3())){
$size_att3= $value->getAttachement3()->getClientSize();
}
}
$totalSize=0;
$totalSize=(int)$size_att1 +(int)$size_att2 +(int)$size_att3;
if ($totalSize > 3145728) { //check if total size of files is bigger than 3MB
$this->context->addViolation('Maximum size is 3MB!');
}
}
}
Usage:
In the entity class:
use MyBundle\Validator\FilesTotalSize;
/**
* @ORM\Entity
* @FilesTotalSize
*/
class EntityName {
Subscribe to:
Posts (Atom)