I have three tables users, addresses and a morph pivot table addressables that looks like this:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('addressables', function (Blueprint $table) {
$table->foreignId('address_id')->constrained()->onDelete('cascade');
$table->unsignedBigInteger('addressable_id');
$table->string('addressable_type', 20); // user, club, company
$table->string('type', 20)->default('home_address');
$table->unique(['address_id', 'addressable_id', 'addressable_type', 'type'], 'addressables_index');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('addressables');
}
};
I also have an AddressController with method store:
public function store(AddressRequest $request): RedirectResponse
{
if ($request->filled('id')) {
// Use existing address
$address = Address::findOrFail($request->input('id'));
} else {
// Create new address
$address = Address::create($request->validated());
}
Log::info('Address: ' . $address);
// Get the addressable type and log it
$addressableType = $request->input('addressable_type');
Log::info('Addressable Type: ' . $addressableType);
// Assuming $addressableType is a fully qualified class name, e.g., App\Models\User
$addressable = $addressableType::find($request->input('addressable_id'));
// Use the relationship to attach the address
$addressable->addresses()->attach($address, ['type' => $request->input('type')]);
return redirect()->back()->with('success', 'Address added successfully.');
}
and here is my blade component submitting a request to the store method:
{{-- This component is used to create new address or attach existing address to edited addressable --}}
<div x-data="{ showForm: false }">
<div class="flex justify-start gap-2 pt-2">
<h2 class="font-bold">{{__("Address")}}</h2>
<button type="button" id="add-address-button" @click="showForm = !showForm">
<i :class="showForm ? 'fa-regular fa-circle-xmark' : 'fa-regular fa-square-plus'"></i>
</button>
</div>
<template x-if="showForm">
<div x-data="addressFormComponent()" class="p-2 bg-amber-100 address">
<form method="POST"
action="{{route('address.store')}}"
class="space-y-2"
@submit.prevent="handleSubmit">
@csrf
<!-- Hidden inputs for addressable -->
<input type="hidden" name="addressable_id" value="{{ $addressable->id }}">
<input type="hidden" name="addressable_type" value="{{ addslashes(get_class($addressable)) }}">
<!-- Address Type Dropdown -->
<div>
<x-input-label for="type" :value="__('Address Type')"/>
<select id="type"
name="type"
x-model="selectedType" {{-- Bind the value of the select element --}}
class="block w-auto rounded-md border-0 py-2 font-bold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:max-w-xs sm:text-sm sm:leading-6">
required>
<option value="home_address">{{__("home_address")}}</option>
<option value="billing_address">{{__("billing_address")}}</option>
</select>
<x-input-error :messages="$errors->get('type')" />
</div>
<!-- id Field -->
<input type="hidden" name="id" id="address_id" x-model="id">
<!-- Care Of Field -->
<div>
<x-input-label for="care_of" :value="__('Care of')" />
<x-text-input id="care_of" name="care_of" :value="old('care_of')" x-bind:disabled="addressSelected"/>
<x-input-error :messages="$errors->get('care_of')" />
</div>
<!-- Street Address Field -->
<div>
<x-input-label for="street_address" :value='__("Street")' />
<x-text-input id="street_address" name="street_address" x-model="street" @input="searchAddresses" x-bind:disabled="addressSelected" required/>
<x-input-error :messages="$errors->get('street_address')" />
</div>
<!-- Postcode and City Fields -->
<div class="flex gap-2">
<div>
<x-input-label for="postcode" :value='__("Postcode")' />
<x-text-input id="postcode" name="postcode" x-model="postcode" @input="searchAddresses" x-bind:disabled="addressSelected" required/>
<x-input-error :messages="$errors->get('postcode')" />
</div>
<div>
<x-input-label for="city" :value='__("City")' />
<x-text-input id="city" name="city" x-model="city" @input="searchAddresses" x-bind:disabled="addressSelected" required/>
<x-input-error :messages="$errors->get('city')" />
</div>
</div>
<!-- Address Search Results -->
<div id="address-results" x-show="searchResults.length > 0" class="mt-2">
<!-- Display search results here -->
<ul>
<li>{{__("Click on address below if it exactly matches yours.")}}</li>
<template x-for="address in searchResults" :key="address.id">
<li>
<button type="button" @click="selectAddress(address)">
<!-- Display address details -->
<span x-text="address.street_address"></span>,
<span x-text="address.postcode"></span>,
<span x-text="address.city"></span>
</button>
</li>
</template>
</ul>
</div>
<!-- Submit Button -->
<div>
<x-primary-button type="submit" x-bind:disabled="isDuplicate" @mouseover="enableFields">
{{ __("Save Address") }}
</x-primary-button>
</div>
</form>
</div>
</template>
</div>
<script>
function addressFormComponent() {
return {
id: null,
street: '',
postcode: '',
city: '',
searchResults: [],
addressSelected: false,
addressablesAddresses:[],
selectedType: '',
isDuplicate: false,
init() {
// Watch for changes in selectedType
this.$watch('selectedType', () => {
this.checkDuplicateAddress();
});
// Initialize selectedType with the default value of the select element
this.selectedType = document.getElementById('type').value;
},
// Search database for addresses similar to what you type into the form fields
async searchAddresses() {
if (this.street.length < 3 && this.postcode.length < 3 && this.city.length < 3) {
this.searchResults = [];
return;
}
try {
const response = await fetch(
`{{ route('addresses.search') }}?street=${encodeURIComponent(this.street)}&postcode=${encodeURIComponent(this.postcode)}&city=${encodeURIComponent(this.city)}`
);
if (response.ok) {
this.searchResults = await response.json();
} else {
console.error('Error fetching address search results:', response.statusText);
}
} catch (error) {
console.error('Error fetching address search results:', error);
}
},
// Select an existing address from database
async selectAddress(address) {
// Implement the functionality to select an address from the search results
console.log('Selected address:', address);
// Set form fields with the selected address details if needed
this.id = address.id;
this.street = address.street_address;
this.postcode = address.postcode;
this.city = address.city;
this.searchResults = [];
this.addressSelected = true; // Disable fields
// Fetch attached addresses id and type
try {
const response = await fetch(
`{{ route('addressables.addresses', ['addressable_id' => $addressable->id, 'addressable_type' => 'ADDRESSABLE_TYPE_PLACEHOLDER']) }}`
.replace('ADDRESSABLE_TYPE_PLACEHOLDER', encodeURIComponent('{{ addslashes(get_class($addressable)) }}'))
);
if (response.ok) {
this.addressablesAddresses = await response.json();
console.log(this.addressablesAddresses);
this.checkDuplicateAddress();
}
} catch (error) {
console.error('Error fetching addresses:', error);
}
},
// Used to disable submit button since storing duplicates would cause unique index violation
checkDuplicateAddress() {
this.isDuplicate = this.addressablesAddresses
.some(addressable => addressable.address_id === this.id && addressable.type === this.selectedType);
console.log('Check, duplicateAddress is: ' + this.isDuplicate);
},
// Enable fields before form submission
enableFields() {
this.addressSelected = false;
},
// Handle form submission
handleSubmit(event) {
// Enable fields before submitting the form
this.enableFields();
// Submit the form programmatically
event.target.submit();
}
};
}
document.addEventListener('alpine:init', () => {
Alpine.data('addressFormComponent', addressFormComponent);
});
</script>
The component is icluded in a user\edit.blade.php like this:
<!-- Add Address Component -->
<x-add-address-form :addressable="$user" />
My problem is that when I submit the form I am getting this output to the log:
[2024-06-07 13:20:07] local.INFO: Address: {"id":1,"care_of":null,"street_address":"Solberga Nordg\u00e5rden 1","city":"Sjuntorp","postcode":"461 97","region":null,"created_at":"2024-06-02T20:07:07.000000Z","updated_at":"2024-06-02T20:07:07.000000Z"}
[2024-06-07 13:20:07] local.INFO: Addressable Type: App\\Models\\User
[2024-06-07 13:20:07] local.ERROR: Cannot declare class App\Models\User, because the name is already in use {"userId":1,"exception":"[object] (Symfony\\Component\\ErrorHandler\\Error\\FatalError(code: 0): Cannot declare class App\\Models\\User, because the name is already in use at C:\\Users\\larsf\\PhpStormProjects\\Pegasos\\app\\Models\\User.php:11)
[stacktrace]
#0 {main}
"}
I of course have a Models\User class for the user and it looks like Laravel is trying to create a new Models/User class when this line of code is executed:
// Assuming $addressableType is a fully qualified class name, e.g., App\Models\User
$addressable = $addressableType::find($request->input('addressable_id'));
Why is this happening, and how can I modify my code to prevent this?
The times I encounter this error is when I defined the same model name (User in your case) in another Model. So, instead of the model name matching the filename, I inadvertently used a model name already defined elsewhere. The other way I encountered this error was when I defined the model more than once in the Controller. Both are "duhhhhh" moments, for me that is.
I was able to usually find this error, including the offending file(s) while doing a "composer update".
Sign in to participate in this thread!
The Laravel portal for problem solving, knowledge sharing and community building.
The community