Implementing a Custom Field on a Doctrine Entity - php

I have an Attachment Entity in Doctrine which references a file on Amazon S3. I need to be able to provide a sort of 'Calculated Field' on the Entity that works out what I call the downloadpath. The downloadpath would be a calculated URL, for example http://site.s3.amazon.com/%s/attach/%s where I need to replace the two string values with values on the entity itself (account and filename), so;
http://site.s3.amazon.com/1/attach/test1234.txt
Although we use a Service Layer, I'd like the downloadpath to be available on the Entity at all times without it having to pass through the SL.
I've considered the obvious route of adding say a constant to the Entity;
const DOWNLOAD_PATH = 'http://site.s3.amazon.com/%s/attach/%s'; and a custom getDownloadPath() but I'd like to keep specifics like this URL in my app's configuration, not the Entities class (also, see update below)
Does anyone have any ideas on how I could achieve this?
UPDATE To add to this, I am aware now that I would need to generate a temporary URL with the AmazonS3 library to allow temporary authed access to the file - I'd prefer not to make a static call to our Amazon/Attachment Service to do this as It just doesn't feel right.

Turns out the cleanest way to do this is using the postLoad event like so;
<?php
namespace My\Listener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Events;
use Doctrine\ORM\Event\LifecycleEventArgs;
use My\Entity\Attachment as AttachmentEntity;
use My\Service\Attachment as AttachmentService;
class AttachmentPath implements EventSubscriber
{
/**
* Attachment Service
* #param \My\Service\Attachment $service
*/
protected $service;
public function __construct(AttachmentService $service)
{
$this->service = $service;
}
public function getSubscribedEvents()
{
return array(Events::postLoad);
}
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
if ($entity instanceof AttachmentEntity) {
$entity->setDownloadPath($this->service->getDownloadPath($entity));
}
}
}

Related

How to use a repository not linked to an entity in Symfony 4?

I have a custom repository returning raw data that wouldn't fit in an entity.
namespace App\Repository;
class RevenuesRepository
{
/**
* #return array Raw data about revenues
*/
public function getRevenuesRecap()
{
// ...
return $result;
}
}
I want to use it in a controller, but I can't use $em->getRepository(...) because this repository is not linked to an entity. How can I do that?
If you want to use your standalone custom repository in a controller function, it should be absolutely sufficient to add it to either action method signature or constructor signature:
use App\Repository\RevenuesRepository;
class RevenuesController {
private $revenuesRepository;
// inject it in constructor
public __construct(RevenuesRepository $revenuesRepository) {
$this->revenuesRepository = $revenuesRepository;
}
// OR (!) inject it in action
public function getRevenuesRecapAction(RevenuesRepository $revenuesRepository) {
$recap = $revenuesRepository->getRevenuesRecap();
$response->setContent(json_encode([
'data' => $recap,
]));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
This way, when testing for example, it's clearly visible what the dependencies are and what you might have to mock or provide. Also, it provides your IDE with direct data for static analysis and code completion and more helpful information like method signatures.
This works due to auto-wiring. In symfony 4 and 5 repositories are by default services and thus can be auto-wired and injected quasi automatically.

Laravel DependencyInjection is this "okay" to do?

To handle some logic of my application I created a Service in App/Services/CarsService.php.
I injected this service in my controller through DependencyInjection like so:
CarsController.php
<?php
namespace App\Http\Controllers;
use App\Services\CarsService;
class CarsController extends Controller
{
/** #var CarsService $carsService */
private $carsService;
/**
* Create a new controller instance.
*
* #param CarsService $carsService
*/
public function __construct(CarsService $carsService)
{
$this->carsService = $carsService;
}
So for example when I want to query all the cars with some parameters provided by the user I do something like this in one of my controller methods:
$cars = $this->carsService->getCars($brand, $type);
This keeps my controller clean and my logic is seperated in a Service, seems pretty good and clean to me.
But my question is actually if this is bad practice to do in Laravel? I imagine that there might be a more "Laravel-y" way to handle this.
You don't want to use a service for that. Inject and use model if you're using Eloquent. Or use repository if you're using Query Builder, raw queries or API. For example:
public function __construct(Car $car)
{
$this->car = $car;
}
$this->car->getByBrandAndType($brand, $type);
If you're asking about is using IoC container a good or a bad practice, it's definitely a good tool to use in any app.

How to make a Symfony Callback validator Doctrine aware?

As said in the title, I actually need to create a validation process with Symfony.
I'm using YAML file, everything is okay.
But in some cases, I need to check the database before saying that the data is validated.
I was searching in the Callback method, but it actually only allows me to basically check the values. I searched to make dependency injection, or even passing a defined service as a Callback, but it does not help too.
So the question, in short is: is it possible to achieve it? In which way?
With what #dragoste said in comments, I searched how to made it with my own constraint.
The solution is so to use a Custom Constraint. It is a bit messy to know what file to make and what to do, so here is what I have done.
To explain you what are my files, the goal was to validate a rent, not by how it is made but just check that there is no rent at the same moment. That's why I have to use a constraint with Doctrine inside it.
Creating the Validator folder inside the root of your bundle. Then, adding a Constraints folder inside the Validator folder.
Creating a file RentDatesConstraint.php in Validaor/Constraints folder.
Here is how it looks:
<?php
namespace ApiBundle\Validator\Constraints;
use ApiBundle\Validator\RentDatesValidator;
use Symfony\Component\Validator\Constraint;
class RentDatesConstraint extends Constraint
{
public $message = 'The beginning and ending date of the rent are not available for this vehicle.'; // note that you could use parameters inside it, by naming it with % to surround it
/**
* #inheritdoc
*/
public function validatedBy()
{
return RentDatesValidator::class; // this is the name of the class that will be triggered when you need to validate this constraint
}
/**
* #inheritdoc
*/
public function getTargets()
{
return self::CLASS_CONSTRAINT; // says that this constraints is a class constraint
}
}
Now you have created your own class constraint, you have to create your own validator.
Create a file RentDatesValidator.php in Validator folder.
<?php
namespace ApiBundle\Validator;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Collections\Collection;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RentDatesValidator extends ConstraintValidator
{
/**
* #var Registry $doctrine
*/
private $doctrine;
/**
* RentDatesValidator constructor.
* #param Registry $_doctrine
*/
public function __construct(Registry $_doctrine)
{
$this
->setDoctrine($_doctrine)
;
}
/**
* #param Registry $_doctrine
* #return $this
*/
public function setDoctrine(Registry $_doctrine)
{
$this->doctrine = $_doctrine;
return $this;
}
/**
* #inheritdoc
* #param Rent $_value
*/
public function validate($_value, Constraint $_constraint)
{
//do your stuff here
if ($testFails) {
$this
->context
->buildViolation($_constraint->message) // here you can pass an array to set the parameters of the string, surrounded by %
->addViolation()
;
}
}
}
We are almost finished, we have to declare it as a service, so here we edit services.yml in Resources/config
services:
# [...]
validator.rent_dates:
class: ApiBundle\Validator\RentDatesValidator
tags:
- { name: validator.constraint_validator }
arguments: [ "#doctrine" ]
You can notice here that I passed #doctrine service, but you can actually pass any service you want, even many, as long as you are defining the RentDatesValidator class properly to accept those services in its constructor.
And now, all you have to do is to use this in your validation.
Here we edit Rent.yml in Resource/config/validation to add this only line:
ApiBundle\Entity\Rent:
constraints:
- ApiBundle\Validator\Constraints\RentDatesConstraint: ~
We are done! The validation will work when passing your object to the validator service.
You can notice that this is made with YAML, I personally prefer this way of doing things as it separate each parts (entity-definition, database schema, validation files, ...) but you can do it with annotation, XML or even pure PHP. It's up to you, so if you want to see more syntax, you can still go on the link to Symfony Documentation to know how to do this.

Laravel Own ServiceProvider Client Call Type error: Argument 1 passed to ... must be an instance of

I want to swap out my client call or better i try to make a wrapper around this package, so i dont have to write this everytime, so i made a new ServiceProvider which should call
// Create a new client,
// so i dont have to type this in every Method
$client = new ShopwareClient('url', 'user', 'api_key');
on every request i make.
// Later after the Client is called i can make a Request
return $client->getArticleQuery()->findAll();
SwapiServiceProvider
<?php
namespace Chris\Swapi;
use Illuminate\Support\ServiceProvider;
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiServiceProvider extends ServiceProvider
{
/**
* Perform post-registration booting of services.
*
* #return void
*/
public function boot()
{
}
/**
* Register any package services.
*
* #return void
*/
public function register()
{
$this->app->singleton(ShopwareClient::class, function () {
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
});
}
}
My Class
...
use LeadCommerce\Shopware\SDK\ShopwareClient as Shopware;
class Swapi
{
public function fetchAllArticles(Shopware $shopware)
{
return $shopware->getArticleQuery()->findAll();
}
}
Testing
I just call it in my routes.php for testing
use Chris\Swapi\Swapi;
Route::get('swapi', function () {
// Since this is a package i also made the Facade
return Swapi::fetchAllArticles();
});
But i get everytime the error
FatalThrowableError in Swapi.php line 18: Type error: Argument 1
passed to Chris\Swapi\Swapi::fetchAllArticles() must be an instance of
LeadCommerce\Shopware\SDK\ShopwareClient, none given, called in
/Users/chris/Desktop/code/swapi/app/Http/routes.php on line 7
So i am asking why this
return new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
);
is not called everytime i call a method e.g $shopware->getArticleQuery()->findAll();
Does anyone know why?
I think there might be some confusion here about Laravel's IoC. When you use return Swapi::fetchAllArticles();, Laravel doesn't know what you are doing because you haven't used the container to build out the Swapi class (even though you have registered one with the container) nor do you have a facade built to access it in that manner. Otherwise PHP is going to complain because your function isn't static.
I just wrote this code and verified that it works as far as Laravel putting it all together.
In my service provider, my register function was this...
public function register()
{
$this->app->singleton('swapi', function($app) {
return new SwapiRepository(
new ShopwareClient(
env('SHOPWARE_URL'),
env('SHOPWARE_USER'),
env('SHOPWARE_KEY')
)
);
});
}
Keep in mind, swapi is really just a key the container will use to find the actual class. There's no need to pass in the entire qualified class name when you can keep it simple and easy.
My SwapiRepository which is really the wrapper for the Shopware SDK.
use LeadCommerce\Shopware\SDK\ShopwareClient;
class SwapiRepository
{
protected $client;
public function __construct(ShopwareClient $client)
{
$this->client = $client;
}
public function fetchAllArticles()
{
return $this->client->getArticleQuery()->findAll();
}
}
At this point, you are basically done. Just add App\Providers\SwapiServiceProvider::class, in the providers array (which you probably have done already) in app/config.php and use your wrapper like so...
$swapi = app('swapi');
$swapi->fetchAllArticles();
Or you can have Laravel inject it into other classes as long as Laravel is building said class.
If you want to build out a facade for this to save yourself a line of code each time you want to use this or for snytactical sugar...
use Illuminate\Support\Facades\Facade;
class Swapi extends Facade
{
protected static function getFacadeAccessor() { return 'swapi'; }
}
Make sure to update your aliases array in app/config.php so that it contains 'Swapi' => App\Repositories\Swapi::class,
And finally you should be able to use it like so...
Swapi::fetchAllArticles();
Please note your namespaces are different than mine so you may need to replace mine with yours. You should also now be able to easily inject Swapi into other classes and even method injected into your controllers where needed.
Just remember if you do that though, make sure you are grabbing instances of those classes from Laravel's service container using the app() function. If you try to build them out yourself using new SomeClass, then you have the responsibility of injecting any dependencies yourself.

Zend Framework - Doctrine 2 integration : where to store the EntityManager?

I am integrating Zend Framework and Doctrine 2.
The question is, in my controllers and view, in need to access the model. I can do all this through a single instance of the EntityManager.
Where do I store this instance ?
Zend_Registry ? That's where it is now, it is accessible from everywhere, but not really practical : $em = Zend_Registry::get('EntityManager');
As a controller and view property ? That would be accessible as $this->em, I like this
Create a factory class that will return the instance ? $em = My\EntityManager\Factory::getInstance();. Encapsulation is good, but long to type...
Is the EntityManager a Singleton already ? -> (update) not it is not
I wouldn't recommend using the EntityManager directly in your Controllers and Views. Instead, use a Service layer and inject the EntityManager it that.
I have two custom action helpers, one to retrieve Repositories and one for Services. Each action hold a reference to the EntityManager and inject it accordingly before handing it back to the Controller.
Not my actual code but something like this (not tested):
My/Controller/Action/Helper/Service.php
<?php
namespace My\Controller\Action\Helper;
use Doctrine\ORM\EntityManager;
class Service extends \Zend_Controller_Action_Helper_Abstract
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function direct($serviceClass)
{
return new $serviceClass($this->em);
}
}
You can write a similar Action Helper to retrieve Repositories.
Then, register the helper in your bootstrap (where we also have access to the EntityManager):
<?php
use Zend_Controller_Action_HelperBroker as HelperBroker,
My\Controller\Action\Helper\Service;
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
public function _initActionHelpers()
{
$this->bootstrap('doctrine');
$em = $this->getResource('doctrine');
HelperBroker::addHelper(new Service($em));
}
}
Now write a simple Service.
My/Domain/Blog/Service/PostService.php
<?php
namespace My\Domain\Blog\Service;
use Doctrine\ORM\EntityManager,
My\Domain\Blog\Entity\Post;
class PostService implements \My\Domain\Service
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function createPost($data)
{
$post = new Post();
$post->setTitle($data['title']);
$post->setContent($data['content']);
$this->em->persist($post);
$this->em->flush(); // flush now so we can get Post ID
return $post;
}
}
And to bring it all together in a controller action:
<?php
class Blog_PostController extends Zend_Controller_Action
{
private $postService;
public function init()
{
$this->postService = $this->_helper->Service('My\Domain\Blog\PostService');
}
public function createPostAction()
{
// normally we'd get data from the actual request
$data = array(
"title" => "StackOverflow is great!",
"content" => "Imagine where I'd be without SO :)"
);
// and then validate it too!!
$post = $this->postService->createPost($data);
echo $post->getId(); // Blog post should be persisted
}
}
Since the EntityManager is usually created and configured during bootstrap - either as the return value of an explicit _initDoctrine() call or by using an application resource - storing it in the Bootstrap seems to make the most sense to me. Then inside a controller, it is accessible as:
$em = $this->getInvokeArg('bootstrap')->getResource('doctrine');
I see a lot of examples of accessing bootstrap via the front controller singleton:
$em = Zend_Controller_Front::getInstance()->getParam('bootstrap')->getResource('doctrine');
which has the advantage that works everywhere.
Take a look at the integration provided by the Bisna package, written by one of the Doctrine 2 contributes. It is at https://github.com/guilhermeblanco/ZendFramework1-Doctrine2
It allows you to configure Doctrine in your application.ini. It uses an application resource plugin to process the ini settings. I have written documentation for Bisna. It may be integrated into the package by the time you read this. If not, you can find it at https://github.com/kkruecke/ZendFramework1-Doctrine2, in the bisna-documentation/html subdirectory of that package. I have also put the documentation a http://www.kurttest.com/zfa/bisna.html (although this may be temporary).
I store the Entity Manager in Zend_Registry and then I've also created an action helper which I call in my controllers.
Zend_Registry i think is a goods idea.
Basically it is a bad idea to access EM from view, if your really need this feature, your can create a view helper, and use it

Categories