I have an abstract class where source code looks like this:
/*
* #assert (0) == NULL
*/
public static function factory($num) {
if ($num==0)
return NULL;
//do some other stuff
}
If I delete the previously generated test file and use the "Create PHPUnit tests", it creates a new unit test file that doesn't seem to have taken the assert into account at all:
/**
* #covers {className}::{origMethodName}
* #todo Implement testFactory().
*/
public function testFactory() {
// Remove the following lines when you implement this test.
$this->markTestIncomplete(
'This test has not been implemented yet.'
);
}
I must be doing something silly, but I can't figure out what. Is the failure to expand the class name and method name in the generated #covers annotation perhaps a clue?
I'm running NetBeans 7.0.1 on a Mac with PHP 5.3.6 and PHPUnit 3.6.2.
All annotations must appear in DocBlock comments which start with /** and not /*. You're missing an asterisk.
/**
* #assert (0) == NULL
*/
public static function factory($num) {
Related
I am working on a third party bundle which is in the vendor/ directory.
I have an Entity class which looks like this:
/**
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Repository\ArticleRepository")
* #ORM\Table(name="acme_demo_article")
*/
class Article
And a Repository class like this:
class ArticleRepository extends ServiceEntityRepository
{
public function __construct(RegistryInterface $registry)
{
parent::__construct($registry, Article::class);
}
}
This generates the following error:
The "Acme\DemoBundle\Repository\ArticleRepository" entity repository
implements
"Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepositoryInterface",
but its service could not be found. Make sure the service exists and
is tagged with "doctrine.repository_service".
If i remove the repositoryClass from the entity definition, I dont have the error anymore and i can use doctrine as such from my controller:
this->getDoctrine()->getRepository(Article::class)->findBy([], null, $limit, ($page - 1) * $limit);
I tried adding the repository as a service in the bundle service definition but it does not change anything:
vendor/Acme/demo-bundle/Resources/config/services.yaml
services:
Acme\DemoBundle\Repository\:
resource: '../../Repository/ArticleRepository.php'
autoconfigure: true
tags: ['doctrine.repository_service']
bin/console debug:autowire or debug:container wont show the service.
I also tried adding the extension:
namespace Acme\BlogBundle\DependencyInjection;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
class AcmeBlogExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.xml');
}
}
Did not work either. I dont have the impression that the extension is being called. I tried adding a constructor to it and dump, die in the constructor, but there are no results of the dump.
So my question is how do i define my repositories as a service from the vendor directory ?
The source code is overhere: https://github.com/khalid-s/sf4-bundle-test
After much struggling, i succedded in my task. I dont think that's it should be done like this, but if this can help someone...
I added in my DependencyInjection folder of the bundle:
class AcmeBlogExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader(
$container,
new FileLocator(__DIR__.'/../Resources/config')
);
$loader->load('services.yaml');
}
}
I created a compiler (this is the part which i struggled to figure out) to register my service
class RepositoryCompiler implements CompilerPassInterface
{
/**
* #inheritdoc
*/
public function process(ContainerBuilder $container)
{
$container->register('acme_blog.repository', ArticleRepository::class);
}
}
I added in my Bundle class:
class AcmeBlogBundle extends Bundle
{
/** #info this function normally is useless */
public function getContainerExtension()
{
// This is only useful if the naming convention is not used
return new AcmeBlogExtension();
}
/**
* #inheritDoc
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new RepositoryCompiler());
parent::build($container);
}
}
And finally the service itself:
services:
Acme\BlogBundle\Repository\:
resource: '../../Repository/*Repository.php'
autoconfigure: true
autowire: true
tags: ['doctrine.repository_service']
The autoconfigure and autowire are useless since they are not taken into consideration when i debug:container which looks like this:
php bin/console debug:container acme
Information for Service "acme_blog.article.repository"
=======================================================
---------------- -----------------------------------------------
Option Value
---------------- -----------------------------------------------
Service ID acme_blog.article.repository
Class Acme\BlogBundle\Repository\ArticleRepository
Tags doctrine.repository_service
Public yes
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired no
Autoconfigured no
---------------- -----------------------------------------------
One very important note which made me loose a lot of time:
Do clear your cache after every change to your services. Even in dev
mode they are not reloaded after every refresh
Is there any way to set the circular reference limit in the serializer component of Symfony (not JMSSerializer) with any config or something like that?
I have a REST Application with FOSRestBundle and some Entities that contain other entities which should be serialized too. But I'm running into circular reference errors.
I know how to set it like this:
$encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceHandler(function ($object) {
return $object->getName();
});
But this has to be done in more than one controller (overhead for me).
I want to set it globally in the config (.yml) e.g. like this:
framework:
serializer:
enabled: true
circular_limit: 5
Found no serializer API reference for this so I wonder is it possible or not?
For a week have I been reading Symfony source and trying some tricks to get it work (on my project and without installing a third party bundle: not for that functionality) and I finally got one. I used CompilerPass (https://symfony.com/doc/current/service_container/compiler_passes.html)... Which works in three steps:
1. Define build method in bundle
I choosed AppBundle because it is my first bundle to load in app/AppKernel.php.
src/AppBundle/AppBundle.php
<?php
namespace AppBundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
class AppBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new AppCompilerPass());
}
}
2. Write your custom CompilerPass
Symfony serializers are all under the serializer service. So I just fetched it and added to it a configurator option, in order to catch its instanciation.
src/AppBundle/AppCompilerPass.php
<?php
namespace AppBundle;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class AppCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$container
->getDefinition('serializer')
->setConfigurator([
new Reference(AppConfigurer::class), 'configureNormalizer'
]);
}
}
3. Write your configurer...
Here, you create a class following what you wrote in the custom CompilerPass (I choosed AppConfigurer)... A class with an instance method named after what you choosed in the custom compiler pass (I choosed configureNormalizer).
This method will be called when the symfony internal serializer will be created.
The symfony serializer contains normalizers and decoders and such things as private/protected properties. That is why I used PHP's \Closure::bind method to scope the symfony serializer as $this into my lambda-like function (PHP Closure).
Then a loop through the nomalizers ($this->normalizers) help customize their behaviours. Actually, not all of those nomalizers need circular reference handlers (like DateTimeNormalizer): the reason of the condition there.
src/AppBundle/AppConfigurer.php
<?php
namespace AppBundle;
class AppConfigurer
{
public function configureNormalizer($normalizer)
{
\Closure::bind(function () use (&$normalizer)
{
foreach ($this->normalizers as $normalizer)
if (method_exists($normalizer, 'setCircularReferenceHandler'))
$normalizer->setCircularReferenceHandler(function ($object)
{
return $object->getId();
});
}, $normalizer, $normalizer)();
}
}
Conclusion
As said earlier, I did it for my project since I dind't wanted FOSRestBundle nor any third party bundle as I've seen over Internet as a solution: not for that part (may be for security). My controllers now stand as...
<?php
namespace StoreBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class ProductController extends Controller
{
/**
*
* #Route("/products")
*
*/
public function indexAction()
{
$em = $this->getDoctrine()->getManager();
$data = $em->getRepository('StoreBundle:Product')->findAll();
return $this->json(['data' => $data]);
}
/**
*
* #Route("/product")
* #Method("POST")
*
*/
public function newAction()
{
throw new \Exception('Method not yet implemented');
}
/**
*
* #Route("/product/{id}")
*
*/
public function showAction($id)
{
$em = $this->getDoctrine()->getManager();
$data = $em->getRepository('StoreBundle:Product')->findById($id);
return $this->json(['data' => $data]);
}
/**
*
* #Route("/product/{id}/update")
* #Method("PUT")
*
*/
public function updateAction($id)
{
throw new \Exception('Method not yet implemented');
}
/**
*
* #Route("/product/{id}/delete")
* #Method("DELETE")
*
*/
public function deleteAction($id)
{
throw new \Exception('Method not yet implemented');
}
}
The only way I've found is to create your own object normalizer to add the circular reference handler.
A minimal working one can be:
<?php
namespace AppBundle\Serializer\Normalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
class AppObjectNormalizer extends ObjectNormalizer
{
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory = null, NameConverterInterface $nameConverter = null, PropertyAccessorInterface $propertyAccessor = null, PropertyTypeExtractorInterface $propertyTypeExtractor = null)
{
parent::__construct($classMetadataFactory, $nameConverter, $propertyAccessor, $propertyTypeExtractor);
$this->setCircularReferenceHandler(function ($object) {
return $object->getName();
});
}
}
Then declare as a service with a slithly higher priority than the default one (which is -1000):
<service
id="app.serializer.normalizer.object"
class="AppBundle\Serializer\Normalizer\AppObjectNormalizer"
public="false"
parent="serializer.normalizer.object">
<tag name="serializer.normalizer" priority="-500" />
</service>
This normalizer will be used by default everywhere in your project.
Building a JSON response for an API type thing, to retrieve a specific set of data that includes a ManyToOne relationship in the entity for my entity that extends FOSUSerBundle's User entity (called Account in my case).
The problem is, the Account entity thats included as a field in the response, is wanted, but I dont want to include all of the password and role type stuff.
I've been browing the internet for a couple hours now, and I've followed many guides on this, and I've cleared my cache every single time, and to no avail; So here's where I ended up:
// app/config/config.yml
jms_serializer:
metadata:
auto_detection: true
directories:
FOSUserBundle:
namespace_prefix: "FOS\\UserBundle"
path: "%kernel.root_dir%/Resources/serializer/FOS"
I've for below I've tried User.Model.yml and Model.User.yml and User.Entity.yml as well in a vain thought that the file name actually matters
// app/Resources/serializer/FOS/Entity.User.yml
FOS\UserBundle\Model\User:
exclusion_policy: ALL
properties:
id:
expose: true
and what I get still looks like this:
{
"status":"ok",
"api_version":"1.0",
"code":200,
"data":{
"video":{
"id":1,
"published":true,
"visibility":true,
"title":"Megaman 2",
"slug":"megaman-2",
"summary":"A rap song about Megaman",
"description":"A rap song\r\nAbout megaman",
"youtube_id":"R6L9bUouDr8",
"date_published":"2014-07-02T14:09:26-0700",
"date_created":"2014-07-02T14:09:26-0700",
"date_updated":"2014-07-02T14:09:26-0700",
"author_id":3,
"author":{
"id":3,
"username":"kharrison",
"username_canonical":"kharrison",
"email":"(sorry private)",
"email_canonical":"(sorry, private)",
"enabled":true,
"salt":"(sorry, private)",
"password":"(sorry, private)",
"last_login":"2014-07-04T15:17:34-0700",
"locked":false,
"expired":false,
"roles":[
"ROLE_SUPER_ADMIN"
],
"credentials_expired":false,
"display_name":"Kyle Harrison",
"slug":"kyle-harrison",
"bio":"Test"
}
}
}
}
The "author" field, is my Account entity thats being run through the JMSSerializer
I want to exclude ALL of that, except the user ID, Display name, and slug.
And finally this is how the API works:
// My/Bundle/Controller/BaseAPIController.php
//......... other code
/**
* #param string $status
* #param integer $code
* #return Response
*/
public function render_api($status, $code)
{
$this->apiResponse->setStatus($status);
$this->apiResponse->setCode($code);
return new Response($this->apiResponse->serialize($this->get('jms_serializer')), $this->apiResponse->getCode(), ["Content-type"=>"application/json"]);
}
//............. other code
and finally, that calls this:
// My/Bundle/Models
class APIResponse {
protected $status;
protected $apiVersion;
protected $code;
protected $data;
public function __construct($apiVersion, $status = "OK", $code = 500)
{
$this->status = $status;
$this->code = $code;
$this->apiVersion = $apiVersion;
$this->data = [];
}
// ... getters and setters
/**
* #return mixed
*/
public function serialize($serializer) {
return $serializer->serialize($this, "json");
}
}
I've for below I've tried User.Model.yml and Model.User.yml and
User.Entity.yml as well in a vain thought that the file name actually
matters.
It does matter, actually. It's a concatenation of the namespace and class name. In this case, you're trying to configure the FOS\UserBundle\Model\User class, so the file name should be Model.User.yml. (FOS\UserBundle\ should be excluded from the file name, since you configured it as namespace_prefix in your config.yml)
Also make sure that your Account class doesn't re-declare (overwrite) the properties, as the serializer config only works if you configure it for the class that actually declares the properties.
Ok So, the actual answer, couldn't have been arrived to via the information I provided. But Nic's Answer did lead me towards the solution. The description of how the the serializer looks at and deciphers the config file lead me to the real problem at hand.
This is what I failed to show:
<?php
namespace [PRIVATE]\[PRIVATE]Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\VirtualProperty;
/**
* Account
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="[PRIVATE]\[PRIVATE]Bundle\Entity\AccountRepository")
*/
class Account extends BaseUser
{
The problem lays with the Alias I provided the FOS\UserBundle\Model\User namespace. I no longer remember why I wrote that that way. However, the moment I remove the Alias and rewrote the extends to resemble this instead:
<?php
namespace [PRIVATE]\[PRIVATE]Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
use JMS\Serializer\Annotation\Groups;
use JMS\Serializer\Annotation\VirtualProperty;
/**
* Account
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="[PRIVATE]\[PRIVATE]Bundle\Entity\AccountRepository")
*/
class Account extends User
{
combined with the new correct filename from Nic's answer, the config based Exclusion policy for JMSSerializerBundle totally kicks in, and every instance of FOSUserBundle's items are now completely hidden, except for the fields I've now explicitly told it to expose.
This is exactly what I wanted :)
Thanks everyone for your help! Cheers
~k
I'm not sure it's the exact way you want it, more a way around:
way around 1: Select only the properties you want (via the entity manager) and then serialize the array obtained.
It's what I do with what I call my API (which is not a class as you but controllers)
I use Acceleo in order to generate code with a model I have made. I managed to protect my methods in order to protect them usinig "#generated NOT" in case I need to regenerate my code with Acceleo. The problem is that adding #generated NOT protect all the method content, that is to say the body, the signature and JavaDocs.
The thing is that I only need to keep the method body, or at least the method body and its signature, but I need the doc to be updated. How can I do this ?
Just for information here is an example of a potential generated class :
/*
* #generated
*/
public class ActeurRefEntrepriseServicesImpl implements ActeurRefEntrepriseServices {
#Autowired
HelloWorldService helloWorldService;
/**
* Service which say hello
*
* #param name
* user name
* #return print Hello username
*
* #generated NOT
*/
#Override
public void sayHello(final String name) {
helloWorldService.print(name);
}
}
Baptiste,
The #generated tags use the standard EMF protection rules : "#generated" means that the body of the block for which it is set will be generated, anything else means no re-generation. If you set something as "#generated" in any of your metamodels' generated code, you will see that there, too, the javadoc is preserved whatever the edits you do.
In short, you cannot tell EMF to re-generate anything other than the code itself.
If you need to have the body protected but not the javadoc, you have to shift from the "#generated" protection to Acceleo's [protected] blocks. i.e, change your template from :
[template generatedMethod(methodName : String)]
/**
* Some doc.
* #param param1
* param documentation.
* #generated
*/
[generateSignature(methodName)/] {
[generateBody()/]
}
[/template]
to something using a protected block :
[template generatedMethod(methodName : String)]
/**
* Some doc.
* #param param1
* param documentation.
*/
[protected (methodName)]
[generateSignature(methodName)/] {
[generateBody()/]
}
[/protected]
[/template]
With this paradigm, anything that is outside of the protected area will be regenerated, everything else will remain untouched by a regeneration.
See also the full documentation available from the Acceleo website.
If you absolutely need to use the "#generated" protection method for your model, you will need to tamper with the JMerger API from EMF and alter the launcher Acceleo generated for you in order to use your own merging strategy (see the getGenerationStrategy method from that launcher). Note that this is by no means an easy task.
I have placed this class file called 'My_Form_validation.php' into 'application/core' and I have also tried placing it in 'application/libraries'.
In my controller I am using
$this->form_validation->set_rules('user_postcode', 'Postcode', 'valid_postcode|trim|required|xss_clean');
This is whats in My_Form_validation.php - the actual logic is not in question here because I have a couple of options to actually validate the postcode. What I need help with is understanding why it is not loading or getting called!
My CI version is
define('CI_VERSION', '2.0.2');
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Form validation for UK Postcodes
*
* Check that its a valid postcode
* #author James Mills <james#koodoocreative.co.uk>
* #version 1.0
* #package FriendsSavingMoney
*/
class MY_Form_validation extends CI_Form_validation
{
function __construct()
{
parent::__construct();
log_message('debug', '*** Hello from MY_Form_validation ***');
}
function valid_postcode($postcode)
{
/**
*
* UK Postcode validation expression from Wikipedia
* http://en.wikipedia.org/wiki/Postcodes_in_the_United_Kingdom
*
* Note: Remember to strtoupper() your postcode before inserting into database!
*
*/
$pattern = "/^(GIR 0AA)|(((A[BL]|B[ABDHLNRSTX]?|C[ABFHMORTVW]|D[ADEGHLNTY]|E[HNX]?|F[KY]|G[LUY]?|H[ADGPRSUX]|I[GMPV]|JE|K[ATWY]|L[ADELNSU]?|M[EKL]?|N[EGNPRW]?|O[LX]|P[AEHLOR]|R[GHM]|S[AEGKLMNOPRSTY]?|T[ADFNQRSW]|UB|W[ADFNRSV]|YO|ZE)[1-9]?[0-9]|((E|N|NW|SE|SW|W)1|EC[1-4]|WC[12])[A-HJKMNPR-Y]|(SW|W)([2-9]|[1-9][0-9])|EC[1-9][0-9]) [0-9][ABD-HJLNP-UW-Z]{2})$/";
if (preg_match($pattern, strtoupper($postcode)))
{
return TRUE;
}
else
{
$this->set_message('valid_postcode', 'Please enter a valid postcode');
return FALSE;
}
}
}
Because you're extending a CodeIgniter library and not a core component, you want to place that in application/libraries (not application/core).
And of course, don't forget to load the Form_validation library within your controller code.
$this->load->library('form_validation');
Other things to check:
Filename case sensitivity (MY_Form_validation.php loads while My_Form_validation.php won't)
Class name case sensitivity (class MY_Form_validation extends CI_Form_validation)
Reference material:
Extending Core Classes
Extending Native Libraries
You have to add $rules on your __construct method and also pass this to parent constructor
eg:
function __construct($rules = array())
{
parent::__construct($rules);
}
Look at Form_validation and provide same variables.