I couldn't find a similar post, so I'll give it a try. I'm currently working on a Fullstack Symfony 6 Project (utilizing React and Symfony 6), mostly focusing on API development. However, I've hit a roadblock. I've implemented custom Pagination with Doctrine (avoiding the use of other bundles). The issue arises when attempting to Order By columns by clicking in the Frontend; it only affects the current page's data. The goal is to Order it by all entries. What would be the best practice in this scenario? Should I call another API to retrieve all entries? Could you provide an example?
Repository (Query Builder)
class CustomerRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Customer::class);
}
public function getCustomers(
string $column,
string $type,
int $page = 1,
int $entriesPerPage = 10): array
{
$query = $this->createQueryBuilder('d')
->orderBy("d.$column", $type)
->getQuery();
$query->setFirstResult($entriesPerPage * ($page - 1))
->setMaxResults($entriesPerPage);
$paginator = new Paginator($query);
$total = $paginator->count();
$lastPage = (int)ceil($total / $entriesPerPage);
return [
'customers' => $paginator,
'lastPage' => $lastPage,
'total' => $total
];
}
}
Controller (API)
#[Route(path: "/api/customers/{column}/{type}/{page}", name: "customer_get", methods: ["GET"])]
public function getCustomers(
CustomerService $customerService,
string $column,
string $type,
int $page=1
): JsonResponse
{
return $this->json($customerService->getCustomers($column, $type, $page));
}
Response (called route: api/customers/created_at/asc/3)
{
"customers": [
{
"id": 156,
"name": "Test",
"created_at": "2024/03/07",
"updated_at": null
},
{
"id": 157,
"name": "Test",
"created_at": "2024/03/07",
"updated_at": null
}
],
"lastPage": 3,
"total": 11
}
React
const [sortBy, setSortBy] = useState({field: "name", order: "ASC"});
const [sortByCreatedAt, setSortByCreatedAt] = useState(null);
const fetchCustomers = () => {
const sortField = sortByCreatedAt ? 'created_at' : sortBy.field;
const sortOrder = sortByCreatedAt ? sortByCreatedAt.order : sortBy.order;
axios.get(`/api/customers/${sortField}/${sortOrder}/${currentPage}`)
.then(response => {
setCustomers(response.data.departments);
setLastPage(response.data.lastPage)
})
.catch(error => {
console.error(error);
});
};
Question 2: Are there any possibilities to improve my "Order By" clause in the Query Builder? Can it be made more dynamic, or is it fine as it is?
Yes, and that's logical because each time you're sending the same
sortFieldandsortOrdervalues to the API, you're not updating these values inside thesortByobject when a user changes the order of sort or the column used in sorting.Instead, you will need to update these values whenever a user changes the ordering behavior, for example, if I want to sort by email column in descending order, the
sortByobject should look like that:I'm not a React developer, and I'm not sure about the best practices regarding this situation, but logically you need to call the
setSortBy()function inside the function where you're doing the client side sorting, and then you need to call thefetchCustomers()function after that, for example:And disable any client-side sorting. So I suggest letting the entire sorting process be handled by the server and not doing anything on the client.
You could allow multiple sorting criteria with different directions, for example, you could allow sorting by name column in descending order and sorting by created_at column in ascending order. This way, if multiple rows have the same value inside the name column, they will get sorted by the created_at in ascending order.
Inside your
CustomerRepositoryclass, you may need to do something like this:However, this piece of code is dangerous:
You may be vulnerable to SQL injection attacks, because you're passing the column name as a variable, and these parts of an SQL query can't use query binds. For more information please consult the SQL Injection Prevention page on OWASP cheatsheet website.