diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3806554 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +/.idea +composer.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c87ff13 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,50 @@ +language: php + +sudo: false + +cache: + directories: + - $HOME/.composer/cache + +matrix: + include: + - php: 5.5 + env: SYMFONY_VERSION=2.7.* + - php: 5.6 + env: SYMFONY_VERSION=2.7.* + - php: 7.0 + env: SYMFONY_VERSION=2.7.* + - php: 7.0 + env: SYMFONY_VERSION=3.3.* + - php: 7.0 + env: DEPENDENCIES=beta + - php: 7.1 + env: SYMFONY_VERSION=2.7.* + - php: 7.1 + env: SYMFONY_VERSION=3.3.* + - php: 7.1 + env: DEPENDENCIES=beta + - php: nightly + env: SYMFONY_VERSION=2.7.* + - php: nightly + env: SYMFONY_VERSION=3.3.* + - php: nightly + env: DEPENDENCIES=beta + allow_failures: + - php: nightly + +env: + global: + - deps=no + +before_install: + - composer self-update + # Set composer minimum-stability configuration filter to beta versions + - if [ "$DEPENDENCIES" = "beta" ]; then perl -pi -e 's/^}$/,"minimum-stability":"beta"}/' composer.json; fi; + +install: + - if [ "$deps" = "no" ]; then composer update; fi; + - if [ "$deps" = "low" ]; then composer --prefer-lowest --prefer-stable update; fi; + +script: + - vendor/jakub-onderka/php-parallel-lint/parallel-lint --exclude vendor . diff --git a/EventListener/ErrorLogSubscriber.php b/EventListener/ErrorLogSubscriber.php deleted file mode 100644 index 144885a..0000000 --- a/EventListener/ErrorLogSubscriber.php +++ /dev/null @@ -1,135 +0,0 @@ -logger = $logger; - $this->request = $request; - } - - public static function getSubscribedEvents() - { - return array(FormEvents::POST_BIND => 'postBind'); - } - - /** - * - * @param \Symfony\Component\Form\FormEvent $event - * @return null - */ - public function postBind(FormEvent $event) - { - $form = $event->getForm(); - - $errors = $this->getErrorMessages($form); - - if(empty($errors)) { - return null; - } - - $formName = $form->getName(); - - foreach($errors as $key => $error) { - $uri = $this->request->getUri(); - $this->logger->log($formName, $key, $error['messages'], $error['value'], $uri); - } - - return null; - } - - private function getErrorMessages(\Symfony\Component\Form\Form $form) { - - $errors = array(); - - /* Get the errors from this FormType */ - foreach ($form->getErrors() as $key => $error) { - $data = $form->getData(); - - /* If it's a bound object then we need to log it somehow */ - if(is_object($data)) - { - // JsonSerializable is for php 5.4 - if(class_exists('\JsonSerializable', false) && $data instanceof \JsonSerializable) { - $data = json_encode($data); - } - // otherwise we could just see if that method exists - elseif(method_exists($data, 'jsonSerialize')) - { - $data = json_encode($data->jsonSerialize()); - } - // some people create a toArray() method - elseif(method_exists($data, 'toArray') && is_array($array = $data->toArray())) - { - // JSON_PRETTY_PRINT is > PHP 5.4 - if(defined('JSON_PRETTY_PRINT')) { - $data = json_encode($array, JSON_PRETTY_PRINT); - }else { - $data = json_encode($array); - } - - } - // lets try to serialize - // this could be risky if the object is too large or not implemented correctly - elseif(method_exists($data, '__sleep') || $data instanceof Serializable) { - $data = @serialize($data); - } - // lets see if we can get the form data from the request - elseif($this->request->request->has($form->getName())) { - // lets log it - $data = 'POST DATA: '.json_encode($this->request->request->get($form->getName())); - } - // it looks like the object isnt loggable - else { - $data = ''; - } - } - $errors[$key] = array('messages'=>$error->getMessage(), 'value'=>$data); - } - if ($form->count() > 0) { - foreach ($form->all() as $child) { - if (!$child->isValid()) { - $childErrors = $this->getErrorMessages($child); - $messages = $values = array(); - foreach($childErrors as $childError) { - $messages[] = $childError['messages']; - $values[] = $childError['value']; - } - - // if there's more than 1 error or value on a field then we can log them all - $messages = implode(' | ', $messages); - $values = implode(' | ', $values); - - $errors[$child->getName()] = array('messages'=>$messages, 'value'=>$values); - } - } - } - - return $errors; - } -} \ No newline at end of file diff --git a/Logger/DatabaseLogger.php b/Logger/DatabaseLogger.php deleted file mode 100644 index 721f23d..0000000 --- a/Logger/DatabaseLogger.php +++ /dev/null @@ -1,41 +0,0 @@ -em = $em; - $this->entityClass = $entityClass; - } - - public function log($formName, $key, $error, $value = '', $uri = '') - { - if($this->entityClass == 'Oh\FormErrorLogBundle\Entity\FormErrorLogEntityInterface') { - throw new InvalidArgumentException('You need to update your %oh_form_error_log.db.entity.class% parameter to your own class. See the README for help.'); - } - $entity = new $this->entityClass; - - $entity->setFormName($formName); - $entity->setField($key); - $entity->setError($error); - $entity->setValue($value); - // for BC - if(method_exists($entity, 'setUri')) { - $entity->setUri($uri); - } - - $this->em->persist($entity); - - $this->em->flush($entity); - - } - -} \ No newline at end of file diff --git a/Logger/Logger.php b/Logger/Logger.php deleted file mode 100644 index 8da3457..0000000 --- a/Logger/Logger.php +++ /dev/null @@ -1,41 +0,0 @@ -logger = $logger; - } - - /** - * - * @param string $formName - * @param string $key - * @param string $error - * @param string $value - */ - public function log($formName, $key, $error, $value = '', $uri = '') - { - - $this->logger->notice(strtr('%0 - Error in form "%1" in position "%2": "%3" with value "%4"', array( - '%0'=>$uri, - '%1'=>$formName, - '%2'=>$key, - '%3'=>$error, - '%4'=>$value - ))); - - } - -} \ No newline at end of file diff --git a/README.md b/README.md index c0f04a0..7f1aeea 100644 --- a/README.md +++ b/README.md @@ -19,14 +19,11 @@ Installation ------------ This bundle is alpha stability due to the lack of testing on different form -types. Your composer.json needs to reflect that by setting the -minimum-stability to "alpha" or "dev" - - "minimum-stability": "alpha" +types. Install this bundle as usual by adding to composer.json: - "oh/form-error-log-bundle": "dev-master" + "lendable/form-error-log-bundle": "~1.0" Register the bundle in `app/AppKernel.php`: @@ -271,4 +268,5 @@ Todo Credits ------- -* Ollie Harridge (ollietb) as the author. \ No newline at end of file +* Ollie Harridge (ollietb) as the original author. +* Lendable Ltd as the maintainer. \ No newline at end of file diff --git a/Resources/config/services.yml b/Resources/config/services.yml deleted file mode 100644 index 3cdc5ed..0000000 --- a/Resources/config/services.yml +++ /dev/null @@ -1,23 +0,0 @@ -parameters: - oh_form_error_log.listener.class: Oh\FormErrorLogBundle\EventListener\ErrorLogSubscriber - oh_form_error_log.logger.class: Oh\FormErrorLogBundle\Logger\Logger - oh_form_error_log.db.class: Oh\FormErrorLogBundle\Logger\DatabaseLogger - oh_form_error_log.db.entity.class: Oh\FormErrorLogBundle\Entity\FormErrorLogEntityInterface - -services: - oh_form_error_log.logger.db: - class: %oh_form_error_log.db.class% - arguments: [@doctrine.orm.default_entity_manager, %oh_form_error_log.db.entity.class%] - oh_form_error_log.logger: - class: %oh_form_error_log.logger.class% - arguments: [@logger] - tags: - - { name: monolog.logger, channel: formerror } - oh_form_error_log.listener.db: - class: %oh_form_error_log.listener.class% - arguments: [@oh_form_error_log.logger.db, @request] - scope: request - oh_form_error_log.listener: - class: %oh_form_error_log.listener.class% - arguments: [@oh_form_error_log.logger, @request] - scope: request diff --git a/Tests/Controller/DefaultControllerTest.php b/Tests/Controller/DefaultControllerTest.php deleted file mode 100644 index 4aedada..0000000 --- a/Tests/Controller/DefaultControllerTest.php +++ /dev/null @@ -1,17 +0,0 @@ -request('GET', '/hello/Fabien'); - - $this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0); - } -} diff --git a/composer.json b/composer.json index 8992e48..bb571ea 100644 --- a/composer.json +++ b/composer.json @@ -1,24 +1,30 @@ { - "name": "oh/form-error-log-bundle", - "type": "symfony-bundle", - "description": "Log form errors", - "keywords": ["form", "Symfony2"], - "homepage": "https://github.com/ollieLtd/OhFormErrorLogBundle", - "license": "MIT", - "authors": [ - { - "name": "Ollie Harridge", - "email": "code@oll.ie" - } - ], - "require": { - "php": ">=5.3.2", - "symfony/framework-bundle": "~2.1" + "name": "lendable/form-error-log-bundle", + "type": "symfony-bundle", + "description": "Log form errors", + "keywords": ["symfony", "bundle", "lendable"], + "homepage": "https://github.com/ollieLtd/OhFormErrorLogBundle", + "license": "MIT", + "authors": [ + { + "name": "Ollie Harridge", + "email": "code@oll.ie" }, - "autoload": { - "psr-0": { - "Oh\\FormErrorLogBundle": "" - } - }, - "target-dir": "Oh/FormErrorLogBundle" + { + "name": "Lendable Ltd", + "email": "dev@lendable.co.uk" + } + ], + "autoload": { + "psr-4": { + "Oh\\FormErrorLogBundle\\": "src/" + } + }, + "require": { + "php": ">=5.5", + "symfony/symfony": "~2.7|~3.0|~4.0" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "^0.9.2" + } } diff --git a/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php similarity index 68% rename from DependencyInjection/Configuration.php rename to src/DependencyInjection/Configuration.php index 52e5337..0f68d89 100644 --- a/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -20,9 +20,13 @@ public function getConfigTreeBuilder() $treeBuilder = new TreeBuilder(); $rootNode = $treeBuilder->root('oh_form_error_log'); - // Here you should define the parameters that are allowed to - // configure your bundle. See the documentation linked above for - // more information on that topic. + $rootNode + ->children() + ->scalarNode('logger')->isRequired()->cannotBeEmpty()->end() + ->end() + ->children() + ->scalarNode('db_entity_class')->defaultValue('Oh\FormErrorLogBundle\Entity\FormErrorLog')->cannotBeEmpty()->end() + ->end(); return $treeBuilder; } diff --git a/DependencyInjection/OhFormErrorLogExtension.php b/src/DependencyInjection/OhFormErrorLogExtension.php similarity index 78% rename from DependencyInjection/OhFormErrorLogExtension.php rename to src/DependencyInjection/OhFormErrorLogExtension.php index 546b128..88784a1 100644 --- a/DependencyInjection/OhFormErrorLogExtension.php +++ b/src/DependencyInjection/OhFormErrorLogExtension.php @@ -22,7 +22,10 @@ public function load(array $configs, ContainerBuilder $container) $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); - $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $container->setAlias('oh_form_error_log.logger.manager', $config['logger']); + $container->setParameter('oh_form_error_log.db.entity.class', $config['db_entity_class']); + + $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config')); $loader->load('services.yml'); } } diff --git a/src/Entity/FormErrorLog.php b/src/Entity/FormErrorLog.php new file mode 100644 index 0000000..5ef5032 --- /dev/null +++ b/src/Entity/FormErrorLog.php @@ -0,0 +1,133 @@ +form_name; + } + + public function setFormName($formName) + { + $this->form_name = $formName; + } + + public function getField() + { + return $this->field; + } + + public function setField($field) + { + $this->field = $field; + } + + public function getError() + { + return $this->error; + } + + public function setError($error) + { + $this->error = $error; + } + + public function getValue() + { + return $this->value; + } + + public function setValue($value) + { + $this->value = $value; + } + + public function setUri($uri) + { + $this->uri = $uri; + + return $this; + } + + public function getUri() + { + return $this->uri; + } + + public function getCreated() + { + return $this->created; + } + + public function setCreated(\DateTime $created) + { + $this->created = $created; + + return $this; + } + + /** + * @ORM\PrePersist + */ + public function setCreatedValue() + { + $this->created = new \DateTime(); + } +} diff --git a/Entity/FormErrorLogEntityInterface.php b/src/Entity/FormErrorLogEntityInterface.php similarity index 80% rename from Entity/FormErrorLogEntityInterface.php rename to src/Entity/FormErrorLogEntityInterface.php index ee727da..cc4421b 100644 --- a/Entity/FormErrorLogEntityInterface.php +++ b/src/Entity/FormErrorLogEntityInterface.php @@ -9,5 +9,6 @@ public function setFormName($formName); public function setField($field); public function setError($error); - + + public function setValue($value); } \ No newline at end of file diff --git a/src/Event/Events.php b/src/Event/Events.php new file mode 100644 index 0000000..4c81e81 --- /dev/null +++ b/src/Event/Events.php @@ -0,0 +1,8 @@ +entity = $entity; + } + + /** + * @return FormErrorLogEntityInterface + */ + public function getEntity() + { + return $this->entity; + } +} \ No newline at end of file diff --git a/src/EventListener/ErrorLogSubscriber.php b/src/EventListener/ErrorLogSubscriber.php new file mode 100644 index 0000000..64a7551 --- /dev/null +++ b/src/EventListener/ErrorLogSubscriber.php @@ -0,0 +1,119 @@ +logger = $logger; + $this->request = $request->getMasterRequest(); + } + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + FormEvents::POST_SUBMIT => 'postSubmit', + ]; + } + + /** + * @param \Symfony\Component\Form\FormEvent $event + */ + public function postSubmit(FormEvent $event) + { + $form = $event->getForm(); + + $errors = $this->getErrorMessages($form); + + if (empty($errors)) { + return; + } + + $formName = $form->getName(); + + foreach ($errors as $key => $error) { + $uri = $this->request->getUri(); + $this->logger->log($formName, $key, $error['messages'], $error['value'], $uri); + } + } + + /** + * @param \Symfony\Component\Form\Form $form + * @return array + */ + private function getErrorMessages(\Symfony\Component\Form\Form $form) + { + $errors = []; + + /* Get the errors from this FormType */ + foreach ($form->getErrors() as $key => $error) { + $data = $form->getData(); + + $serializedData = $this->serialize($data); + if (empty($serializedData)) { + $formData = $this->request->request->has($form->getName()) + ? $this->request->request->get($form->getName()) + : null; + $serializedData = 'POST DATA: '.json_encode($formData); + } + + $errors[$key] = [ + 'messages' => $error->getMessage(), + 'value' => $serializedData, + ]; + } + + if ($form->count() > 0) { + foreach ($form->all() as $child) { + if (!$child->isValid()) { + $childErrors = $this->getErrorMessages($child); + $messages = $values = []; + foreach($childErrors as $childError) { + $messages[] = $childError['messages']; + $values[] = $childError['value']; + } + + // if there's more than 1 error or value on a field then we can log them all + $messages = implode(' | ', $messages); + $values = implode(' | ', $values); + + $errors[$child->getName()] = [ + 'messages' => $messages, + 'value' => $values, + ]; + } + } + } + + return $errors; + } +} diff --git a/src/Form/Extension/FormLogTypeExtension.php b/src/Form/Extension/FormLogTypeExtension.php new file mode 100644 index 0000000..bd498f6 --- /dev/null +++ b/src/Form/Extension/FormLogTypeExtension.php @@ -0,0 +1,27 @@ +eventSubscriber = $eventSubscriber; + } + + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->addEventSubscriber($this->eventSubscriber); + } + + public function getExtendedType() + { + return 'form'; + } +} diff --git a/src/Logger/DatabaseLogger.php b/src/Logger/DatabaseLogger.php new file mode 100644 index 0000000..cbf9a13 --- /dev/null +++ b/src/Logger/DatabaseLogger.php @@ -0,0 +1,73 @@ +em = $em; + $this->entityClass = $entityClass; + $this->eventDispatcher = $eventDispatcher; + } + + /** + * @param string $formName + * @param string $key + * @param string $error + * @param string $value + * @param string $uri + * @throws InvalidArgumentException + */ + public function log($formName, $key, $error, $value = '', $uri = '') + { + if ($this->entityClass === 'Oh\FormErrorLogBundle\Entity\FormErrorLogEntityInterface') { + throw new InvalidArgumentException('You need to update your %oh_form_error_log.db.entity.class% parameter to your own class. See the README for help.'); + } + + /** @var FormErrorLogEntityInterface $entity */ + $entity = new $this->entityClass; + + $entity->setFormName($formName); + $entity->setField($key); + $entity->setError($error); + $entity->setValue($value); + // for BC + if (method_exists($entity, 'setUri')) { + $entity->setUri($uri); + } + + $this->eventDispatcher->dispatch(Events::PRE_PERSIST, new PrePersistEntityEvent($entity)); + + $this->em->persist($entity); + + $this->em->flush($entity); + } +} diff --git a/Logger/ErrorLogInterface.php b/src/Logger/ErrorLogInterface.php similarity index 100% rename from Logger/ErrorLogInterface.php rename to src/Logger/ErrorLogInterface.php diff --git a/src/Logger/Logger.php b/src/Logger/Logger.php new file mode 100644 index 0000000..921977a --- /dev/null +++ b/src/Logger/Logger.php @@ -0,0 +1,44 @@ +logger = $logger; + } + + /** + * @param string $formName + * @param string $key + * @param string $error + * @param string $value + * @param string $uri + */ + public function log($formName, $key, $error, $value = '', $uri = '') + { + $logMessage = strtr( + '%0 - Error in form "%1" in position "%2": "%3" with serialized value "%4"', + [ + '%0' => $uri, + '%1' => $formName, + '%2' => $key, + '%3' => $error, + '%4' => $value, + ] + ); + $this->logger->notice($logMessage); + } +} diff --git a/src/Logger/SerializeData.php b/src/Logger/SerializeData.php new file mode 100644 index 0000000..b80a3af --- /dev/null +++ b/src/Logger/SerializeData.php @@ -0,0 +1,94 @@ +serializeObject($data); + } elseif (is_resource($data)) { + return $this->serializeResource($data); + } elseif (is_array($data)) { + return $this->serializeArray($data); + } else { + return $this->serializeNonObject($data); + } + } + + private function serializeObject($object) + { + $data = ''; + + // JsonSerializable is for php 5.4 + if (class_exists('\JsonSerializable', false) && $object instanceof \JsonSerializable) { + $data = json_encode($object); + + // otherwise we could just see if that method exists + } elseif (method_exists($object, 'jsonSerialize')) { + $data = json_encode($object->jsonSerialize()); + + // some people create a toArray() method + } elseif (method_exists($object, 'toArray') && is_array($array = $object->toArray())) { + // JSON_PRETTY_PRINT is > PHP 5.4 + if (defined('JSON_PRETTY_PRINT')) { + $data = json_encode($array, JSON_PRETTY_PRINT); + } else { + $data = json_encode($array); + } + + // lets try to serialize + // this could be risky if the object is too large or not implemented correctly + } elseif (method_exists($object, '__sleep') || $object instanceof Serializable) { + $data = @serialize($object); + } + + return $data; + } + + /** + * @param resource $resource + * @return string + */ + private function serializeResource($resource) + { + // we cann't serialize PHP resources + return ''; + } + + /** + * @param array $array + * @return string + */ + private function serializeArray($array) + { + foreach ($array as &$value) { + if (is_object($value)) { + $value = $this->serializeObject($value); + } elseif (is_resource($value)) { + $value = $this->serializeResource($value); + } + } + + return $this->serializeNonObject($array); + } + + /** + * @param int|string|array|null $nonObject + * @return string + */ + private function serializeNonObject($nonObject) + { + $data = ''; + try { + $data = serialize($nonObject); + } catch (\Throwable $t) { + // do nothing, will catch in PHP >= 7.0 + } catch (\Exception $e) { + // do nothing, will catch in PHP <= 5.6 + } finally { + return $data; + } + } +} diff --git a/OhFormErrorLogBundle.php b/src/OhFormErrorLogBundle.php similarity index 100% rename from OhFormErrorLogBundle.php rename to src/OhFormErrorLogBundle.php diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml new file mode 100644 index 0000000..f724f69 --- /dev/null +++ b/src/Resources/config/services.yml @@ -0,0 +1,45 @@ +parameters: + oh_form_error_log.listener.class: Oh\FormErrorLogBundle\EventListener\ErrorLogSubscriber + oh_form_error_log.logger.class: Oh\FormErrorLogBundle\Logger\Logger + oh_form_error_log.db.class: Oh\FormErrorLogBundle\Logger\DatabaseLogger + +services: + oh_form_error_log.logger.db: + public: true + class: "%oh_form_error_log.db.class%" + arguments: + - "@doctrine.orm.default_entity_manager" + - "%oh_form_error_log.db.entity.class%" + - "@event_dispatcher" + oh_form_error_log.logger: + public: true + class: "%oh_form_error_log.logger.class%" + arguments: + - "@logger" + tags: + - { name: monolog.logger, channel: formerror } + oh_form_error_log.listener.db: + public: true + class: "%oh_form_error_log.listener.class%" + arguments: + - "@oh_form_error_log.logger.db" + - "@request_stack" + oh_form_error_log.listener: + public: true + class: "%oh_form_error_log.listener.class%" + arguments: + - "@oh_form_error_log.logger" + - "@request_stack" + oh_form_subscriber: + public: true + class: Oh\FormErrorLogBundle\EventListener\ErrorLogSubscriber + arguments: + - "@oh_form_error_log.logger.manager" + - "@request_stack" + oh_form_extension: + public: true + class: Oh\FormErrorLogBundle\Form\Extension\FormLogTypeExtension + arguments: + - "@oh_form_subscriber" + tags: + - { name: form.type_extension, alias: form, extended_type: Symfony\Component\Form\Extension\Core\Type\FormType }