Poprawna organizacja struktury aplikacji

0

Witam, mam pytanie odnośnie poprawnej struktury aplikacji.
Załóżmy, że łącze się aplikacją do rest-api, który zwraca mi dane takie jak:

  • id użytkownika
  • nazwa
  • data rejestracji

No to idąc po kolei:

  • tworze projekt: composer init, przechodzę pomyślnie przez przygotowanie pliku package.
  • Otrzymuje folder src, vendor etc.
  • Zaczynam programowanie i teraz tak:

Mogę zrobić to tak:

Tworzę plik: mkdir ./src/Users/Users.php

Plik ten mógłby wyglądać tak:

class Users {
	private $users;
	
	public function getAllUsers() {
		// Polaczenie do rest api oraz zapisanie zwroconych danych do zmiennej
		$response = $requestToApi;
		
		// Zwrocone DANE to JSON zakladajac, ze mam juz tablice obiektow robie for po tablicy
		for ($i = 0; $i < count($response); $i++) {
			$this->users[$i] = $response[$i]["id"];
			$this->users[$i] = $response[$i]["name"];
			$this->users[$i] = $response[$i]["created_at"];
		}
	}
	
	public function getUsers() {
		return $this->users;
	}
}

Ale również mogę zrobić to tak:

Tworzę plik: mkdir ./src/Users/Users.php
Tworzę plik: mkdir ./src/Users/User.php

Zawartość pliku Users.php:

class Users {
	private $users;
	
	public function getAllUsers() {
		// Polaczenie do rest api oraz zapisanie zwroconych danych do zmiennej
		$response = $requestToApi;
		
		// Zwrocone DANE to JSON zakladajac, ze mam juz tablice obiektow robie for po tablicy
		for ($i = 0; $i < count($response); $i++) {
			$user = new User();
			$user->setId($response[$i]["id"]);
			// set name
			// set created at
			array_push($this->users, $user);

		}
	}
	
	public function getUsers() {
		return $this->users;
	}
}

Zawartość pliku User.php:

class User {
	private $id;
	private $name;
	private $created_at;
	
	public function setId($id) {
		$this->$id = $id;
	}
	public function getId() {
		return $this->id;
	}
	// reszta setterow i getterow
}

Który z tych modeli jest ,,modelem rozsądnym" ?
A może żaden z tych modeli nie jest poprawny i powinno się to robić w inny sposób ?
Osobiście byłbym za modelem drugim.

Jednakże wtedy w folderze /src/Users/ będę posiadał pliki:
Users.php
User.php

Trochę mi się nie podoba takie rozwiązanie - kole w oczy.
Jeżeli sposób drugi jest ,,dosyć poprawnym" podejściem do OOP to jak ten plik nazwać lub do jakiego folderu go przenieść aby miało to ręce i nogi ?

Ogólnie jeżeli ktoś miałby jakieś ciekawe artykuły na temat ,,przygotowania poprawnej organizacji kodu" chętnie poczytam.

0

Rzuć okiem np. na Laravela czy Symfony - jako tako nie jesteś za daleko od poprawnej drogi ;-)

0

Grzebie trochę w Symfony 2.x, ale tam jest ten kod ,,strasznie złożony", a dokładnie rzecz biorąc sama aplikacja podczas przechodzenia do następnych klas staje się ,,kolosem", a przeglądając klasę 10tą gubie gdzieś wątek dlaczego 5tą czy 4rta się do niej odnosiła.
Jednak może faktycznie poprawnie byłoby zagłębić się ze zrozumieniem w klasy symfony.

Samą logikę tworzenia stron w Symfony rozumiem, oparte na modelu MVC wygląda to dobrze.
Folder z controllerami
Folder z modelami
Folder z widokami

Ten model MVC potrafiłbym przełożyć też na stworzenie strony internetowej, odseparować kontrolery od modeli i widoków, ale w aplikacjach nie zawsze coś ma wracać do widoku, model nie musi się łączyć z bazą danych, a nawet nie musi się łączyć z czymkolwiek i mogą być to samo obliczenia matematyczne, które można by wykonać w modelu, a można by w kontrolerze, można zrobić z nich obiekt (jak User.php), ale można nie robić i zawsze z tą organizacją kodu gdzieś tam kuleje...

0

Wiesz, że gdzieś dzwonią, ale nie za bardzo w którym kościele ;-) Zakładam, że chciałbyś mieć model z prawdziwego zdarzenia, a nie tablicę tyle, że przeszkadza Ci klasa "Users". Dlaczego Users? - nazwa jest lekko od czapy i faktycznie gryzie Ci się z modelem. Sama nazwa jest chybiona i jedynym jako tako sensownym przypadkiem jej wykorzystania byłaby klasa, która manipuluje kolekcjami użytkowników. Np: dostaje pakiet użytkowników i ich filtruje po jakiś parametrach. Ale i tak sensowniejszą nazwą wtedy byłaby 'UsersCollection'.

Nic z powyższych mnie nie przekonuje. Architektura szwankuje. Chcesz pobierać użytkowników przez API (a w przyszłości zapewne nie tylko użytkowników) i chcesz mieć model. Ja bym stworzył klasę 'Api' służącą tylko i wyłącznie do obsługi poboru przez Api dowolnych danych, które wypluwane są zawsze w jednolitej formie np: tablicy asocjacyjnej, stworzyłbym managera klas modelu, który jako zależność (dependency) przyjmuje klasę Api, który umie pobierać dane przez Api i tworzyć modele oraz same klasy modelu. W najprostszym przykładzie:

class Api {
    const USERS_URL = '...';

    /** Zwraca tablicę elementów zwracach przez API będąch tablicami asocjacyjnymi **/
    public function getResponse(string $url) {
        // pobór z api
        return $elements;
    }

    public function getUsers() {
        return $this->getResponse(self::USERS_URL);
    }
}

class EntityManager {
    private $api;
    private $modelName;

    public function __construct(string $modelName, Api $api) {
        $this->modelName = $modelName;
        $this->api = $api;
    }

    public function getUsers() {
        $users = $this->api->getUsers();
        $modelUsers = [];
        foreach ($users as $user) {
            $modelUsers[] = $this->hydrate($user);
        }
        return $modelUsers;
    }

    /** Maps Api response to model object **/
    protected function hydrate(array $apiResponseElement) {
        $model = new {$this->modelName}();
        foreach ($apiResponseElement as $name => $value) {
            $setterMethod = 'set' . ucfirst($name);
            $model->$setterMethod($value);
        }
        return $model;
}

class User() { .... }

użycie:

$userManager = new EntityManager('User', new Api());
$users = $userManager->getUsers();

Starałem się bardzo uprościć, zignorowałem mapowanie parametrów zwracanych przez API na nazwy własności w klasach modelu, czego w dobrej implementacji nie powinno zabraknąć. Sam bym się do tej implementacji w kilku miejscach przyczepił, ale dla zobrazowania drogi jaką powinna iść architektura przy problemie, który zgłosiłeś, powinno wystarczyć. Niezbyt skomplikowane, a potem w banalny sposób to rozbudowujesz...

0

Nie piszę w PHP, ale nie prościej coś w stylu:

class UsersApi {
  private $client;
  
  public function __construct($client) {
    $this->client = $client;
  }
  
  public function all() {
    return $this->client->get('http://some-external-api.com/users').getContent();
  }
}

?

0
Marcin Kula-Bukowski napisał(a):

Zakładam, że chciałbyś mieć model z prawdziwego zdarzenia, a nie tablicę tyle, że przeszkadza Ci klasa "Users".

Nic z powyższych mnie nie przekonuje. Architektura szwankuje. Chcesz pobierać użytkowników przez API (a w przyszłości zapewne nie tylko użytkowników) i chcesz mieć model.

Starałem się bardzo uprościć, zignorowałem mapowanie parametrów zwracanych przez API na nazwy własności w klasach modelu, czego w dobrej implementacji nie powinno zabraknąć. Sam bym się do tej implementacji w kilku miejscach przyczepił, ale dla zobrazowania drogi jaką powinna iść architektura przy problemie, który zgłosiłeś, powinno wystarczyć. Niezbyt skomplikowane, a potem w banalny sposób to rozbudowujesz...

Pierwsze Twoje zdanie jest jak najbardziej prawdziwe, że chciałbym w końcu tworzyć modele z prawdziwego zdarzenia.
Właśnie gdzieś ta Architektura szwankuje i muszę w końcu to nadrobić..

,,Mapowanie parametrów" - rozumiem
,,Sam bym się do tej implementacji w kilku miejscach przyczepił" - rozjaśnisz trochę ?

Skomplikowane faktycznie nie jest, bo rozumiem każdą linijkę kodu, ale jak to sensownie ulokować w folderach ?
Co do samego problemu, to dałem przykład problemu, który wpadł mi do głowy na poczekaniu.

Czasami człowiek chciałby siąść do większego projektu i mieć możliwość dosyć łatwego powrotu do kodu czy jego rozbudowy, a bez dobrej architektury z każdą linią popada się w nicość.

@Maciej Cąderek dzięki i za Twoją sugestie, ale przyznam szczerze, że bardziej wypowiedź Marcina rozjaśnia mi poprawną architekturę.

0

Nie mogę edytować postu, a zapomniałem dopisać.

Gdyby ktoś miał przykłady kodów open-source (github czy podobne, poza frameworkami typu symfony, laravel etc) prosiłbym o podrzucenie linku chętnie przeglądnę.
Dzięki wszystkim też za zainteresowanie tematem.

1 użytkowników online, w tym zalogowanych: 0, gości: 1