In PHP, should a child class redeclare constructor dependencies if the parent constructor already defines them, or can it omit them entirely?

So I’m kinda stuck wrapping my head around this whole parent-child dependency thing. My assumption was, once you declare all the dependencies in the parent (BaseController), the child (like IndexController) wouldn’t need to declare them again in its own constructor; it could just rely on parent::__construct(). Tried removing the four dependencies from IndexController, but got hit with an error. Basically, you still gotta re-declare those four deps in the child’s constructor so they can be passed up to parent::__construct(). At least that’s what the AI told me. Lastly, are those use statements really necessary to be in every file?

BaseController.php

   <?php

use AppModelsSiteSettings;
use AppServices{
    AuthService,
    LoggerService,
    MessageService
};

class BaseController
{
    public function __construct(
        protected SiteSettings $siteSettings,
        protected AuthService $authService,
        protected LoggerService $loggerService,
        protected MessageService $messageService
    ) {}
} 

IndexController.php

<?php

use AppModelsSiteSettings;
use AppServices{
    AuthService,
    LoggerService,
    MessageService
};

class IndexController extends BaseController
{
    public function __construct(
        SiteSettings $site_settings,
        AuthService $auth_service,
        LoggerService $logger_service,
        MessageService $message_service,
        private ServersRepository $serversRepository,
        private ServerFilter $serverFilter,
        private CategoriesRepository $categoriesRepository,
        private LanguagesRepository $languagesRepository
    ) {
        parent::__construct(
            $site_settings,
            $auth_service,
            $logger_service,
            $message_service
        );
    }
$this->messageService->get('MSG_LOGIN_SUCCESS');
} 

Container.php

<?php

use AppModelsSiteSettings;
use AppServices{
AuthService,
LoggerService,
MessageService
};

class Container
{
public function __construct()
{

        $this->factories[IndexController::class] = function (Container $c) {
            return new IndexController(
                $c->get(SiteSettings::class),
                $c->get(AuthService::class),
                $c->get(LoggerService::class),
                $c->get(MessageService::class),
                $c->get(ServersRepository::class),
                $c->get(ServerFilter::class),
                $c->get(CategoriesRepository::class),
                $c->get(LanguagesRepository::class)
            );
        };
} 

playwright and devServer

using javascript when user clicks getcardetails im calling getcars rest api to load car details . if the api response is 5xx then user clicks Retrigger button which again calls getcars api to load car details . I wrote playwright test to click getcardetails button. which is received by devServer.js. devServer.js has variable called count=1. if count is 1 and api request is getcars then set count to 2 and send 5xx response. if is count 2 and api request is getcars then send 200 response. is there anyother solution for this problem.

   await page.getByRole('button', { name: 'getcardetails' }).click();

    page.locator('#find-panel').getByRole('button', { name: 'Retrigger' }).click();

I tried by setting session-id in the test. based on the session-id devServer can respond with 5xx or 2xx response. The issue with this approach is even though test is sending session-id, its not received by devServer
playwright Test:

await page.setExtraHTTPHeaders({'session-id': 'session-1'});
await page.getByRole('button', { name: 'getcardetails' }).click();
await page.setExtraHTTPHeaders({'session-id': 'session-2'});
page.locator('#find-panel').getByRole('button', { name: 'Retrigger' }).click();

devServer:

const sessionId = req.headers['session-id'];

console.error('Error handling request1:', sessionId);

jQuery Events are not binding on buttons in a responsive datatable

I have a table

column 1 column 2 colum 3 [buttons]
data data data [Button][Button][Button][Button]

I then convert the HTML Table to a responsive datatable, with the buttons bound using a jQuery selector using the .on('click',fuction () {} ); construct. Now in tables that have not been collapsed due to the length of the table row the events are bound, but where datatables have been wrapped the events are not being bound.

It does not matter if I bind the events before or after the creation of the datatable, yet if I specifically bind to a button it works.

This is how I am binding the buttons, all the other buttons that match the selector are bond:


$('button[data-eh-location]').on('click', function () { alert('Wibble'); }; 

This is the thing I have noticed with the datatabe in that you have to bind things and create tooltips BEFORE your convert the HTLML table to a data table. But in this case it does not seem to be working

How a button is defined in the HTML:

<button type="button" data-eh-location="/@item.UniqueRefenceForLinks/Edit" class="btn btn-edit" title="Edit job application" data-bs-toggle="tooltip"><i class="fa-solid fa-pen-line fa-fw"></i></button>

If the table does not wrap the button is bound correctly

Which Stack Overflow features help improve collaboration among developers?

Stack Overflow improves collaboration among developers through features like Q&A threads, voting systems, and accepted answers that highlight best practices. Tags and filtering make discussions more accessible, while Stack Overflow for Teams fosters private knowledge sharing. These tools encourage collective problem-solving, accelerate learning, and create a reliable ecosystem where developers collaborate effectively and grow together.

How to disable Android keyboard word prediction on websites?

I’m making a word game site and I noticed on Android keyboards that word predictions/suggestions appear on top of the keyboard despite me setting autoCorrect and other related input props to “off” (which seems to work for ios).

<input 
  type="text" 
  autoComplete="off" 
  autoCapitalize="off" 
  autoCorrect="off" 
  spellCheck={false} 
/>

I’d prefer not creating a custom javascript keyboard (like wordle) so I’m feeling a little stuck. I’ve tried playing around with using a hidden password input that’ll trigger the keyboard to appear while rendering text in a visible component styled to look like the input, but it’s quite finicky and I’m worried this’ll produce unexpected side effects. Has anyone dealt with this problem before?

supabase signInWithOAuth no creating a new user

I have a simple login page in my Nuxt3 app that uses supabase to login using Google or Github
Here is my component

<template>
  <div
    class="flex flex-col items-center justify-center h-screen bg-gray-500 text-white"
  >
    <div>
      <h1 class="text-5xl text-white uppercase font-bold tracking-wide">
        login with google
      </h1>
      <button
        class="mt-10 uppercase font-bold bg-amber-900 px-10 py-5 w-full cursor-pointer"
        @click="login"
      >
        google
      </button>
    </div>
  </div>
</template>

<script setup>
const sb = useSupabaseClient();

const login = async () => {
  await sb.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: 'http://localhost:3000/',
    },
  });
};
</script>

I also have a middleware name auth.global.ts that is used to protect some pages but in the first place, Im just logging the user in it but the user is returned Null. Meaning signInWithOAuth is returning a null user.

export default defineNuxtRouteMiddleware(async (to, from) => {
  const client = useSupabaseClient();
  const {
    data: { user },
  } = await client.auth.getUser();
  console.log(user);
});

When I check on supabase admin panel, I see the user is created. The problem is that the user object is returned null so I’m stuck at the login page.

I also looked in to the page cookies and I see a key “sb-mtppetqpmayisqbqpgsh-auth-token-code-verifier” is created and has a value

I also have a middleware name auth.global.ts that is used to protect some pages but in the first place, Im just logging the user in it but the user is returned Null. Meaning signInWithOAuth is returning a null user.

export default defineNuxtRouteMiddleware(async (to, from) => {
  const client = useSupabaseClient();
  const {
    data: { user },
  } = await client.auth.getUser();
  console.log(user);
});

When I check on supabase admin panel, I see the user is created. The problem is that the user object is returned null so I’m stuck at the login page.

I also looked in to the page cookies and I see a key “sb-mtppetqpmayisqbqpgsh-auth-token-code-verifier” is created and has a value

Next.js usePathname active state disappears after hydration in production standalone builds

I have a hydration timing issue in Next.js 15.5.4 where navigation active states work perfectly in development, but disappear during production hydration, despite usePathname() returning correct values throughout the process.

Specific Problem Statement

Navigation buttons briefly show correct active styling immediately after page load, then revert to default styling within 200-300ms. This behavior occurs consistently in production builds but never in development mode.

Reproduction Steps

  1. Build application: npm run build

  2. Start production server: node .next/standalone/server.js

  3. Navigate to http://localhost:3000/birre

  4. Observe navigation component behavior during page load

Expected vs Actual Behavior

  • Expected: Button for “/birre” route shows active styling (dark background, white text) and persists

  • Actual: Button briefly shows active styling, then reverts to default styling (white background, dark text)

  • Development Mode: Active styling persists correctly throughout the page lifecycle

Console Output During Issue

Initial page load to /birre shows correct pathname detection:

[Navigation] usePathname result: "/birre"
[Navigation] Found matching category: {id: "birre", label: "Birre", href: "/birre"}
[Navigation] Applying active class to category: Birre
[Hydration] React hydration warning: Text content did not match. Server: "" Client: "active"

Browser DevTools Elements tab shows this DOM class change sequence:

  1. Initial render: <a class="categoryButton"> (no active class)

  2. ~200ms later: <a class="categoryButton active"> (active class appears)

  3. ~100ms later: <a class="categoryButton"> (active class disappears)

Component Implementation

"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";
import styles from "@/styles/HorizontalHeader.module.css";

const PRODUCT_CATEGORIES = [
  { id: "birre", label: "Birre", href: "/birre" },
  { id: "vini", label: "Vini", href: "/vini" },
  { id: "condimenti", label: "Condimenti", href: "/condimenti" },
  // ... 15 more categories
];

export default function HorizontalHeader() {
  const pathname = usePathname();

  return (
    <header className={styles.horizontalHeader}>
      <nav className={styles.nav}>
        <div className={styles.navContainer}>
          {PRODUCT_CATEGORIES.map((category) => (
            <Link
              key={category.id}
              href={category.href}
              className={`${styles.categoryButton} ${
                pathname === category.href ? styles.active : ""
              }`}
            >
              <span className={styles.categoryText}>{category.label}</span>
            </Link>
          ))}
        </div>
      </nav>
    </header>
  );
}

CSS Module Styles

.categoryButton {
  display: block;
  text-align: center;
  background: white;
  border: 1.5px solid #b6c0ba;
  padding: 0.5rem 1rem;
  transition: all 0.3s ease;
  cursor: pointer;
  white-space: nowrap;
}

.categoryText {
  font-size: 0.85rem;
  font-weight: 500;
  color: #b6c0ba;
  text-transform: uppercase;
}

.categoryButton.active {
  background: #b6c0ba;
}

.categoryButton.active .categoryText {
  color: white;
}

Environment Details

  • Next.js: 15.5.4 with output: "standalone"

  • React: 19.1.0

  • Node.js: 22 LTS

  • Typescript ^5

  • Build command: npm run build --turbopack

  • Production server: node .next/standalone/server.js

  • Deployment: Ubuntu VPS with PM2 process manager + Nginx reverse proxy

Detailed Investigation

Environment Comparison Testing

  • Development (npm run dev): Active states persist correctly throughout navigation

  • Production local (node .next/standalone/server.js): Active states disappear after ~300ms

  • Production deployed (Ubuntu VPS): Identical behavior to local production

DOM Investigation

Using Chrome DevTools Elements panel, I observed this sequence on page load to /birre:

  1. Initial HTML (server-rendered): All navigation buttons have only categoryButton class

  2. First client paint: Browser displays server HTML with no active styling

  3. Hydration begins: JavaScript executes, usePathname() returns “/birre”

  4. Active class applied: Target button gains active class, styling appears correctly

  5. Hydration completion: React removes active class, reverting to server state

Console Debugging Results

Added logging to component to track hydration behavior:

useEffect(() => {
  console.log('Component mounted, pathname:', pathname);
  console.log('Active category should be:', 
    PRODUCT_CATEGORIES.find(cat => cat.href === pathname));
}, [pathname]);

Output confirms component logic works correctly:

Component mounted, pathname: /birre
Active category should be: {id: "birre", label: "Birre", href: "/birre"}

Network Tab Analysis

  • No failed resource loads or JavaScript errors

  • All Next.js chunks load successfully

  • CSS modules load correctly with expected class names

React DevTools Profiler

Shows component re-renders during hydration phase, suggesting React is reconciling server and client states and choosing server state.

Configuration Details

next.config.ts:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  output: "standalone",
};

export default nextConfig;

package.json scripts:

{
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build --turbopack",
    "start": "next start"
  }
}

Related Research

I have investigated several potential causes:

  • SSR/Hydration Mismatch: Server renders neutral state, client applies active state, React reconciliation reverts to server state

  • usePathname Timing: Hook may not be available during initial server render, creating server/client content mismatch

  • CSS Module Loading: Verified that CSS classes load correctly and active styles work when manually applied in DevTools

  • Standalone Build Differences: Issue occurs with standalone builds but not standard development server

Attempted Solutions

  • NoSSR wrapper: Prevented server rendering but created layout shift

  • useState hydration flag: Caused development environment chunk loading errors with Turbopack

  • CSS-only solutions: Verified CSS module class names are consistent between server and client renders

  • Manual DOM manipulation: Works but defeats purpose of React declarative approach

Question

How can I prevent React hydration from overriding client-side active states when using usePathname() in production builds? Is there a recommended pattern for handling route-based styling that survives the hydration process in Next.js standalone deployments?

The core issue appears to be that the server cannot know which route is active during SSR, creating a mismatch that React resolves by favoring the server state. I need a solution that works reliably with standalone builds for VPS deployment.

Cross-origin Javascript for live results doesn’t work

I’m facing a problem using a script for live sports results on my website (wordpress).

https://www.goldiretta.live

I got the following error in my browser console:

GET https://azscore.co.it/football/widget/it/all/games?type=all&LeaguesCategoryName=%2325a519ff&fwCategory=bold&fwLeagues=bold&fwTeam=400&fwStatus=bold&MatchStatus=%23131315ff net::ERR_BLOCKED_BY_RESPONSE.NotSameOrigin 403 (Forbidden)

Until 10 days ago, everything was fine. Suddenly, I encountered this error. The error occur only on my desktop laptop (mac os) because if I try to solve the website on mobile phone (iphone) it works fine. This is strange. Probably something doesn’t work anymore on my Mac?

I tested it with different browser with same results.

Any suggestions would be appreciated.

Thanks.

Basic userscript: /little customization (for hotkeying buttons on a website)

I have wished to enable proceeding to next chapter or visiting the previous, by pressing one’s right or left keyboard buttons, which are the actions offered by the two arrows along inner flanks of webpages such as seen here:
https://www.biblegateway.com/passage/?search=1%20Timothy%201&version=NIV
.

I am using Tampermonkey

I assume I would want addEventListener('onkeydown', […]) or something?

When I inspect the element; it is given a title: <a class="next-chapter" […]

I have got the @match https://www.biblegateway.com/* part down.

Dashboard into Pdf in react [closed]

I am confused , which libraries in react, Javascript that make our scrollable dashboard into pdf.

give me suggestion for use of libraries, i used jspdf/html2pdf but that take screenshot , i want to print our dashboard as it is into pdf.

run ajax in ajax response

I have javascript function that run ajax. in ajax response i create a table. I want run another ajax to show some result in one of table cell. but result of second ajax not show in table. my code like this:

function sememb(obj, id) {
  var report = 'testmem';
  var report1 = "postss";
  let table = '';
  $.ajax({
    url: 'members.php',
    type: 'POST',
    data: 'report=' + report + '&id=' + id,
    success: function(result) {
      var tdata = result;
      table += '<table id="r_hold">';
      table += '<thead><tr><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr></thead><tbody>';
      let raj = 0;
      for (let i = 0; i < tdata.length; i++) {
        raj++
        let obj = tdata[i];
        table += '<tr><td>' + raj + '</td><td>' + obj.code + '</td><td>' + obj.id + '</td><td>' + obj.fname + '</td><td>' + obj.lname + '</td><td>' + obj.mobile + '</td><td>' + obj.statuss + '</td><td>' + obj.level + '</td><td>';
        $.ajax({
          url: 'members.php',
          type: 'POST',
          data: 'report=' + report1 + '&id=' + obj.id,
          success: function(res) {
            var tda = res;
            for (let j = 0; j < tda.length; j++) {
              let obd = tda[j];
              table += obd.post;
            }
          }
        })
        table += '</td>';
      }
      table += '</tbody></table>';
      const tableContainer = document.getElementById('d1');
      tableContainer.innerHTML = table;
    }
  })
}

any things add in table variable in second ajax not showing in table.

How to redact a specific word?

So I’m creating a script that target specific word that given in the console it is working but after the redact is done it leave a black box object it’s not a annotation nor it is a image.

var redactWords = app.trustedFunction(function(doc, wordsArray) {
app.beginPriv();
var totalRedacted = 0;

for (var p = 0; p < doc.numPages; p++) {
    var numWords = doc.getPageNumWords(p);

    for (var w = 0; w < numWords; w++) {
        var word = this.getPageNthWord(p, w, false).trim(); // Trim spaces/newlines

        for (var i = 0; i < wordsArray.length; i++) {
            if (word.toUpperCase() === wordsArray[i].toUpperCase()) {
                var quads = doc.getPageNthWordQuads(p, w);
                if (quads) {
                    for (var q = 0; q < quads.length; q++) {
                        doc.addAnnot({
                            page: p,
                            type: "Redact",
                            quads: [quads[q]],
                            fillColor: color.black
                        });
                        totalRedacted++;
                    }
                } else {
                    console.println("No quads found for word: " + word + " on page " + (p+1));
                }
            }
        }
    }
}

if (totalRedacted > 0) {
    doc.applyRedactions({ nPage: -1 });
    app.alert("Redaction complete! Total words redacted: " + totalRedacted);
} else {
    app.alert("No words found for redaction.");
}

app.endPriv();
});

How to generate SEO-friendly markup for a Discuz! forum to improve ranking? [closed]

I’m currently managing a Discuz-based forum website(text) and I’m trying to improve its visibility on search engines. I’ve already implemented some basic SEO strategies such as meta tags, sitemap generation, and keyword optimization, but the ranking is still not improving as expected.

Could anyone share some advanced techniques or best practices specifically for Discuz forums? Are there any plugins, configurations, or server-level optimizations that significantly boost SEO performance?

Symfony API Platform subresource POST/PUT operations are not working as expected

I am unable to get subresource write operations (POST, PUT) working in API Platform 4.1.

I will walk you through the issues step-by-step. However, to summarize upfront, below are the expected behaviors as well as the actual results:

POST

  • Expected: new subresources can be created ad infinitum.
  • Actual: only one subresource can be created; additional attempts simply overwrite the original subresource.

PUT

  • Expected: subresources can be created at a specific IRI ad infinitum.
  • Actual: subresources can only be created at a specific IRI once.

Minimal Example

Assume we have two entities: User and UserEmail in a one-to-many relationship, like so:

User.php

#[Groups(['user:read'])]
#[ORMOneToMany(targetEntity: UserEmail::class, mappedBy: 'user', cascade: ['persist', 'remove'])]
private Collection $emails;

UserEmail.php

#[ApiResource(
    uriTemplate: '/users/{userUuid}/emails/{uuid}',
    uriVariables: [
        'userUuid' => new Link(fromClass: User::class, fromProperty: 'emails'),
        'uuid' => new Link(fromClass: UserEmail::class),
    ],
    operations: [
        new Get(
            normalizationContext: ['groups' => ['userEmail:read']],
            security: "is_granted('ROLE_ADMIN') or (is_granted('ROLE_USER') and user.getUuid() == request.attributes.get('userUuid'))",
        ),
        new Put(
            normalizationContext: ['groups' => ['userEmail:read']],
            security: "is_granted('ROLE_ADMIN') or (is_granted('ROLE_USER') and user.getUuid() == request.attributes.get('userUuid'))",
            processor: UserEmailProcessor::class,
            allowCreate: true,
        ),
        new Delete(
            security: "is_granted('ROLE_ADMIN') or (is_granted('ROLE_USER') and user.getUuid() == request.attributes.get('userUuid'))",
        ),
    ]
)]
#[ApiResource(
    uriTemplate: '/users/{userUuid}/emails',
    uriVariables: [
        'userUuid' => new Link(fromClass: User::class, fromProperty: 'emails'),
    ],
    extraProperties: [
        'standard_put' => true,
    ],
    operations: [
        new GetCollection(
            normalizationContext: ['groups' => ['userEmail:read']],
            security: "is_granted('ROLE_ADMIN') or (is_granted('ROLE_USER') and user.getUuid() == request.attributes.get('userUuid'))",
        ),
        new Post(
            normalizationContext: ['groups' => ['userEmail:read']],
            security: "is_granted('ROLE_ADMIN') or (is_granted('ROLE_USER') and user.getUuid() == request.attributes.get('userUuid'))",
            processor: UserEmailProcessor::class,
        ),
    ]
)]
// @formatter:on

#[ORMEntity(repositoryClass: UserEmailRepository::class)]
#[ORMTable(name: 'user_email')]
#[UniqueEntity(fields: ['emailNumber'], message: 'This email number is already in use.')]
final class UserEmail
{
    #[ApiProperty(identifier: false)]
    #[ORMId]
    #[ORMGeneratedValue(strategy: 'SEQUENCE')]
    #[ORMColumn(type: 'integer')]
    private int $id;
    
    #[ApiProperty(identifier: true)]
    #[AssertUuid(versions: [4], groups: ['userEmail:read', 'userEmail:write'])]
    #[Groups(['userEmail:read', 'user:read'])]
    #[ORMColumn(type: 'string', length: 36, unique: true)]
    private string $uuid;

    #[ApiProperty]
    #[AssertNotBlank]
    #[AssertEmail]
    #[Groups(['userEmail:read', 'userEmail:write', 'user:read'])]
    #[ORMColumn(type: 'string', length: 20, unique: true)]
    private string $email;

    #[ApiProperty]
    #[Groups(['userEmail:read'])]
    #[ORMManyToOne(targetEntity: User::class, inversedBy: 'emails')]
    #[ORMJoinColumn(nullable: false)]
    private User $user;

    public function __construct(?UuidInterface $uuid = null)
    {
        $this->uuid = $uuid?->toString() ?? Uuid::uuid4()->toString();
    }

    // ...
}

POST

First, let’s discuss POST.

POST /users/00000000-0000-0000-0000-000000000001/emails
{
    "email": "[email protected]"
}
{
  "title": "An error occurred",
  "detail": "An exception occurred while executing a query: SQLSTATE[23502]: Not null violation: 7 ERROR:  null value in column "user_id" of relation "user_email" violates not-null constraintnDETAIL:  Failing row contains (1, 6b63235a-8c25-468c-881f-b4ce80618c56, 2222222, null).",
  "status": 500,
  "type": "/errors/500"
}

UserEmail::$user is not being set from the URI as expected.

We can use a state processor to set the UserEmail::$user field manually. We must attach this state processor to the POST operation.

Question #1

Is this something we can solve without a state processor?

UserEmail.php

#[ApiResource(
    operations: new Post(
        processor: UserEmailProcessor::class,
        // ...
    )
    // ...
)]

UserEmailProcessor.php

final readonly class UserEmailProcessor implements ProcessorInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private UserRepository $userRepository,
        private RequestStack $requestStack,
    ) {
    }

    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
    {
        if (!$data instanceof UserEmail) {
            return $data;
        }

        $userUuid = $uriVariables['userUuid'] ?? null;
        
        $user = $this->userRepository->findOneBy(['uuid' => $userUuid]);
        if (!$user) {
            throw new NotFoundHttpException();
        }

        $data->setUser($user);

        $this->entityManager->persist($data);
        $this->entityManager->flush();

        return $data;
    }
}

Now let’s try the same request as before:

POST /users/00000000-0000-0000-0000-000000000001/emails
{
    "email": "[email protected]"
}

201 Created

{
  "uuid": "988a50a6-f77f-47aa-b5de-eaa5029fb1f2",
  "email": "[email protected]",
  "user": "/users/00000000-0000-0000-0000-000000000001"
}

It worked! Let’s test it one more time with a different email:

POST /users/00000000-0000-0000-0000-000000000001/emails
{
    "email": "[email protected]"
}

201 Created

{
  "uuid": "988a50a6-f77f-47aa-b5de-eaa5029fb1f2",
  "email": "[email protected]",
  "user": "/users/00000000-0000-0000-0000-000000000001"
}

WRONG. We received a 201 Created response code and the email is correct. However, the UUID is the exact same. The previous resource was updated or recreated. Note that if we try this with a different user’s UUID, it will also work the first time, but follow-up attempts will show the same problem.

Question #2

What is causing this, and how do we resolve it?

PUT

In the past, API Platform’s PUT operation was notorious for having incorrect behavior, functioning similarly to PATCH. However, with API Platform 4.0, these issues are purportedly resolved.

Let’s try a PUT request.

PUT /users/00000000-0000-0000-0000-000000000001/emails/cccccccc-cccc-cccc-cccc-cccccccccccc
{
    "email": "[email protected]"
}
{
  "uuid": "b797c3fd-2d31-4452-8aaa-b87a0644cc28",
  "email": "[email protected]",
  "user": "/users/00000000-0000-0000-0000-000000000001"
}

The email was created. However, the UUID is incorrect (and by extension, so is the IRI).

Let’s try it again.

PUT /users/00000000-0000-0000-0000-000000000001/emails/dddddddd-dddd-dddd-dddd-dddddddddddd
{
    "email": "[email protected]"
}
{
  "uuid": "92bde1cf-d103-406c-895d-9e96393089f6",
  "email": "[email protected]",
  "user": "/users/00000000-0000-0000-0000-000000000001"
}

The same issue occurred. However, the UUID is different! That means PUT is behaving how we expected POST to originally.

We can use a state processor to set the correct UUID. Since we already have a state processor from before, let’s update it as follows:

Question #3

Again—is this something we can solve without a state processor?

UserEmail.php

#[ApiResource(
    operations: new Put(
        processor: UserEmailProcessor::class,
        // ...
    )
    // ...
)]

UserEmailProcessor.php

// For PUT operations, ensure the UUID from the URI is used
if ($operation instanceof Put) {
    $emailUuid = $uriVariables['uuid'] ?? null;
    if ($emailUuid && $data->getUuid() !== $emailUuid) {
        $data->setUuid($emailUuid);
    }
}

Here, we are manually setting the UUID in the state processor. I don’t like having setters for my identifiers, but we’ll ignore that for now. Let’s try a request:

PUT /users/00000000-0000-0000-0000-000000000001/emails/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee
{
    "email": "[email protected]"
}
{
  "uuid": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
  "email": "[email protected]",
  "user": "/users/00000000-0000-0000-0000-000000000001"
}

It worked! This is exactly what we expected to see. Now let’s try replacing that same resource with a different email:

PUT /users/00000000-0000-0000-0000-000000000001/emails/eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee
{
    "email": "[email protected]"
}
{
  "title": "An error occurred",
  "detail": "An exception occurred while executing a query: SQLSTATE[23505]: Unique violation: 7 ERROR:  duplicate key value violates unique constraint "uniq_a68d6c85d17f50a6"nDETAIL:  Key (uuid)=(eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee) already exists.",
  "status": 500,
  "type": "/errors/500"
}

Yet another problem. PUT is neither updating the existing database rows, nor deleting and repopulating them. Instead, it is simply trying to create a new row with the specified UUID.

Question #4

Yet again—what is causing this, and how do we resolve it?


Thank you for your help!