How to generate SQL from Request? - php

Currently I use next approach for retreiving data according to request:
/**
* #QueryParam(name="filters", nullable=true, map=true, description="Filter by fields. Must be an array ie. &filters[id]=3")
*/
public function cgetAction(ParamFetcherInterface $paramFetcher)
{
$filters = $paramFetcher->get('filters') ?: [];
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository($this->entityClassName())
->findBy($filters);
return $entities;
}
But I need something like this: specify complex conditions in GET request, for example
?filter={"where":{"or":[{"id":1},{"id":2},...,{"id":20"},{"id":21}]}}
?filter[where][date][gt]=2014-04-01T18:30:00.000Z
?filter={"where": {"keywords": {"inq": ["foo", "bar"]}}}
?filter[where][and][0][title]=My%20Post&filter[where][and][1][content]=Hello
etc
and get data from repository in according to this request.
Does exist any bundle for Symfony for this purpose? Will be glad for any advice.

Use the LexikFormFilterBundle, it's made for this use case, building form filters and then build a doctrine query from this form filter.
You'll find a complete example here.

Related

Symfony - Request data as integer to relational database

I have created a project that have three tables (hardwarePlacement , HardwareUnitType, hardwareUnit)
And created the entities / controllers for them, with get, post, put and delete.
And it works perfectly when i test the methods for hardwarePlacement and HardwareUnitType, but the last table "hardwareUnit" is a relational table to the other two. so i have Forign keys (hardwarePlacementId and HardwareUnitTypeId).
So when i from postman try to make a post request, i get the error: "that my setHardwareUnitTypeId and hardwarePlacementId must be of type integer".
In my HardwareUnit entity i have the following for the other tables:
#[ORM\ManyToOne(inversedBy: 'hardwareUnits')]
#[ORM\JoinColumn(nullable: false)]
private ?HardwareUnitType $hardwareUnitTypeId = null;
#[ORM\ManyToOne(inversedBy: 'hardwareUnits')]
#[ORM\JoinColumn(nullable: false)]
private ?HardwarePlacement $hardwarePlacementId = null;
public function getHardwareUnitTypeId(): ?HardwareUnitType
{
return $this->hardwareUnitTypeId;
}
public function setHardwareUnitTypeId(?HardwareUnitType $hardwareUnitTypeId): self
{
$this->hardwareUnitTypeId = $hardwareUnitTypeId;
return $this;
}
public function getHardwarePlacementId(): ?HardwarePlacement
{
return $this->hardwarePlacementId;
}
public function setHardwarePlacementId(?HardwarePlacement $hardwarePlacementId): self
{
$this->hardwarePlacementId = $hardwarePlacementId;
return $this;
}
And my create method in HardwareUnit controller:
#[Route('/hardwareUnit', name: 'hardwareUnit_new', methods: ['POST'])]
public function new(ManagerRegistry $doctrine, Request $request): JsonResponse
{
$entityManager = $doctrine->getManager();
$hardwareUnit = new HardwareUnit();
$hardwareUnit->setHardwareUnitTypeId($request->request->get('hardwareUnitTypeId'));
$hardwareUnit->setHardwarePlacementId($request->request->get('hardwarePlacementId'));
$hardwareUnit->setName($request->request->get('name'));
$hardwareUnit->setCreatedDate(new \DateTime());
$hardwareUnit->setEditedDate(new \DateTime());
$entityManager->persist($hardwareUnit);
$entityManager->flush();
return $this->json('Oprettet ny hardware unit id: ' . $hardwareUnit->getId());
}
I have tried retrieving request as intval:
$hardwareUnit->setHardwareUnitTypeId($request->request->get(intval('hardwareUnitTypeId')));
$hardwareUnit->setHardwarePlacementId($request->request->get(intval('hardwarePlacementId')));
But then i get the error that my post value for setHardwareUnitTypeId and setHardwarePlacementId is null
Any suggestions on how i can convert my request to int?
Here is an image of my postman, if it helps:
You're reading the error wrong. It states that the argument of setHardwareUnitTypeId should be of type HardwareUnitType, but that you're providing a string:
App\Entity\HardwareUnit::setHardwareUnitTypeId(): Argument #1 ($hardwareUnitTypeId) must be of type ?App\Entity\HardwareUnitType, string given ...
Looking at your code, the error is quite clear. In your "new" route, you're calling the functions like this:
$hardwareUnit->setHardwareUnitTypeId($request->request->get('hardwareUnitTypeId'));
$hardwareUnit->setHardwarePlacementId($request->request->get('hardwarePlacementId'));
The $request->request->get() method returns a string (or int|float|bool|null), as it's parsing request parameters. It's not "magically" returning objects of the correct type. You need to take that ID and fetch the correct entity.
So what you need to do is the following:
Get the repository for HardwareUnitType and HardwarePlacement. This can be done through dependency injection of the repository directly into your controller action "new". See the docs for more info. You could also use $entityManager that you already have, like this example from docs, to get the repository.
In your controller, use the repository to fetch the entity based on $request->request->get('hardwareUnitTypeId') and $request->request->get('hardwarePlacementId'). You should be able to use the repository's built in find method (see previous example from docs). You'd get something like this:
$entity = $repository->find((int) $request->request->get('...'));
Use the result from find as argument to your setters. Building on on the example at the previous list item: you'd get something like: $hardwareUnit->setHardwareUnitTypeId($entity)
I hope this will help you figuring out your problem. Let me know if you need more help!

Laravel moving Controller logic to a Model

I'm at a stage where I'm refactoring my code, and I've come across an interesting conundrum.
In my ArticleController I have a bog standard store method for storing an article in my articles database table.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(StoreArticle $request)
{
$article = new Article();
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($request->get('featuredArticle') == "1" ? true : false);
$IntranetOnly = ($request->get('IntranetOnly') == "1" ? true : false);
$article->title = $request->get('title');
$article->slug = str_slug($request->get('title'));
$article->author = $request->get('author');
$article->category = $request->get('category');
$article->excerpt = $request->get('excerpt');
$article->content = clean($request->get('content'));
$article->featuredImage = $request->get('featuredImage');
$article->featuredVideo = $request->get('featuredVideo');
$article->readingTime = $this->calculateReadTime($request);
$article->featuredArticle = $isFeatured;
$article->IntranetOnly = $IntranetOnly;
$article->published = $defauultPublished;
$article->save();
$article->handleTags($request);
return redirect('editable/news-and-updates')->with('success', 'Article has been added');
}
I also have a function for calculating read time:
/**
* Calculate a rough reading time for an articles by counting the words present
* These words are then divided by a given reading time and rounded to the nearest whole number
* Reading time average is roughly 267 words per minute, so this also accounts for relatively slow readers
*
* #param Request $request
* #return void
*/
public function calculateReadTime(Request $request)
{
$readingSpeed = 200;
$title = str_word_count(strip_tags($request->get('title')));
$excerpt = str_word_count(strip_tags($request->get('excerpt')));
$content = str_word_count(strip_tags($request->get('content')));
$words = ($title + $excerpt + $content);
$minutes = round($words / $readingSpeed);
return $minutes . ' minute' . ($minutes == 1 ? '' : 's');
}
My question is should these methods be moved to the Article model?
Controller should be as slim as possible. Following a resourceful approach (which you seem to be doing), the store() method in your ArticleController class should strive as much as possible to look like this:
class ArticleController extends Controller
{
public function store(CreateArticleRequest $request)
{
$article = Article::create($request->validated());
// Redirect with success message
}
}
Here, your request data is validated in a form request class before it even reaches the controller method; and then an Article model instance is created from that validated data.
A couple of other notes…
Statements like ($data['featuredArticle'] == "1" ? true : false) are overly verbose. You’re doing a condition check which will evaluate to true or false; you don’t need to manually return each value in a ternary operator. So this could be slimmed down to $data['featuedArticle'] == '1'. Furthermore, if you pass a value of 0 by default, then you could just get rid of the check entirely. If in your Blade template you put a hidden input before your checkbox:
<input type="hidden" name="featuredArticle" value="0" />
<input type="checkbox" name="featuredArticle" value="1" />
Then 1 will be send if the checkbox is checked (as it overrides the hidden input’s value, or 0 sent if the checkbox isn’t checked).
Also, try to stick to Laravel conventions to make your life easier. If you use snake_case for your input names, then it just makes life easier matching them up to model attribute and table column names. So use featured_article, have an attribute in your model with the same name, which maps to a database column with the same name again. This allows you to do shorthand calls like create() (as per my controller example) and update().
Finally, methods like calculating reading time definitely belong on your model. Models represent something in your application. It therefore follows that you can do things with your models. Calculating the time to read an Article model instance therefore lends itself to having a calculateReadingTime() method on the Article model.
A bit long-winded, but hopefully there should be some helpful pointers for you in the above. I’ve been working on Laravel projects for around five years now and have found that this approach and conventions is what works best.
Your controller's store article is fine, because it fills your article instance based on request data. It could use some refactoring and you could encapsulate more logic into your Article (for example, assign slug field inside your Article model whenever title is changed and so on).
But the line $article->handleTags($request); is a suspect, because your model should never operate with requests - it will quickly polute your model code with very specialized dependencies that you don't want (what happens when you receive your tags from cache and don't have a request instance? What happens if other type of request contains tags differently? and so on). Your model shouldn't have knowledge about requests or other parts of your app. Your controller is connecting the dots between them, so make sure your handleTags takes some basic abstract types/structures as a parameter (for example, an array) and make sure your controller takes and transforms data from request accordingly before feeding it to your article.
As for your calculateReadTime dilemma, it should definitely be inside your model. Think about it this way - do you have everything you need to calculate read time of your article inside your Article model? The answer is yes, it's a property of an article object, doesn't matter if you store it in DB or calculate it off other properties. Make getReadTime method. You don't want a controller to compute something about your model because it will tie that logic to a specific place in your app which is bad (what happens when you need to calculate read time of an article in other controller? Other model? and so on).
Make sure you read about has and is concepts regarding object-oriented design, it will help you immensely.
I think you should move those assignments to a Service Class. You could also go ahead and create a repository class. This would thus become your code structure:
Controller -> Service -> Repository -> Model.
Doing this $article = new Article(); is bad. You will have a had time when writing a test for your controller store method.
I would suggest you do this:
Create a Service class, say ArticleService.php. Define a store method in it.
ArticleService.php
use Article;
class ArticleService {
protected $article;
public function __construct(Article $article){
$this->article = $article;
}
public function store(array $data){
$defauultPublished = "draft";
$IntranetOnly = false;
$isFeatured = false;
$isFeatured = ($data['featuredArticle'] == "1" ? true : false);
$IntranetOnly = ($data['IntranetOnly'] == "1" ? true : false);
$this->article->title = $data['title'];
$this->article->slug = str_slug($data['title']);
$this->article->author = $data['author'];
$this->article->category = $data['category'];
$this->article->excerpt = $data['excerpt'];
$this->article->content = clean($data['content']);
$this->article->featuredImage = $data['featuredImage'];
$this->article->featuredVideo = $data['featuredVideo'];
$this->article->readingTime = $data['reading_time'];
$this->article->featuredArticle = $isFeatured;
//Capital letter I? You should be consistent with your naming convention
$this->article->IntranetOnly = $IntranetOnly;
$this->article->published = $defauultPublished;
if($this->article->save()){
$this->article->handleTags($request);
return true;
}
return false;
}
}
And your Controller now becomes:
class ArticleController{
protected $articleService;
public function __construct(ArticleService $articleService){
$this->articleService = $articleService;
}
public function store(Request $request){
//Some Validation Logic
$readingTime = $this->calculateReadTime($request)
$data = array_merge(['reading_time' => $readTime], $request->all());
return $this->articleService->store($request->all());
}
}
I also see that you are not validating the incoming Request. You should always do that because you can/should never trust your users to always provide/input the right data. It is your duty to force them to do that. e.g I as your user might decide to enter my name in your email field. If you don't validate that data, you will end up with wrong data.
There is also the issue of individually assigning your request parameter to their corresponding Model attribute. I decided to leave it that way so as not to overload you with information.
In summary, just take a look at the following resources for more insight.
https://laravel.com/docs/5.1/quickstart-intermediate
https://laravel.com/docs/5.6/validation
In short, read up the whole Laravel documentation! Goodluck!

Laravel How to display $hidden attribute on model on paginate

I'm using Laravel 5.5.
I read about this and know this function and it works makeVisible
$hidden = ['password', 'remember_token', 'email'];
I can display email using
$profile = auth()->user()->find($request->user()->id);
$profile->makeVisible(['email']);
On the frontend email is displayed. But it not works on many results like
// Get all users
$users = User::with('role', 'level')->makeVisible(['email'])->paginate(10); // Doesn't work
Also try this method from Laracasts toJson it works but I can't do it using paginate. Can you provide other methods or how to solve this? My aim is to display email column that is hidden. Thanks.
Another, possible easier solution depending on your requirements, is to call makeVisible on the collection:
// Get all users
$users = User::with('role', 'level')->paginate(10)->makeVisible(['email']);
You can also use this with find or get:
$profile = auth()->user()->find($request->user()->id)->makeVisible(['email']);
I solve this using this method.
Users.php on model
public function toArray()
{
// Only hide email if `guest` or not an `admin`
if (auth()->check() && auth()->user()->isAdmin()) {
$this->setAttributeVisibility();
}
return parent::toArray();
}
public function setAttributeVisibility()
{
$this->makeVisible(array_merge($this->fillable, $this->appends, ['enter_relationship_or_other_needed_data']));
}
and on controller just a simple
return User::with('role', 'level')->paginate(10);
I've read where pagination comes from toArray before creating pagination. Thanks for all your help. Also helps
You can use this:
$paginator = User::with('role', 'level')->paginate($pageSize);
$data = $pagination->getCollection();
$data->each(function ($item) {
$item->setHidden([])->setVisible(['email']);
});
$paginator->setCollection($data);
return $paginator;
You can try to use this approach. Using API Resources.
API Resources lets you format the data the way you want. You can create multiple Resource object to format in different ways your collections.
Set visible your parameter (in this case email) and when you need to return that item you can use a different Resource object that returns that elemement.
So when no need for email:
$users = User::with('role', 'level')->paginate(10);
return UserWithoutEmail::collection($users);
when email is needed:
$users = User::with('role', 'level')->paginate(10);
return UserWithEmail::collection($users);

How to get an array of fe_groups in TYPO3?

I'm pretty new to TYPO3 and many things are confusing at the moment, especially how the data modeling and data fetching actually works if you're relying on ExtBase.
Thing I want to achive is to get an array of records from the fe_groups table and pass it into my Fluid view and render those items in f:form.select input field.
So far, I've tried nothing since I have no idea from where and how to start it.
Other thing I've did successfully is to pass a hard coded array of object items into my view, and rendered them successfully, like this:
<f:form.select
class="form-control"
property="taskTypes"
options="{taskTypes}"
optionValueField="name"
optionLabelField="value"
id="taskTypes" />
This is the method in my Controller which fills the taskTypes array:
private function getTaskTypes() {
$task_type_names = [
' - Task Types - ',
'New client',
'Maintenance',
];
$task_types = [];
foreach($task_type_names as $i => $task_type_name) {
$task_type = new \stdClass();
$task_type->key = $i;
$task_type->value = $task_type_name;
$task_types[] = $task_type;
}
return $task_types;
}
And then a simple view assignment in controller's action:
$this->view->assign('taskTypes', $this->getTaskTypes());
And this works like a charm!
But I'm clueless how to do something similar with dynamic content fetched from the database tables.
So, basically, I just need a way to pass items from fe_groups table to my view and render them.
You'll have to inject the Repository for FrontenduserGroups from Extbase
/**
* #var \TYPO3\CMS\Extbase\Domain\Repository\FrontendUserGroupRepository
* #inject
*/
protected $feUserGroupRepository;
in your method you can then use this Repository to get the data from the database
$feUserGroup = $this->feUserGroupRepository->findAll();
$userByUid = $this->feUserGroupRepository->findByUid(12);
The repository also provides more ->findBy* methods.
Here is a cheatsheet that might help you http://lbrmedia.net/codebase/Eintrag/extbase-query-methods/
Note:
the #inject in the doc comment is actually parsed by Extbase and loads the class that is refered in #var
the storagePid needs to be set to the UID of the folder that contains the usergroups in the backend

How can I take a big amount of GET parameters and filter a query depending on them cleanly?

I have this controller for a RESTful API I am building in Laravel Lumen which takes a relatively big amount of parameters and parses them into where queries, and data is fetched depending on if they were provided. For example,
GET /nodes?region=California
GET /nodes?ip=127.0.0.1
I am currently taking them in the constructor, building an array of the parameters (since I couldn't figure out how to get the raw get array in Lumen and it would be inconvenient because I already have other parameters there), and filtering out the null values (I am setting values to null if they are not in the query).
Now, when it comes to filtering the values each in the array, I am doing it by a foreach array. This is the cleanest way I could figure out to do it, without too much code (I don't want to make my controllers too fat.).
Is there any other way to do this cleanly, maybe with separation of functions/classes?
Here is my constructor code:
/**
* Get some values before using functions.
*
* #param Request $request Instance of request.
*/
public function __construct(Request $request)
{
$this->offset = (int) $request->input('offset', 0);
// TODO: I'm not sure how to implement this, code in question
$this->filters = [
'region' => $request->input('region', null),
'name' => $request->input('name', null),
'ip' => $request->input('ip', null)
];
$this->filters = array_filter($this->filters, function ($v) {
return !is_null($v);
});
// Set a sane SQL limit.
$this->limit = 5;
$this->request = $request;
}
And the controller code:
/**
* List all nodes.
*
* #return [string] [JSON containing list of nodes, if sorted.]
*/
public function all()
{
try {
// use filters provided
$data = Nodes::limit($this->limit)->offset($this->offset);
foreach ($this->filters as $filter => $value) {
$data->where($filter, $value);
}
$data = $data->get();
$response = $this->respond($data);
} catch (\Exception $e) {
$response = $this->respondServerError('Could not retrieve data from database.');
}
return $response;
}
So any time I have to do filtering of a resource-list in an API, here's how I do it.
First off though, before I begin, a quick tip concerning getting the Request object when you're in your controller method: If you add Request $request as a parameter for your all() function, you will have access to the $request variable there, same as your constructor. So the complete signature would be public function all(Request $request). Controller methods have the same magic dependency injection that other class constructors get in Laravel/Lumen. Alternatively, in your function you can always ask the app() function to give you an object of a specific class. Because the Request object is bound in the Container to just 'request', you can ask for the full class name, or just 'request': $request = app('request');
So once I have my request object, inside my controller method I like to go through each filter either as a group, or one-by-one, depending on how complex each filter is. Sometimes filters are complex, like a list of comma-separated IDs that need to be exploded into an array. If it's just simple string filters though, I tend to throw the list into an array and run through that.
Here's an example function to illustrate some ideas:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
/**
* Continue with the rest of response formatting below here
*/
}
You'll notice I used the paginate function to limit my results. When building an API endpoint that lists resources, you're going to want to put in your headers (my preference) or the response body information on how to get the first, previous, next, and last page of results. The Pagination feature in Laravel makes that easy, as it can construct most of the links using the links() method.
Unfortunately, you need to tell it what filter parameters were passed in the request so it can make sure it adds those to the links it generates. Otherwise you'll get links back without your filters, which doesn't do the client very much good for paging.
So here's a more complete example of recording filter parameters so they can be appended onto pagination links:
public function getIndex(Request $request)
{
//Create a User object to append WHERE clauses onto
$user = app('App\Models\User');
//List of filters we found to append to links later
$appends = [];
//Run through our simple text fields
foreach(['first_name', 'last_name', 'region', 'ip'] as $field) {
if ($request->has($field)) {
$appends[$field] = $request->input($field);
$user->where($field, $request->input($field));
}
}
//This field uses a LIKE match, handle it separately
if ($request->has('email')) {
$appends['email'] = $request->input('email');
$user->where('email', LIKE, '%' . $request->input('email') . '%');
}
//This field is a list of IDs
if ($request->has('id')) {
$appends['id'] = $request->input('id');
$ids = explode(',', $request->input('id'));
$user->whereIn('id', $ids);
}
//Use pagination
$users = $user->paginate(25);
//Make sure we append our filter parameters onto the pagination object
$users->appends($appends);
//Now calling $users->links() will return the correct links with the right filter info
/**
* Continue with the rest of response formatting below here
*/
}
Pagination documentation can be found here: https://laravel.com/docs/5.2/pagination
For an example of how pagination linking can be awesomely done, check out Github's API documentation: https://developer.github.com/v3/#pagination
In the end it's not too far off from what you were doing, conceptually. The advantage here is that you move the code into the method that needs it, instead of having it run in your constructor every single time the controller is initialized, even if a different method will be called.
Hope that helps!

Categories